UNPKG

unleash-server

Version:

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

287 lines • 11.8 kB
import supertest from 'supertest'; import getApp from '../../../lib/app.js'; import { createTestConfig } from '../../config/test-config.js'; import { IAuthType } from '../../../lib/types/option.js'; import { createServices } from '../../../lib/services/index.js'; import sessionDb from '../../../lib/middleware/session-db.js'; import { DEFAULT_PROJECT, } from '../../../lib/types/index.js'; import { DEFAULT_ENV } from '../../../lib/util/index.js'; import { initialServiceSetup, } from '../../../lib/server-impl.js'; process.env.NODE_ENV = 'test'; function httpApis(request, config) { const base = config.server.baseUriPath || ''; return { addStrategyToFeatureEnv: (postData, envName, featureName, project = DEFAULT_PROJECT, expectStatusCode = 200) => { const url = `${base}/api/admin/projects/${project}/features/${featureName}/environments/${envName}/strategies`; return request.post(url).send(postData).expect(expectStatusCode); }, createFeature: (feature, project = DEFAULT_PROJECT, expectedResponseCode = 201) => { let body = feature; if (typeof feature === 'string') { body = { name: feature, }; } return request .post(`${base}/api/admin/projects/${project}/features`) .send(body) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, getFeatures(name, expectedResponseCode = 200) { const featuresUrl = `/api/admin/features${name ? `/${name}` : ''}`; return request .get(featuresUrl) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, getProjectFeatures(project = DEFAULT_PROJECT, name, expectedResponseCode = 200) { const featuresUrl = `/api/admin/projects/${project}/features${name ? `/${name}` : ''}`; return request .get(featuresUrl) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, archiveFeature(name, project = DEFAULT_PROJECT, expectedResponseCode = 202) { return request .delete(`${base}/api/admin/projects/${project}/features/${name}`) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, createContextField(contextField, expectedResponseCode = 201) { return request .post(`${base}/api/admin/context`) .send(contextField) .expect(expectedResponseCode); }, linkProjectToEnvironment(project, environment = DEFAULT_ENV, expectedResponseCode = 200) { return request .post(`${base}/api/admin/projects/${project}/environments`) .send({ environment, }) .expect(expectedResponseCode); }, importToggles(importPayload, expectedResponseCode = 200) { return request .post('/api/admin/features-batch/import') .send(importPayload) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, addDependency(child, parent, project = DEFAULT_PROJECT, expectedResponseCode = 200) { return request .post(`/api/admin/projects/${project}/features/${child}/dependencies`) .send(typeof parent === 'string' ? { feature: parent } : parent) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, addTag(feature, tag, expectedResponseCode = 201) { return request .post(`/api/admin/features/${feature}/tags`) .send({ type: tag.type, value: tag.value }) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, enableFeature(feature, environment, project = 'default', expectedResponseCode = 200) { return request .post(`/api/admin/projects/${project}/features/${feature}/environments/${environment}/on`) .expect(expectedResponseCode); }, favoriteFeature(feature, project = 'default', expectedResponseCode = 200) { return request .post(`/api/admin/projects/${project}/features/${feature}/favorites`) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, createSegment(postData, expectedResponseCode = 201) { return request .post(`/api/admin/segments`) .send(postData) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, deleteSegment(segmentId, expectedResponseCode = 204) { return request .delete(`/api/admin/segments/${segmentId}`) .set('Content-Type', 'application/json') .expect(expectedResponseCode); }, updateSegment(segmentId, postData, expectStatusCode = 204) { return request .put(`/api/admin/segments/${segmentId}`) .send(postData) .expect(expectStatusCode); }, getRecordedEvents(queryParams = {}, expectedResponseCode = 200) { const query = new URLSearchParams(queryParams).toString(); return request .get(`/api/admin/search/events${query ? `?${query}` : ''}`) .expect(expectedResponseCode); }, login(args) { if ('email' in args) { const { email } = args; return request .post(`${base}/auth/demo/login`) .send({ email }) .expect(200); } const { username, password } = args; return request .post(`${base}/auth/simple/login`) .send({ username, password, }) .expect(200); }, }; } async function createApp(stores, adminAuthentication = IAuthType.NONE, preHook, customOptions, db) { const config = createTestConfig({ authentication: { type: adminAuthentication, customAuthHandler: preHook, }, server: { unleashUrl: 'http://localhost:4242', }, disableScheduler: true, ...{ ...customOptions, experimental: { ...(customOptions?.experimental ?? {}), flags: { strictSchemaValidation: true, ...(customOptions?.experimental?.flags ?? {}), }, }, }, }); const services = createServices(stores, config, db); await initialServiceSetup(config, services); // @ts-expect-error We don't have a database for sessions here. const unleashSession = sessionDb(config, undefined); const app = await getApp(config, stores, services, unleashSession, db); const request = supertest.agent(app); const destroy = async () => { // iterate on the keys of services and if the services at that key has a function called destroy then call it await Promise.all(Object.keys(services).map(async (key) => { if (services[key].destroy) { await services[key].destroy(); } })); }; // TODO: use create from server-impl instead? return { request, destroy, services, config, ...httpApis(request, config), }; } export async function setupApp(stores) { return createApp(stores); } export async function setupAppWithoutSupertest(stores, customOptions, db) { const config = createTestConfig({ authentication: { type: IAuthType.DEMO, }, server: { unleashUrl: 'http://localhost:4242', }, disableScheduler: true, ...{ ...customOptions, experimental: { ...(customOptions?.experimental ?? {}), flags: { strictSchemaValidation: true, ...(customOptions?.experimental?.flags ?? {}), }, }, }, }); const services = createServices(stores, config, db); await initialServiceSetup(config, services); // @ts-expect-error we don't have a db for the session here const unleashSession = sessionDb(config, undefined); const app = await getApp(config, stores, services, unleashSession, db); const server = app.listen(0); const destroy = async () => { // iterate on the keys of services and if the services at that key has a function called destroy then call it await Promise.all(Object.keys(services).map(async (key) => { if (services[key].destroy) { await services[key].destroy(); } })); await server.close(); }; return { server, destroy, services, config, }; } export async function setupAppWithCustomConfig(stores, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types customOptions, db) { return createApp(stores, undefined, undefined, customOptions, db); } export async function setupAppWithAuth(stores, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types customOptions, db) { return createApp(stores, IAuthType.DEMO, undefined, customOptions, db); } export async function setupAppWithCustomAuth(stores, preHook, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types customOptions, db) { return createApp(stores, IAuthType.CUSTOM, preHook, customOptions, db); } export async function setupAppWithBaseUrl(stores, baseUriPath = '/hosted') { return createApp(stores, undefined, undefined, { server: { unleashUrl: 'http://localhost:4242', baseUriPath, }, }); } export const insertLastSeenAt = async (featureName, db, environment = DEFAULT_ENV, date = '2023-10-01T12:34:56.000Z') => { try { await db.raw(`INSERT INTO last_seen_at_metrics (feature_name, environment, last_seen_at) VALUES ('${featureName}', '${environment}', '${date}');`); return date; } catch (err) { console.log(err); return Promise.resolve(''); } }; export const insertFeatureEnvironmentsLastSeen = async (featureName, db, environment = 'default', date = '2022-05-01T12:34:56.000Z') => { await db.raw(` INSERT INTO feature_environments (feature_name, environment, last_seen_at, enabled) VALUES ('${featureName}', '${environment}', '${date}', true) ON CONFLICT (feature_name, environment) DO UPDATE SET last_seen_at = '${date}', enabled = true; `); return date; }; export const createUserWithRootRole = async ({ app, stores, email, name = email, roleName, }) => { const createdUser = await stores.userStore.insert({ name, email, }); if (roleName) { const roles = await app.services.accessService.getRootRoles(); const role = roles.find((role) => role.name === roleName); if (!role) { throw new Error(`Role ${roleName} not found`); } await app.services.accessService.addUserToRole(createdUser.id, role.id, 'default'); } return createdUser; }; //# sourceMappingURL=test-helper.js.map