unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
287 lines • 11.8 kB
JavaScript
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