UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

172 lines • 6.23 kB
import { responseTimeMetrics, storeRequestedRoute, } from './response-time-metrics.js'; import { REQUEST_TIME } from '../metric-events.js'; import { vi } from 'vitest'; import EventEmitter from 'events'; const isDefined = async (timeInfo, limit = 10) => { let counter = 0; while (timeInfo === undefined) { // Waiting for event to be triggered await new Promise((resolve) => setTimeout(resolve, 10)); counter++; if (counter > limit) { throw new Error('Event was not triggered'); } } }; const flagResolver = { isEnabled: vi.fn(), getAll: vi.fn(), getVariant: vi.fn(), getStaticContext: vi.fn(), }; // Make sure it's always cleaned up let res; beforeEach(() => { res = { statusCode: 200, locals: {}, // res will always have locals (according to express RequestHandler type) once: vi.fn((event, callback) => { if (event === 'finish') { callback(); } }), }; }); describe('responseTimeMetrics new behavior', () => { const instanceStatsService = { getAppCountSnapshot: vi.fn(), }; const eventBus = new EventEmitter(); test('uses baseUrl and route path to report metrics with flag enabled, but no res.locals.route', async () => { let timeInfo; // register a listener eventBus.on(REQUEST_TIME, (data) => { timeInfo = data; }); const middleware = responseTimeMetrics(eventBus, flagResolver, instanceStatsService); const req = { baseUrl: '/api/admin', route: { path: '/features', }, method: 'GET', path: 'should-not-be-used', headers: {}, }; // @ts-expect-error req doesn't have all properties and we're not passing next middleware(req, res, () => { }); await isDefined(timeInfo); expect(timeInfo).toMatchObject({ path: '/api/admin/features', method: 'GET', statusCode: 200, time: expect.any(Number), }); expect(timeInfo.time).toBeGreaterThan(0); expect(res.once).toHaveBeenCalledWith('finish', expect.any(Function)); }); test('uses res.locals.route to report metrics when flag enabled', async () => { let timeInfo; // register a listener eventBus.on(REQUEST_TIME, (data) => { timeInfo = data; }); const middleware = responseTimeMetrics(eventBus, flagResolver, instanceStatsService); const req = { baseUrl: '/api/admin', route: { path: '/features', }, method: 'GET', path: 'should-not-be-used', }; const reqWithoutRoute = { method: 'GET', headers: {}, }; // @ts-expect-error req and res doesn't have all properties storeRequestedRoute(req, res, () => { }); // @ts-expect-error req and res doesn't have all properties middleware(reqWithoutRoute, res, () => { }); await isDefined(timeInfo); expect(timeInfo).toMatchObject({ path: '/api/admin/features', method: 'GET', statusCode: 200, time: expect.any(Number), }); expect(timeInfo.time).toBeGreaterThan(0); expect(res.once).toHaveBeenCalledWith('finish', expect.any(Function)); }); test.each([undefined, '/'])('reports (hidden) when route is undefined and path is %s', async (path) => { let timeInfo; // register a listener eventBus.on(REQUEST_TIME, (data) => { timeInfo = data; }); const middleware = responseTimeMetrics(eventBus, flagResolver, instanceStatsService); const req = { baseUrl: '/api/admin', method: 'GET', path: 'should-not-be-used', }; const reqWithoutRoute = { method: 'GET', path, headers: {}, }; // @ts-expect-error req and res doesn't have all properties storeRequestedRoute(req, res, () => { }); // @ts-expect-error req and res doesn't have all properties middleware(reqWithoutRoute, res, () => { }); await isDefined(timeInfo); expect(timeInfo).toMatchObject({ path: '(hidden)', method: 'GET', statusCode: 200, time: expect.any(Number), }); expect(timeInfo.time).toBeGreaterThan(0); expect(res.once).toHaveBeenCalledWith('finish', expect.any(Function)); }); test.each([ ['/api/admin/features', '/api/admin/(hidden)'], ['/api/admin/features/my-feature', '/api/admin/(hidden)'], ['/api/frontend/client/metrics', '/api/frontend/(hidden)'], ['/api/client/metrics', '/api/client/(hidden)'], ['/edge/validate', '/edge/(hidden)'], ['/whatever', '(hidden)'], ['/healthz', '(hidden)'], ['/internal-backstage/prometheus', '(hidden)'], ])('when path is %s and route is undefined, reports %s', async (path, expected) => { let timeInfo; // register a listener eventBus.on(REQUEST_TIME, (data) => { timeInfo = data; }); const middleware = responseTimeMetrics(eventBus, flagResolver, instanceStatsService); const req = { baseUrl: '/api/admin', method: 'GET', path: 'should-not-be-used', }; const reqWithoutRoute = { method: 'GET', path, headers: {}, }; // @ts-expect-error req and res doesn't have all properties storeRequestedRoute(req, res, () => { }); // @ts-expect-error req and res doesn't have all properties middleware(reqWithoutRoute, res, () => { }); await isDefined(timeInfo); expect(timeInfo).toMatchObject({ path: expected, time: expect.any(Number), method: 'GET', statusCode: 200, }); expect(timeInfo.time).toBeGreaterThan(0); }); }); //# sourceMappingURL=response-time-metrics.test.js.map