mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-12 01:17:04 +02:00
This PR is the first step in separating the client and admin stores. Currently our feature toggle services uses the client store to serve multiple purposes. Admin API uses the feature toggle service to serve both the feature toggle list and playground features, while the client API uses the feature toggle service to serve client features. The admin API can change often and have very different requirements than the client API, which changes infrequently and generally keeps the same stable structure for long periods of time. This architecture is error prone, because when you need to make changes to the admin API, you can very easily affect the client API. I aim to put up a stone wall between the two APIs. Complete separation between the two APIs, at the cost of some duplication. In this PR I have created a feature oriented architecture for client features and disconnected the client API from the feature toggle service. It now goes through it's own service to it's own store. For feature toggle service I have duplicated and replaced the functionality that serves /api/admin/features, I have kept a lot of the ugliness in the code and haven't removed anything in order to avoid breaking changes. Next steps: * Move playground to admin API * Remove client-feature-toggle-store from feature-toggle-service
161 lines
5.3 KiB
TypeScript
161 lines
5.3 KiB
TypeScript
import supertest from 'supertest';
|
|
import createStores from '../../../../test/fixtures/store';
|
|
import getLogger from '../../../../test/fixtures/no-logger';
|
|
import getApp from '../../../app';
|
|
import { createServices } from '../../../services';
|
|
import FeatureController from '../client-feature-toggle.controller';
|
|
import { createTestConfig } from '../../../../test/config/test-config';
|
|
import { secondsToMilliseconds } from 'date-fns';
|
|
import { ClientSpecService } from '../../../services/client-spec-service';
|
|
|
|
let app;
|
|
|
|
async function getSetup() {
|
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
|
const stores = createStores();
|
|
const config = createTestConfig({
|
|
server: { baseUriPath: base },
|
|
});
|
|
const services = createServices(stores, config);
|
|
|
|
app = await getApp(config, stores, services);
|
|
|
|
return {
|
|
base,
|
|
clientFeatureToggleStore: stores.clientFeatureToggleStore,
|
|
request: supertest(app),
|
|
destroy: () => {
|
|
services.versionService.destroy();
|
|
services.clientInstanceService.destroy();
|
|
},
|
|
};
|
|
}
|
|
|
|
const callGetAll = async (controller: FeatureController) => {
|
|
await controller.getAll(
|
|
// @ts-expect-error
|
|
{ query: {}, header: () => undefined, headers: {} },
|
|
{
|
|
json: () => {},
|
|
setHeader: () => undefined,
|
|
},
|
|
);
|
|
};
|
|
|
|
let base;
|
|
let request;
|
|
let destroy;
|
|
|
|
let flagResolver;
|
|
|
|
beforeEach(async () => {
|
|
const setup = await getSetup();
|
|
base = setup.base;
|
|
request = setup.request;
|
|
destroy = setup.destroy;
|
|
flagResolver = {
|
|
isEnabled: () => {
|
|
return false;
|
|
},
|
|
};
|
|
});
|
|
|
|
afterEach(() => {
|
|
destroy();
|
|
});
|
|
|
|
test('should get empty getFeatures via client', () => {
|
|
expect.assertions(1);
|
|
return request
|
|
.get(`${base}/api/client/features`)
|
|
.expect('Content-Type', /json/)
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.body.features.length === 0).toBe(true);
|
|
});
|
|
});
|
|
|
|
test('if caching is enabled should memoize', async () => {
|
|
const getClientFeatures = jest.fn().mockReturnValue([]);
|
|
const getActive = jest.fn().mockReturnValue([]);
|
|
const getActiveForClient = jest.fn().mockReturnValue([]);
|
|
const respondWithValidation = jest.fn().mockReturnValue({});
|
|
const validPath = jest.fn().mockReturnValue(jest.fn());
|
|
const clientSpecService = new ClientSpecService({ getLogger });
|
|
const openApiService = { respondWithValidation, validPath };
|
|
const clientFeatureToggleService = { getClientFeatures };
|
|
const featureToggleService = { getClientFeatures };
|
|
const segmentService = { getActive, getActiveForClient };
|
|
const configurationRevisionService = { getMaxRevisionId: () => 1 };
|
|
|
|
const controller = new FeatureController(
|
|
{
|
|
clientSpecService,
|
|
// @ts-expect-error due to partial implementation
|
|
openApiService,
|
|
// @ts-expect-error due to partial implementation
|
|
clientFeatureToggleService,
|
|
// @ts-expect-error due to partial implementation
|
|
featureToggleService,
|
|
// @ts-expect-error due to partial implementation
|
|
segmentService,
|
|
// @ts-expect-error due to partial implementation
|
|
configurationRevisionService,
|
|
},
|
|
{
|
|
getLogger,
|
|
clientFeatureCaching: {
|
|
enabled: true,
|
|
maxAge: secondsToMilliseconds(10),
|
|
},
|
|
flagResolver,
|
|
},
|
|
);
|
|
|
|
await callGetAll(controller);
|
|
await callGetAll(controller);
|
|
expect(getClientFeatures).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('if caching is not enabled all calls goes to service', async () => {
|
|
const getClientFeatures = jest.fn().mockReturnValue([]);
|
|
const getActive = jest.fn().mockReturnValue([]);
|
|
const getActiveForClient = jest.fn().mockReturnValue([]);
|
|
const respondWithValidation = jest.fn().mockReturnValue({});
|
|
const validPath = jest.fn().mockReturnValue(jest.fn());
|
|
const clientSpecService = new ClientSpecService({ getLogger });
|
|
const clientFeatureToggleService = { getClientFeatures };
|
|
const segmentService = { getActive, getActiveForClient };
|
|
const featureToggleService = { getClientFeatures };
|
|
const openApiService = { respondWithValidation, validPath };
|
|
const configurationRevisionService = { getMaxRevisionId: () => 1 };
|
|
|
|
const controller = new FeatureController(
|
|
{
|
|
clientSpecService,
|
|
// @ts-expect-error due to partial implementation
|
|
openApiService,
|
|
// @ts-expect-error due to partial implementation
|
|
clientFeatureToggleService,
|
|
// @ts-expect-error due to partial implementation
|
|
featureToggleService,
|
|
// @ts-expect-error due to partial implementation
|
|
segmentService,
|
|
// @ts-expect-error due to partial implementation
|
|
configurationRevisionService,
|
|
},
|
|
{
|
|
getLogger,
|
|
clientFeatureCaching: {
|
|
enabled: false,
|
|
maxAge: secondsToMilliseconds(10),
|
|
},
|
|
flagResolver,
|
|
},
|
|
);
|
|
|
|
await callGetAll(controller);
|
|
await callGetAll(controller);
|
|
expect(getClientFeatures).toHaveBeenCalledTimes(2);
|
|
});
|