2023-12-01 12:41:46 +01:00
|
|
|
import EnvironmentService from './environment-service';
|
|
|
|
import { createTestConfig } from '../../../test/config/test-config';
|
2024-01-12 10:25:59 +01:00
|
|
|
import dbInit, { ITestDb } from '../../../test/e2e/helpers/database-init';
|
2023-12-01 12:41:46 +01:00
|
|
|
import NotFoundError from '../../error/notfound-error';
|
2023-12-14 13:45:25 +01:00
|
|
|
import { IUnleashStores, SYSTEM_USER } from '../../types';
|
2023-12-01 12:41:46 +01:00
|
|
|
import NameExistsError from '../../error/name-exists-error';
|
|
|
|
import { EventService } from '../../services';
|
2021-07-07 10:46:50 +02:00
|
|
|
|
|
|
|
let stores: IUnleashStores;
|
2024-01-12 10:25:59 +01:00
|
|
|
let db: ITestDb;
|
2021-07-07 10:46:50 +02:00
|
|
|
let service: EnvironmentService;
|
2023-11-28 12:58:30 +01:00
|
|
|
let eventService: EventService;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
const config = createTestConfig();
|
|
|
|
db = await dbInit('environment_service_serial', config.getLogger);
|
|
|
|
stores = db.stores;
|
2023-11-28 12:58:30 +01:00
|
|
|
eventService = new EventService(stores, config);
|
|
|
|
service = new EnvironmentService(stores, config, eventService);
|
2021-07-07 10:46:50 +02:00
|
|
|
});
|
|
|
|
afterAll(async () => {
|
|
|
|
await db.destroy();
|
|
|
|
});
|
|
|
|
|
2021-09-13 15:57:38 +02:00
|
|
|
test('Can get environment', async () => {
|
|
|
|
const created = await db.stores.environmentStore.create({
|
2021-07-07 10:46:50 +02:00
|
|
|
name: 'testenv',
|
2021-09-13 15:57:38 +02:00
|
|
|
type: 'production',
|
2021-07-07 10:46:50 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
const retrieved = await service.get('testenv');
|
|
|
|
expect(retrieved).toEqual(created);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('Can get all', async () => {
|
2021-09-13 15:57:38 +02:00
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: 'testenv2',
|
|
|
|
type: 'production',
|
2021-07-07 10:46:50 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
const environments = await service.getAll();
|
2021-09-24 13:55:00 +02:00
|
|
|
expect(environments).toHaveLength(3); // the one we created plus 'default'
|
2021-07-07 10:46:50 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
test('Can connect environment to project', async () => {
|
2021-09-13 15:57:38 +02:00
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: 'test-connection',
|
|
|
|
type: 'production',
|
|
|
|
});
|
2021-09-13 10:23:57 +02:00
|
|
|
await stores.featureToggleStore.create('default', {
|
2021-07-07 10:46:50 +02:00
|
|
|
name: 'test-connection',
|
|
|
|
type: 'release',
|
|
|
|
description: '',
|
|
|
|
stale: false,
|
2023-12-22 14:33:16 +01:00
|
|
|
createdByUserId: 9999,
|
2021-07-07 10:46:50 +02:00
|
|
|
});
|
2023-12-14 13:45:25 +01:00
|
|
|
await service.addEnvironmentToProject(
|
|
|
|
'test-connection',
|
|
|
|
'default',
|
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
|
|
|
);
|
2022-11-29 16:06:08 +01:00
|
|
|
const overview = await stores.featureStrategiesStore.getFeatureOverview({
|
|
|
|
projectId: 'default',
|
|
|
|
});
|
2021-08-12 15:04:37 +02:00
|
|
|
overview.forEach((f) => {
|
2021-07-07 10:46:50 +02:00
|
|
|
expect(f.environments).toEqual([
|
|
|
|
{
|
|
|
|
name: 'test-connection',
|
|
|
|
enabled: false,
|
2021-09-23 21:14:43 +02:00
|
|
|
sortOrder: 9999,
|
|
|
|
type: 'production',
|
2023-01-20 18:09:01 +01:00
|
|
|
variantCount: 0,
|
2023-08-04 08:59:54 +02:00
|
|
|
lastSeenAt: null,
|
2023-11-14 13:03:23 +01:00
|
|
|
hasStrategies: false,
|
|
|
|
hasEnabledStrategies: false,
|
2021-07-07 10:46:50 +02:00
|
|
|
},
|
|
|
|
]);
|
|
|
|
});
|
2023-11-28 12:58:30 +01:00
|
|
|
const { events } = await eventService.getEvents();
|
|
|
|
expect(events[0]).toMatchObject({
|
|
|
|
type: 'project-environment-added',
|
|
|
|
project: 'default',
|
|
|
|
environment: 'test-connection',
|
2023-12-14 13:45:25 +01:00
|
|
|
createdBy: SYSTEM_USER.username,
|
|
|
|
createdByUserId: SYSTEM_USER.id,
|
2023-11-28 12:58:30 +01:00
|
|
|
});
|
2021-07-07 10:46:50 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
test('Can remove environment from project', async () => {
|
2021-09-13 15:57:38 +02:00
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: 'removal-test',
|
|
|
|
type: 'production',
|
|
|
|
});
|
2021-09-13 10:23:57 +02:00
|
|
|
await stores.featureToggleStore.create('default', {
|
2021-07-07 10:46:50 +02:00
|
|
|
name: 'removal-test',
|
2023-12-22 14:33:16 +01:00
|
|
|
createdByUserId: 9999,
|
2021-07-07 10:46:50 +02:00
|
|
|
});
|
2023-12-14 13:45:25 +01:00
|
|
|
await service.removeEnvironmentFromProject(
|
|
|
|
'test-connection',
|
|
|
|
'default',
|
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
|
|
|
);
|
|
|
|
await service.addEnvironmentToProject(
|
|
|
|
'removal-test',
|
|
|
|
'default',
|
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
|
|
|
);
|
2022-11-29 16:06:08 +01:00
|
|
|
let overview = await stores.featureStrategiesStore.getFeatureOverview({
|
|
|
|
projectId: 'default',
|
|
|
|
});
|
2021-07-07 10:46:50 +02:00
|
|
|
expect(overview.length).toBeGreaterThan(0);
|
2021-08-12 15:04:37 +02:00
|
|
|
overview.forEach((f) => {
|
2021-07-07 10:46:50 +02:00
|
|
|
expect(f.environments).toEqual([
|
|
|
|
{
|
|
|
|
name: 'removal-test',
|
|
|
|
enabled: false,
|
2021-09-23 21:14:43 +02:00
|
|
|
sortOrder: 9999,
|
|
|
|
type: 'production',
|
2023-01-20 18:09:01 +01:00
|
|
|
variantCount: 0,
|
2023-08-04 08:59:54 +02:00
|
|
|
lastSeenAt: null,
|
2023-11-14 13:03:23 +01:00
|
|
|
hasStrategies: false,
|
|
|
|
hasEnabledStrategies: false,
|
2021-07-07 10:46:50 +02:00
|
|
|
},
|
|
|
|
]);
|
|
|
|
});
|
2023-11-28 12:58:30 +01:00
|
|
|
await service.removeEnvironmentFromProject(
|
|
|
|
'removal-test',
|
|
|
|
'default',
|
2023-12-14 13:45:25 +01:00
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
2023-11-28 12:58:30 +01:00
|
|
|
);
|
2022-11-29 16:06:08 +01:00
|
|
|
overview = await stores.featureStrategiesStore.getFeatureOverview({
|
|
|
|
projectId: 'default',
|
|
|
|
});
|
2021-07-07 10:46:50 +02:00
|
|
|
expect(overview.length).toBeGreaterThan(0);
|
2021-08-12 15:04:37 +02:00
|
|
|
overview.forEach((o) => {
|
2021-07-07 10:46:50 +02:00
|
|
|
expect(o.environments).toEqual([]);
|
|
|
|
});
|
2023-11-28 12:58:30 +01:00
|
|
|
const { events } = await eventService.getEvents();
|
|
|
|
expect(events[0]).toMatchObject({
|
|
|
|
type: 'project-environment-removed',
|
|
|
|
project: 'default',
|
|
|
|
environment: 'removal-test',
|
2023-12-14 13:45:25 +01:00
|
|
|
createdBy: SYSTEM_USER.username,
|
|
|
|
createdByUserId: SYSTEM_USER.id,
|
2023-11-28 12:58:30 +01:00
|
|
|
});
|
2021-07-07 10:46:50 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
test('Adding same environment twice should throw a NameExistsError', async () => {
|
2021-09-13 15:57:38 +02:00
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: 'uniqueness-test',
|
|
|
|
type: 'production',
|
|
|
|
});
|
2023-12-14 13:45:25 +01:00
|
|
|
await service.addEnvironmentToProject(
|
|
|
|
'uniqueness-test',
|
|
|
|
'default',
|
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
|
|
|
);
|
2021-11-04 21:09:52 +01:00
|
|
|
|
2023-12-14 13:45:25 +01:00
|
|
|
await service.removeEnvironmentFromProject(
|
|
|
|
'test-connection',
|
|
|
|
'default',
|
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
|
|
|
);
|
|
|
|
await service.removeEnvironmentFromProject(
|
|
|
|
'removal-test',
|
|
|
|
'default',
|
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
|
|
|
);
|
2021-07-07 10:46:50 +02:00
|
|
|
|
|
|
|
return expect(async () =>
|
2023-12-14 13:45:25 +01:00
|
|
|
service.addEnvironmentToProject(
|
|
|
|
'uniqueness-test',
|
|
|
|
'default',
|
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
|
|
|
),
|
2021-07-07 10:46:50 +02:00
|
|
|
).rejects.toThrow(
|
|
|
|
new NameExistsError(
|
|
|
|
'default already has the environment uniqueness-test enabled',
|
|
|
|
),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('Removing environment not connected to project should be a noop', async () =>
|
|
|
|
expect(async () =>
|
|
|
|
service.removeEnvironmentFromProject(
|
|
|
|
'some-non-existing-environment',
|
|
|
|
'default',
|
2023-12-14 13:45:25 +01:00
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
2021-07-07 10:46:50 +02:00
|
|
|
),
|
|
|
|
).resolves);
|
2021-09-13 15:57:38 +02:00
|
|
|
|
|
|
|
test('Trying to get an environment that does not exist throws NotFoundError', async () => {
|
|
|
|
const envName = 'this-should-not-exist';
|
|
|
|
await expect(async () => service.get(envName)).rejects.toThrow(
|
|
|
|
new NotFoundError(`Could not find environment with name: ${envName}`),
|
|
|
|
);
|
|
|
|
});
|
2022-03-11 10:16:58 +01:00
|
|
|
|
|
|
|
test('Setting an override disables all other envs', async () => {
|
|
|
|
const enabledEnvName = 'should-get-enabled';
|
|
|
|
const disabledEnvName = 'should-get-disabled';
|
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: disabledEnvName,
|
|
|
|
type: 'production',
|
|
|
|
});
|
|
|
|
|
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: enabledEnvName,
|
|
|
|
type: 'production',
|
|
|
|
});
|
|
|
|
|
2022-03-11 14:52:13 +01:00
|
|
|
//Set these to the wrong state so we can assert that overriding them flips their state
|
2022-03-11 10:16:58 +01:00
|
|
|
await service.toggleEnvironment(disabledEnvName, true);
|
|
|
|
await service.toggleEnvironment(enabledEnvName, false);
|
|
|
|
|
|
|
|
await service.overrideEnabledProjects([enabledEnvName]);
|
|
|
|
|
|
|
|
const environments = await service.getAll();
|
|
|
|
const targetedEnvironment = environments.find(
|
2023-09-29 14:18:21 +02:00
|
|
|
(env) => env.name === enabledEnvName,
|
2022-03-11 10:16:58 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
const allOtherEnvironments = environments
|
2023-09-29 14:18:21 +02:00
|
|
|
.filter((x) => x.name !== enabledEnvName)
|
2022-03-11 10:16:58 +01:00
|
|
|
.map((env) => env.enabled);
|
|
|
|
|
2023-04-28 13:59:04 +02:00
|
|
|
expect(targetedEnvironment?.enabled).toBe(true);
|
|
|
|
expect(allOtherEnvironments.every((x) => !x)).toBe(true);
|
2022-03-11 10:16:58 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
test('Passing an empty override does nothing', async () => {
|
|
|
|
const enabledEnvName = 'should-be-enabled';
|
|
|
|
|
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: enabledEnvName,
|
|
|
|
type: 'production',
|
|
|
|
});
|
|
|
|
|
|
|
|
await service.toggleEnvironment(enabledEnvName, true);
|
|
|
|
|
|
|
|
await service.overrideEnabledProjects([]);
|
|
|
|
|
|
|
|
const environments = await service.getAll();
|
|
|
|
const targetedEnvironment = environments.find(
|
2023-09-29 14:18:21 +02:00
|
|
|
(env) => env.name === enabledEnvName,
|
2022-03-11 10:16:58 +01:00
|
|
|
);
|
|
|
|
|
2023-04-28 13:59:04 +02:00
|
|
|
expect(targetedEnvironment?.enabled).toBe(true);
|
2022-03-11 10:16:58 +01:00
|
|
|
});
|
2022-03-11 14:52:13 +01:00
|
|
|
|
|
|
|
test('When given overrides should remap projects to override environments', async () => {
|
|
|
|
const enabledEnvName = 'enabled';
|
|
|
|
const ignoredEnvName = 'ignored';
|
|
|
|
const disabledEnvName = 'disabled';
|
|
|
|
const toggleName = 'test-toggle';
|
|
|
|
|
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: enabledEnvName,
|
|
|
|
type: 'production',
|
|
|
|
});
|
|
|
|
|
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: ignoredEnvName,
|
|
|
|
type: 'production',
|
|
|
|
});
|
|
|
|
|
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: disabledEnvName,
|
|
|
|
type: 'production',
|
|
|
|
});
|
|
|
|
|
|
|
|
await service.toggleEnvironment(disabledEnvName, true);
|
|
|
|
await service.toggleEnvironment(ignoredEnvName, true);
|
|
|
|
await service.toggleEnvironment(enabledEnvName, false);
|
|
|
|
|
|
|
|
await stores.featureToggleStore.create('default', {
|
|
|
|
name: toggleName,
|
|
|
|
type: 'release',
|
|
|
|
description: '',
|
|
|
|
stale: false,
|
2023-12-22 14:33:16 +01:00
|
|
|
createdByUserId: 9999,
|
2022-03-11 14:52:13 +01:00
|
|
|
});
|
|
|
|
|
2023-12-14 13:45:25 +01:00
|
|
|
await service.addEnvironmentToProject(
|
|
|
|
disabledEnvName,
|
|
|
|
'default',
|
|
|
|
SYSTEM_USER.username,
|
|
|
|
SYSTEM_USER.id,
|
|
|
|
);
|
2022-03-11 14:52:13 +01:00
|
|
|
|
|
|
|
await service.overrideEnabledProjects([enabledEnvName]);
|
|
|
|
|
2023-04-28 13:59:04 +02:00
|
|
|
const projects = (
|
|
|
|
await stores.projectStore.getEnvironmentsForProject('default')
|
|
|
|
).map((e) => e.environment);
|
2022-03-11 14:52:13 +01:00
|
|
|
|
|
|
|
expect(projects).toContain('enabled');
|
|
|
|
expect(projects).not.toContain('default');
|
|
|
|
});
|
2022-03-16 13:29:11 +01:00
|
|
|
|
|
|
|
test('Override works correctly when enabling default and disabling prod and dev', async () => {
|
|
|
|
const defaultEnvironment = 'default';
|
|
|
|
const prodEnvironment = 'production';
|
|
|
|
const devEnvironment = 'development';
|
|
|
|
|
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: prodEnvironment,
|
|
|
|
type: 'production',
|
|
|
|
});
|
|
|
|
|
|
|
|
await db.stores.environmentStore.create({
|
|
|
|
name: devEnvironment,
|
|
|
|
type: 'development',
|
|
|
|
});
|
|
|
|
await service.toggleEnvironment(prodEnvironment, true);
|
|
|
|
await service.toggleEnvironment(devEnvironment, true);
|
|
|
|
|
|
|
|
await service.overrideEnabledProjects([defaultEnvironment]);
|
|
|
|
|
|
|
|
const environments = await service.getAll();
|
|
|
|
const targetedEnvironment = environments.find(
|
2023-09-29 14:18:21 +02:00
|
|
|
(env) => env.name === defaultEnvironment,
|
2022-03-16 13:29:11 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
const allOtherEnvironments = environments
|
2023-09-29 14:18:21 +02:00
|
|
|
.filter((x) => x.name !== defaultEnvironment)
|
2022-03-16 13:29:11 +01:00
|
|
|
.map((env) => env.enabled);
|
|
|
|
const envNames = environments.map((x) => x.name);
|
|
|
|
|
|
|
|
expect(envNames).toContain('production');
|
|
|
|
expect(envNames).toContain('development');
|
2023-04-28 13:59:04 +02:00
|
|
|
expect(targetedEnvironment?.enabled).toBe(true);
|
|
|
|
expect(allOtherEnvironments.every((x) => !x)).toBe(true);
|
2022-03-16 13:29:11 +01:00
|
|
|
});
|