unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
289 lines • 11.5 kB
JavaScript
import ClientInstanceService from './instance-service.js';
import FakeEventStore from '../../../../test/fixtures/fake-event-store.js';
import { createTestConfig } from '../../../../test/config/test-config.js';
import { FakePrivateProjectChecker } from '../../private-project/fakePrivateProjectChecker.js';
import FakeClientMetricsStoreV2 from '../client-metrics/fake-client-metrics-store-v2.js';
import FakeStrategiesStore from '../../../../test/fixtures/fake-strategies-store.js';
import FakeFeatureToggleStore from '../../feature-toggle/fakes/fake-feature-toggle-store.js';
import { vi } from 'vitest';
let config;
beforeAll(() => {
config = createTestConfig({});
});
test('Multiple registrations of same appname and instanceid within same time period should only cause one registration', async () => {
const appStoreSpy = vi.fn();
const bulkSpy = vi.fn();
const clientApplicationsStore = {
bulkUpsert: appStoreSpy,
};
const clientInstanceStore = {
bulkUpsert: bulkSpy,
};
const clientMetrics = new ClientInstanceService({
clientMetricsStoreV2: new FakeClientMetricsStoreV2(),
strategyStore: new FakeStrategiesStore(),
featureToggleStore: new FakeFeatureToggleStore(),
clientApplicationsStore,
clientInstanceStore,
eventStore: new FakeEventStore(),
}, config, new FakePrivateProjectChecker());
const client1 = {
appName: 'test_app',
instanceId: 'ava',
strategies: [{ name: 'defaullt' }],
started: new Date(),
interval: 10,
};
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.bulkAdd(); // in prod called by a SchedulerService
expect(appStoreSpy).toHaveBeenCalledTimes(1);
expect(bulkSpy).toHaveBeenCalledTimes(1);
const registrations = appStoreSpy.mock
.calls[0][0];
expect(registrations.length).toBe(1);
expect(registrations[0].appName).toBe(client1.appName);
expect(registrations[0].instanceId).toBe(client1.instanceId);
expect(registrations[0].started).toBe(client1.started);
expect(registrations[0].interval).toBe(client1.interval);
vi.useRealTimers();
});
test('Multiple unique clients causes multiple registrations', async () => {
const appStoreSpy = vi.fn();
const bulkSpy = vi.fn();
const clientApplicationsStore = {
bulkUpsert: appStoreSpy,
};
const clientInstanceStore = {
bulkUpsert: bulkSpy,
};
const clientMetrics = new ClientInstanceService({
clientMetricsStoreV2: new FakeClientMetricsStoreV2(),
strategyStore: new FakeStrategiesStore(),
featureToggleStore: new FakeFeatureToggleStore(),
clientApplicationsStore,
clientInstanceStore,
eventStore: new FakeEventStore(),
}, config, new FakePrivateProjectChecker());
const client1 = {
appName: 'test_app',
instanceId: 'client1',
strategies: [{ name: 'defaullt' }],
started: new Date(),
interval: 10,
};
const client2 = {
appName: 'test_app_2',
instanceId: 'client2',
strategies: [{ name: 'defaullt' }],
started: new Date(),
interval: 10,
};
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client2, '127.0.0.1');
await clientMetrics.registerBackendClient(client2, '127.0.0.1');
await clientMetrics.registerBackendClient(client2, '127.0.0.1');
await clientMetrics.bulkAdd(); // in prod called by a SchedulerService
const registrations = appStoreSpy.mock
.calls[0][0];
expect(registrations.length).toBe(2);
});
test('Same client registered outside of dedup interval will be registered twice', async () => {
const appStoreSpy = vi.fn();
const bulkSpy = vi.fn();
const clientApplicationsStore = {
bulkUpsert: appStoreSpy,
};
const clientInstanceStore = {
bulkUpsert: bulkSpy,
};
const clientMetrics = new ClientInstanceService({
clientMetricsStoreV2: new FakeClientMetricsStoreV2(),
strategyStore: new FakeStrategiesStore(),
featureToggleStore: new FakeFeatureToggleStore(),
clientApplicationsStore,
clientInstanceStore,
eventStore: new FakeEventStore(),
}, config, new FakePrivateProjectChecker());
const client1 = {
appName: 'test_app',
instanceId: 'client1',
strategies: [{ name: 'defaullt' }],
started: new Date(),
interval: 10,
};
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.bulkAdd(); // in prod called by a SchedulerService
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.registerBackendClient(client1, '127.0.0.1');
await clientMetrics.bulkAdd(); // in prod called by a SchedulerService
expect(appStoreSpy).toHaveBeenCalledTimes(2);
expect(bulkSpy).toHaveBeenCalledTimes(2);
const firstRegistrations = appStoreSpy.mock.calls[0][0][0];
const secondRegistrations = appStoreSpy.mock.calls[1][0][0];
expect(firstRegistrations.appName).toBe(secondRegistrations.appName);
expect(firstRegistrations.instanceId).toBe(secondRegistrations.instanceId);
});
test('No registrations during a time period will not call stores', async () => {
const appStoreSpy = vi.fn();
const bulkSpy = vi.fn();
const clientApplicationsStore = {
bulkUpsert: appStoreSpy,
};
const clientInstanceStore = {
bulkUpsert: bulkSpy,
};
const clientMetrics = new ClientInstanceService({
clientMetricsStoreV2: new FakeClientMetricsStoreV2(),
strategyStore: new FakeStrategiesStore(),
featureToggleStore: new FakeFeatureToggleStore(),
clientApplicationsStore,
clientInstanceStore,
eventStore: new FakeEventStore(),
}, config, new FakePrivateProjectChecker());
await clientMetrics.bulkAdd(); // in prod called by a SchedulerService
expect(appStoreSpy).toHaveBeenCalledTimes(0);
expect(bulkSpy).toHaveBeenCalledTimes(0);
});
test('filter out private projects from overview', async () => {
const clientApplicationsStore = {
async getApplicationOverview(_appName) {
return {
environments: [
{
name: 'development',
instanceCount: 1,
sdks: ['unleash-client-node:3.5.1'],
backendSdks: ['unleash-client-node:3.5.1'],
frontendSdks: [],
lastSeen: new Date(),
issues: {
missingFeatures: [],
outdatedSdks: [],
},
},
],
projects: ['privateProject', 'publicProject'],
issues: {
missingStrategies: [],
},
featureCount: 0,
};
},
};
const privateProjectsChecker = {
async filterUserAccessibleProjects(_userId, projects) {
return projects.filter((project) => !project.includes('private'));
},
};
const clientInstanceService = new ClientInstanceService({ clientApplicationsStore }, config, privateProjectsChecker);
const overview = await clientInstanceService.getApplicationOverview('appName', 123);
expect(overview).toMatchObject({
environments: [
{
name: 'development',
instanceCount: 1,
sdks: ['unleash-client-node:3.5.1'],
issues: {
missingFeatures: [],
outdatedSdks: ['unleash-client-node:3.5.1'],
},
},
],
projects: ['publicProject'],
issues: {
missingStrategies: [],
},
featureCount: 0,
});
});
test('`registerInstance` sets `instanceId` to `default` if it is not provided', async () => {
const instanceService = new ClientInstanceService({}, config, {});
await instanceService.registerInstance({
appName: 'appName',
environment: '',
}, '::1');
expect(instanceService.seenClients.appName_default).toMatchObject({
appName: 'appName',
instanceId: 'default',
});
});
describe('upserting into `seenClients`', () => {
test('registerInstance merges its data', async () => {
const instanceService = new ClientInstanceService({}, config, {});
const client = {
appName: 'appName',
instanceId: 'instanceId',
};
const key = instanceService.clientKey(client);
instanceService.seenClients = {
[key]: { ...client, sdkVersion: 'my-sdk' },
};
await instanceService.registerInstance({
...client,
environment: 'blue',
}, '::1');
expect(instanceService.seenClients[key]).toMatchObject({
appName: 'appName',
instanceId: 'instanceId',
environment: 'blue',
sdkVersion: 'my-sdk',
});
});
test('registerBackendClient merges its data', async () => {
const instanceService = new ClientInstanceService({}, config, {});
const client = {
appName: 'appName',
instanceId: 'instanceId',
};
const key = instanceService.clientKey(client);
instanceService.seenClients = {
[key]: { ...client, environment: 'blue' },
};
await instanceService.registerBackendClient({
...client,
sdkVersion: 'my-sdk',
started: new Date(),
interval: 5,
}, '::1');
expect(instanceService.seenClients[key]).toMatchObject({
appName: 'appName',
instanceId: 'instanceId',
environment: 'blue',
sdkVersion: 'my-sdk',
});
});
test('registerFrontendClient merges its data', async () => {
const instanceService = new ClientInstanceService({}, config, {});
const client = {
appName: 'appName',
instanceId: 'instanceId',
};
const key = instanceService.clientKey(client);
instanceService.seenClients = {
[key]: { ...client, metricsCount: 10 },
};
instanceService.registerFrontendClient({
...client,
sdkVersion: 'my-sdk',
sdkType: 'frontend',
environment: 'black',
});
expect(instanceService.seenClients[key]).toMatchObject({
appName: 'appName',
instanceId: 'instanceId',
sdkVersion: 'my-sdk',
sdkType: 'frontend',
environment: 'black',
metricsCount: 10,
});
});
});
//# sourceMappingURL=instance-service.test.js.map