unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
270 lines • 9.49 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const metrics_service_v2_1 = __importDefault(require("./metrics-service-v2"));
const no_logger_1 = __importDefault(require("../../../../test/fixtures/no-logger"));
const store_1 = __importDefault(require("../../../../test/fixtures/store"));
const events_1 = __importDefault(require("events"));
const last_seen_service_1 = require("../last-seen/last-seen-service");
const date_fns_1 = require("date-fns");
function initClientMetrics(flagEnabled = true) {
const stores = (0, store_1.default)();
const eventBus = new events_1.default();
eventBus.emit = jest.fn();
const config = {
eventBus,
getLogger: no_logger_1.default,
flagResolver: {
isEnabled: () => {
return flagEnabled;
},
},
};
const lastSeenService = new last_seen_service_1.LastSeenService({
lastSeenStore: stores.lastSeenStore,
}, config);
lastSeenService.updateLastSeen = jest.fn();
const service = new metrics_service_v2_1.default(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 = (0, date_fns_1.subDays)(new Date(), 1);
const twoDaysAgo = (0, date_fns_1.subDays)(new Date(), 2);
const threeDaysAgo = (0, date_fns_1.subDays)(new Date(), 3);
const baseData = {
featureName: 'feature',
appName: 'test',
environment: 'development',
yes: 0,
no: 0,
};
const clientMetricsStoreV2 = {
getMetricsForFeatureToggleV2(featureName, hoursBack) {
return Promise.resolve([
{
...baseData,
timestamp: (0, date_fns_1.endOfDay)(yesterday),
yes: 2,
no: 1,
variants: { a: 1, b: 1 },
},
]);
},
};
const config = {
flagResolver: {
isEnabled() {
return true;
},
},
getLogger() { },
};
const lastSeenService = {};
const service = new metrics_service_v2_1.default({ clientMetricsStoreV2 }, config, lastSeenService);
const metrics = await service.getClientMetricsForToggle('feature', 3 * 24);
expect(metrics).toMatchObject([
{ ...baseData, timestamp: (0, date_fns_1.endOfDay)(threeDaysAgo) },
{ ...baseData, timestamp: (0, date_fns_1.endOfDay)(twoDaysAgo) },
{
...baseData,
timestamp: (0, date_fns_1.endOfDay)(yesterday),
yes: 2,
no: 1,
variants: { a: 1, b: 1 },
},
]);
});
test('get hourly client metrics for a toggle', async () => {
const hourAgo = (0, date_fns_1.startOfHour)((0, date_fns_1.subHours)(new Date(), 1));
const thisHour = (0, date_fns_1.startOfHour)(new Date());
const baseData = {
featureName: 'feature',
appName: 'test',
environment: 'development',
yes: 0,
no: 0,
};
const clientMetricsStoreV2 = {
getMetricsForFeatureToggleV2(featureName, hoursBack) {
return Promise.resolve([
{
...baseData,
timestamp: thisHour,
yes: 2,
no: 1,
variants: { a: 1, b: 1 },
},
]);
},
};
const config = {
flagResolver: {
isEnabled() {
return true;
},
},
getLogger() { },
};
const lastSeenService = {};
const service = new metrics_service_v2_1.default({ 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 },
},
]);
});
const setupMetricsService = ({ enabledCount, variantCount, enabledDailyCount, variantDailyCount, limit, }) => {
let aggregationCalled = false;
let recordedWarning = '';
const clientMetricsStoreV2 = {
aggregateDailyMetrics() {
aggregationCalled = true;
return Promise.resolve();
},
countPreviousDayHourlyMetricsBuckets() {
return { enabledCount, variantCount };
},
countPreviousDayMetricsBuckets() {
return {
enabledCount: enabledDailyCount,
variantCount: variantDailyCount,
};
},
};
const config = {
flagResolver: {
isEnabled() {
return true;
},
getVariant() {
return { payload: { value: limit } };
},
},
getLogger() {
return {
warn(message) {
recordedWarning = message;
},
};
},
};
const lastSeenService = {};
const service = new metrics_service_v2_1.default({ clientMetricsStoreV2 }, config, lastSeenService);
return {
service,
aggregationCalled: () => aggregationCalled,
recordedWarning: () => recordedWarning,
};
};
test('do not aggregate previous day metrics when metrics already calculated', async () => {
const { service, recordedWarning, aggregationCalled } = setupMetricsService({
enabledCount: 2,
variantCount: 4,
enabledDailyCount: 2,
variantDailyCount: 4,
limit: 6,
});
await service.aggregateDailyMetrics();
expect(recordedWarning()).toBe('');
expect(aggregationCalled()).toBe(false);
});
test('do not aggregate previous day metrics when metrics count is below limit', async () => {
const { service, recordedWarning, aggregationCalled } = setupMetricsService({
enabledCount: 2,
variantCount: 4,
enabledDailyCount: 0,
variantDailyCount: 0,
limit: 5,
});
await service.aggregateDailyMetrics();
expect(recordedWarning()).toBe('Skipping previous day metrics aggregation. Too many results. Expected max value: 5, Actual value: 6');
expect(aggregationCalled()).toBe(false);
});
test('aggregate previous day metrics', async () => {
const { service, recordedWarning, aggregationCalled } = setupMetricsService({
enabledCount: 2,
variantCount: 4,
enabledDailyCount: 0,
variantDailyCount: 0,
limit: 6,
});
await service.aggregateDailyMetrics();
expect(recordedWarning()).toBe('');
expect(aggregationCalled()).toBe(true);
});
//# sourceMappingURL=metrics-service-v2.test.js.map