import ClientMetricsServiceV2 from './metrics-service-v2'; import getLogger from '../../../test/fixtures/no-logger'; import createStores from '../../../test/fixtures/store'; import EventEmitter from 'events'; import { LastSeenService } from './last-seen/last-seen-service'; import { IClientMetricsStoreV2, IUnleashConfig } from 'lib/types'; import { endOfDay, startOfHour, subDays, subHours } from 'date-fns'; import { IClientMetricsEnv } from '../../types/stores/client-metrics-store-v2'; function initClientMetrics(flagEnabled = true) { const stores = createStores(); const eventBus = new EventEmitter(); eventBus.emit = jest.fn(); const config = { eventBus, getLogger, flagResolver: { isEnabled: () => { return flagEnabled; }, }, } as unknown as IUnleashConfig; const lastSeenService = new LastSeenService( { lastSeenStore: stores.lastSeenStore, featureToggleStore: stores.featureToggleStore, }, config, ); lastSeenService.updateLastSeen = jest.fn(); const service = new ClientMetricsServiceV2(stores, config, lastSeenService); return { clientMetricsService: service, eventBus, lastSeenService }; } test('process metrics properly', async () => { const { clientMetricsService, eventBus, lastSeenService } = initClientMetrics(); await clientMetricsService.registerClientMetrics( { appName: 'test', bucket: { start: '1982-07-25T12:00:00.000Z', stop: '2023-07-25T12:00:00.000Z', toggles: { myCoolToggle: { yes: 25, no: 42, variants: { blue: 6, green: 15, red: 46, }, }, myOtherToggle: { yes: 0, no: 100, }, }, }, environment: 'test', }, '127.0.0.1', ); expect(eventBus.emit).toHaveBeenCalledTimes(1); expect(lastSeenService.updateLastSeen).toHaveBeenCalledTimes(1); }); test('process metrics properly even when some names are not url friendly, filtering out invalid names when flag is on', async () => { const { clientMetricsService, eventBus, lastSeenService } = initClientMetrics(); await clientMetricsService.registerClientMetrics( { appName: 'test', bucket: { start: '1982-07-25T12:00:00.000Z', stop: '2023-07-25T12:00:00.000Z', toggles: { 'not url friendly ☹': { yes: 0, no: 100, }, }, }, environment: 'test', }, '127.0.0.1', ); // only toggle with a bad name gets filtered out expect(eventBus.emit).not.toHaveBeenCalled(); expect(lastSeenService.updateLastSeen).not.toHaveBeenCalled(); }); test('process metrics properly even when some names are not url friendly, with default behavior when flag is off', async () => { const { clientMetricsService, eventBus, lastSeenService } = initClientMetrics(false); await clientMetricsService.registerClientMetrics( { appName: 'test', bucket: { start: '1982-07-25T12:00:00.000Z', stop: '2023-07-25T12:00:00.000Z', toggles: { 'not url friendly ☹': { yes: 0, no: 100, }, }, }, environment: 'test', }, '127.0.0.1', ); expect(eventBus.emit).toHaveBeenCalledTimes(1); expect(lastSeenService.updateLastSeen).toHaveBeenCalledTimes(1); }); test('get daily client metrics for a toggle', async () => { const yesterday = subDays(new Date(), 1); const twoDaysAgo = subDays(new Date(), 2); const threeDaysAgo = subDays(new Date(), 3); const baseData = { featureName: 'feature', appName: 'test', environment: 'development', yes: 0, no: 0, }; const clientMetricsStoreV2 = { getMetricsForFeatureToggleV2( featureName: string, hoursBack?: number, ): Promise { return Promise.resolve([ { ...baseData, timestamp: endOfDay(yesterday), yes: 2, no: 1, variants: { a: 1, b: 1 }, }, ]); }, } as IClientMetricsStoreV2; const config = { flagResolver: { isEnabled() { return true; }, }, getLogger() {}, } as unknown as IUnleashConfig; const lastSeenService = {} as LastSeenService; const service = new ClientMetricsServiceV2( { clientMetricsStoreV2 }, config, lastSeenService, ); const metrics = await service.getClientMetricsForToggle('feature', 3 * 24); expect(metrics).toMatchObject([ { ...baseData, timestamp: endOfDay(threeDaysAgo) }, { ...baseData, timestamp: endOfDay(twoDaysAgo) }, { ...baseData, timestamp: endOfDay(yesterday), yes: 2, no: 1, variants: { a: 1, b: 1 }, }, ]); }); test('get hourly client metrics for a toggle', async () => { const hourAgo = startOfHour(subHours(new Date(), 1)); const thisHour = startOfHour(new Date()); const baseData = { featureName: 'feature', appName: 'test', environment: 'development', yes: 0, no: 0, }; const clientMetricsStoreV2 = { getMetricsForFeatureToggleV2( featureName: string, hoursBack?: number, ): Promise { return Promise.resolve([ { ...baseData, timestamp: thisHour, yes: 2, no: 1, variants: { a: 1, b: 1 }, }, ]); }, } as IClientMetricsStoreV2; const config = { flagResolver: { isEnabled() { return true; }, }, getLogger() {}, } as unknown as IUnleashConfig; const lastSeenService = {} as LastSeenService; const service = new ClientMetricsServiceV2( { clientMetricsStoreV2 }, config, lastSeenService, ); const metrics = await service.getClientMetricsForToggle('feature', 2); expect(metrics).toMatchObject([ { ...baseData, timestamp: hourAgo }, { ...baseData, timestamp: thisHour, yes: 2, no: 1, variants: { a: 1, b: 1 }, }, ]); });