UNPKG

@unleash/proxy

Version:
895 lines (784 loc) 23.9 kB
import request, { type Response } from 'supertest'; import { createApp } from '../app'; import metrics from '../examples/metrics.json'; import MockClient from './client.mock'; const unleashUrl = 'http://localhost:4242/test'; const unleashApiToken = 's1'; test('Should return empty list of toggles', () => { const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); return request(app) .get('/proxy') .set('Authorization', 'sdf') .expect(200) .expect('Content-Type', /json/) .then((response: Response) => { expect(response.body.toggles).toEqual([]); }); }); test('Should return list of toggles', () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, { name: 'test2', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); return request(app) .get('/proxy') .set('Authorization', 'sdf') .expect(200) .expect('Content-Type', /json/) .then((response) => { expect(response.body.toggles.length).toEqual(2); }); }); test('Should handle POST with empty/nonsensical body', async () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, { name: 'test2', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); await Promise.all( [{ blah: 'hello' }, undefined, {}].map((body) => request(app) .post('/proxy') .type('json') .send(body) .set('Accept', 'application/json') .set('Authorization', 'sdf') .expect(200) .expect('Content-Type', /json/) .then((response) => { expect(response.body.toggles).toHaveLength(2); }), ), ); }); test('Should handle POST with extra context properties', () => { const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); return request(app) .post('/proxy') .send({ context: { customProperty: 'string', properties: { otherCustomProperty: 24 }, }, }) .set('Accept', 'application/json') .set('Authorization', 'sdf') .expect(200) .expect('Content-Type', /json/); }); test('Should handle POST with toggle names', () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, { name: 'test2', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); return request(app) .post('/proxy') .send({ toggles: ['test'] }) .set('Accept', 'application/json') .set('Authorization', 'sdf') .expect(200) .expect('Content-Type', /json/) .then((response) => { expect(response.body.toggles).toHaveLength(1); expect(response.body.toggles[0].name).toBe('test'); }); }); test('Should return list of toggles with custom proxy client key header', () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, { name: 'test2', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken, clientKeysHeaderName: 'NotAuthorized', }, client, ); client.emit('ready'); return request(app) .get('/proxy') .set('NotAuthorized', 'sdf') .expect(200) .expect('Content-Type', /json/) .then((response) => { expect(response.body.toggles.length).toEqual(2); }); }); test('Should return list of toggles using env with multiple secrets', () => { process.env.UNLEASH_PROXY_SECRETS = 'secret1,secret2'; const toggles = [ { name: 'test', enabled: true, impressionData: true, }, { name: 'test2', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const app = createApp({ unleashUrl, unleashApiToken }, client); client.emit('ready'); return request(app) .get('/proxy') .set('Authorization', 'secret2') .expect(200) .expect('Content-Type', /json/) .then((response) => { expect(response.body.toggles.length).toEqual(2); }); }); test('Should return list of toggles using env with multiple secrets with space', () => { process.env.UNLEASH_PROXY_SECRETS = 'secret11, secret22'; const toggles = [ { name: 'test', enabled: true, impressionData: true, }, { name: 'test2', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const app = createApp({ unleashUrl, unleashApiToken }, client); client.emit('ready'); return request(app) .get('/proxy') .set('Authorization', 'secret22') .expect(200) .expect('Content-Type', /json/) .then((response) => { expect(response.body.toggles.length).toEqual(2); }); }); test('Should send in context to mock', async () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); await request(app) .get('/proxy?userId=123&tenantId=me') .set('Authorization', 'sdf') .expect(200) .expect('Content-Type', /json/); expect(client.queriedContexts[0].userId).toEqual('123'); expect(client.queriedContexts[0].properties?.tenantId).toEqual('me'); }); test('Should send in context with ip as remoteAddress', async () => { const userIp = '123.13.13.42'; const toggles = [ { name: 'test', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken, trustProxy: true }, client, ); client.emit('ready'); await request(app) .get('/proxy?userId=123&tenantId=me') .set('Authorization', 'sdf') .set('X-Forwarded-For', userIp) .expect(200) .expect('Content-Type', /json/); expect(client.queriedContexts[0].remoteAddress).toEqual(userIp); }); test('Should remove "undefined" environment field from context', async () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, environment: 'test', }, client, ); client.emit('ready'); await request(app) .get('/proxy?userId=123&tenantId=me') .set('Authorization', 'sdf') .expect(200) .expect('Content-Type', /json/); expect(client.queriedContexts[0]).not.toHaveProperty('environment'); }); test('Providing a string for `properties` yields a 400', async () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, environment: 'test', }, client, ); client.emit('ready'); await request(app) .get('/proxy?userId=123&properties=string') .set('Authorization', 'sdf') .expect(400); }); test('Should register metrics', () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets }, client, ); client.emit('ready'); return request(app) .post('/proxy/client/metrics') .send(metrics) .set('Authorization', 'sdf') .expect(200); }); test('Should register metrics an /all path as well', () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets }, client, ); client.emit('ready'); return request(app) .post('/proxy/all/client/metrics') .send(metrics) .set('Authorization', 'sdf') .expect(200); }); test('Should require metrics to have correct format', () => { const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets }, client, ); client.emit('ready'); return request(app) .post('/proxy/client/metrics') .send({ some: 'blob' }) .set('Authorization', 'sdf') .expect(400); }); test('Should return errors as JSON', () => { const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets }, client, ); client.emit('ready'); return request(app) .post('/proxy/client/metrics') .send({ some: 'blob' }) .set('Authorization', 'sdf') .expect(400) .expect('Content-Type', /json/); }); describe.each([ '', '/all', '/health', '/client/features', '/internal-backstage/prometheus', ])('GET should return not ready', (url) => { test(`for ${url}`, () => { const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets }, client, ); return request(app).get(`/proxy${url}`).expect(503); }); }); describe.each(['', '/all', '/client/features'])( 'Requires valid token', (url) => { test(`Should return 401 if invalid api token for ${url}`, () => { const client = new MockClient(); const proxySecrets = ['secret']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); return request(app) .get(`/proxy${url}`) .set('Authorization', 'I do not know your secret') .expect(401); }); test(`Should return 401 if no api token for ${url}`, () => { const client = new MockClient(); const proxySecrets = ['secret']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); return request(app).get(`/proxy${url}`).expect(401); }); }, ); describe.each([ { url: '/health', responseBody: 'ok' }, { url: '/internal-backstage/prometheus', responseBody: 'unleash_proxy_up 1', }, ])('Do not require valid token', ({ url, responseBody }) => { test(`Should not require a token for ${url}`, () => { const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets }, client, ); client.emit('ready'); return request(app) .get(`/proxy${url}`) .expect(200) .then((response) => { expect(response.text).toMatch(responseBody); }); }); }); test('Should return 504 for proxy', () => { const client = new MockClient(); const app = createApp({ unleashUrl, unleashApiToken }, client); return request(app) .get('/proxy') .set('Authorization', 'secret2') .expect(503); }); test('Should allow every origin (*)', async () => { const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); const response = await request(app) .get('/proxy?userId=999&tenantId=me') .set('Origin', 'https://test.com') .set('Authorization', 'sdf') .expect(200) .expect('Content-Type', /json/); expect(response.headers).toHaveProperty('access-control-allow-origin', '*'); }); test('Should return the same origin based on cors options', async () => { const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken, cors: { origin: [ 'https://example.com', 'https://demo.unleash-hosted.com', ], }, }, client, ); client.emit('ready'); const response = await request(app) .get('/proxy?userId=999&tenantId=me') .set('Origin', 'https://demo.unleash-hosted.com') .set('Authorization', 'sdf') .expect(200) .expect('Content-Type', /json/); expect(response.headers).toHaveProperty( 'access-control-allow-origin', 'https://demo.unleash-hosted.com', ); }); test('Should return 400 bad request for malformed JSON', async () => { const body = '{ toggles": [] }'; const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { proxySecrets, unleashUrl, unleashApiToken }, client, ); client.emit('ready'); await request(app) .post('/proxy') .type('json') .send(body) .set('Accept', 'application/json') .set('Authorization', 'sdf') .expect(400) .expect('Content-Type', /json/); }); test('Should register server SDK', async () => { const toggles = [ { name: 'test', enabled: true, impressionData: true, }, ]; const client = new MockClient(toggles); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, expServerSideSdkConfig: { tokens: ['s1'] }, }, client, ); client.emit('ready'); const res = await request(app) .post('/proxy/client/register') .send({ appName: 'test', instanceId: 'i1', sdkVersion: 'custom1', environment: 'prod', interval: 10000, started: new Date(), strategies: ['default'], }) .set('Authorization', 'sdf'); expect(res.statusCode).toBe(200); }); test('Should return all feature toggles', () => { const client = new MockClient([ { name: 'a', enabled: true, impressionData: false }, { name: 'b', enabled: false, impressionData: false }, { name: 'c', enabled: true, impressionData: true }, ]); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, enableAllEndpoint: true }, client, ); client.emit('ready'); return request(app) .get('/proxy/all') .set('Authorization', 'sdf') .expect(200) .expect((res) => { expect(res.body.toggles.length).toBe(3); }); }); test('/client/features should return toggle definitions', () => { const client = new MockClient([ { name: 'a', enabled: true, impressionData: false }, { name: 'b', enabled: false, impressionData: false }, { name: 'c', enabled: true, impressionData: true }, ]); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, enableAllEndpoint: true, expServerSideSdkConfig: { tokens: ['server-side'] }, }, client, ); client.emit('ready'); return request(app) .get('/proxy/client/features') .set('Authorization', 'server-side') .expect(200) .expect((res) => { expect(res.body.features.length).toBe(3); expect(res.body.features[0].strategies.length).toBe(1); }); }); test('/client/features should not accept proxy secret', () => { const client = new MockClient([ { name: 'a', enabled: true, impressionData: false }, { name: 'b', enabled: false, impressionData: false }, { name: 'c', enabled: true, impressionData: true }, ]); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, enableAllEndpoint: true, expServerSideSdkConfig: { tokens: ['server-side'] }, }, client, ); client.emit('ready'); return request(app) .get('/proxy/client/features') .set('Authorization', 'sdf') .expect(401); }); test('Should return all feature toggles via POST', () => { const client = new MockClient([ { name: 'a', enabled: true, impressionData: false }, { name: 'b', enabled: false, impressionData: false }, { name: 'c', enabled: true, impressionData: true }, ]); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, enableAllEndpoint: true }, client, ); client.emit('ready'); return request(app) .post('/proxy/all') .send({ context: { customProperty: 'string', properties: { otherCustomProperty: 24 }, }, }) .set('Authorization', 'sdf') .expect(200) .expect((res) => { expect(res.body.toggles.length).toBe(3); }); }); test('Should return all named feature toggles via POST', () => { const client = new MockClient([ { name: 'a', enabled: true, impressionData: false }, { name: 'b', enabled: false, impressionData: false }, { name: 'c', enabled: true, impressionData: true }, ]); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, enableAllEndpoint: true }, client, ); client.emit('ready'); return request(app) .post('/proxy/all') .send({ toggles: ['a'], context: { customProperty: 'string', properties: { otherCustomProperty: 24 }, }, }) .set('Authorization', 'sdf') .expect(200) .expect((res) => { expect(res.body.toggles.length).toBe(1); expect(res.body.toggles[0].name).toBe('a'); }); }); test('Should return all enabled feature toggles when POST-ing', () => { const client = new MockClient([ { name: 'a', enabled: true, impressionData: false }, { name: 'c', enabled: true, impressionData: true }, ]); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, enableAllEndpoint: true }, client, ); client.emit('ready'); return request(app) .post('/proxy') .set('Authorization', 'sdf') .expect(200) .expect((res) => { expect(res.body.toggles.length).toBe(2); }); }); test('Should return 501 when all feature toggles is not enabled', () => { const client = new MockClient([ { name: 'a', enabled: true, impressionData: false }, ]); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets }, client, ); client.emit('ready'); return request(app) .get('/proxy/all') .set('Authorization', 'sdf') .expect(501); }); describe('Request content-types', () => { test.each(['/proxy', '/proxy/all'])( 'Should assign default content-type if the request has a body but no content-type (%s)', async (endpoint) => { const client = new MockClient(); const payload = { context: { appName: 'my-app' }, }; const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, enableAllEndpoint: true, }, client, ); client.emit('ready'); await request(app) .post(endpoint) .set('Authorization', 'sdf') .set('Content-Type', '') .send(payload) .expect(200) .then(() => { expect(client.queriedContexts[0].appName).toEqual( payload.context.appName, ); }); }, ); test.each(['/proxy', '/proxy/all'])( 'Should reject non-"content-type: application/json" for POST requests to %s', (endpoint) => { const client = new MockClient(); const proxySecrets = ['sdf']; const app = createApp( { unleashUrl, unleashApiToken, proxySecrets, enableAllEndpoint: true, }, client, ); client.emit('ready'); return request(app) .post(endpoint) .set('Authorization', 'sdf') .set('Content-Type', 'application/html') .send('<em>reject me!</em>') .expect(415); }, ); });