mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-07 01:16:28 +02:00
Vitest Pros: * Automated failing test comments on github PRs * A nice local UI with incremental testing when changing files (`yarn test:ui`) * Also nicely supported in all major IDEs, click to run test works (so we won't miss what we had with jest). * Works well with ESM Vitest Cons: * The ESBuild transformer vitest uses takes a little longer to transform than our current SWC/jest setup, however, it is possible to setup SWC as the transformer for vitest as well (though it only does one transform, so we're paying ~7-10 seconds instead of ~ 2-3 seconds in transform phase). * Exposes how slow our tests are (tongue in cheek here)
432 lines
13 KiB
TypeScript
432 lines
13 KiB
TypeScript
import EnvironmentService from './environment-service.js';
|
|
import { createTestConfig } from '../../../test/config/test-config.js';
|
|
import dbInit, {
|
|
type ITestDb,
|
|
} from '../../../test/e2e/helpers/database-init.js';
|
|
import NotFoundError from '../../error/notfound-error.js';
|
|
import {
|
|
type IUnleashStores,
|
|
SYSTEM_USER,
|
|
SYSTEM_USER_AUDIT,
|
|
} from '../../types/index.js';
|
|
import NameExistsError from '../../error/name-exists-error.js';
|
|
import type { EventService } from '../../services/index.js';
|
|
import { createEventsService } from '../events/createEventsService.js';
|
|
import { test, beforeAll, afterAll, expect } from 'vitest';
|
|
let stores: IUnleashStores;
|
|
let db: ITestDb;
|
|
let service: EnvironmentService;
|
|
let eventService: EventService;
|
|
|
|
beforeAll(async () => {
|
|
const config = createTestConfig();
|
|
db = await dbInit('environment_service_serial', config.getLogger, {
|
|
dbInitMethod: 'legacy' as const,
|
|
});
|
|
stores = db.stores;
|
|
eventService = createEventsService(db.rawDatabase, config);
|
|
service = new EnvironmentService(stores, config, eventService);
|
|
});
|
|
afterAll(async () => {
|
|
await db.destroy();
|
|
});
|
|
|
|
test('Can get environment', async () => {
|
|
const created = await db.stores.environmentStore.create({
|
|
name: 'testenv',
|
|
type: 'production',
|
|
});
|
|
|
|
const retrieved = await service.get('testenv');
|
|
expect(retrieved).toEqual(created);
|
|
});
|
|
|
|
test('Can get all', async () => {
|
|
await db.stores.environmentStore.create({
|
|
name: 'testenv2',
|
|
type: 'production',
|
|
});
|
|
|
|
const environments = await service.getAll();
|
|
expect(environments).toHaveLength(3); // the one we created plus 'default'
|
|
});
|
|
|
|
test('Can manage required approvals', async () => {
|
|
const created = await db.stores.environmentStore.create({
|
|
name: 'approval_env',
|
|
type: 'production',
|
|
requiredApprovals: 1,
|
|
});
|
|
|
|
const retrieved = await service.get('approval_env');
|
|
|
|
await db.stores.environmentStore.update(
|
|
{
|
|
type: 'production',
|
|
protected: false,
|
|
requiredApprovals: 2,
|
|
},
|
|
'approval_env',
|
|
);
|
|
|
|
const updated = await service.get('approval_env');
|
|
const groupRetrieved = (await service.getAll()).find(
|
|
(env) => env.name === 'approval_env',
|
|
);
|
|
const changeRequestEnvs =
|
|
await db.stores.environmentStore.getChangeRequestEnvironments([
|
|
'approval_env',
|
|
'default',
|
|
'other',
|
|
]);
|
|
|
|
expect(retrieved).toEqual(created);
|
|
expect(updated).toEqual({ ...created, requiredApprovals: 2 });
|
|
expect(groupRetrieved).toMatchObject({ ...created, requiredApprovals: 2 });
|
|
expect(changeRequestEnvs).toEqual([
|
|
{ name: 'approval_env', requiredApprovals: 2 },
|
|
]);
|
|
});
|
|
|
|
test('Can connect environment to project', async () => {
|
|
await db.stores.environmentStore.create({
|
|
name: 'test-connection',
|
|
type: 'production',
|
|
});
|
|
await stores.featureToggleStore.create('default', {
|
|
name: 'test-connection',
|
|
type: 'release',
|
|
description: '',
|
|
stale: false,
|
|
createdByUserId: 9999,
|
|
});
|
|
await service.addEnvironmentToProject(
|
|
'test-connection',
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
const overview = await stores.featureStrategiesStore.getFeatureOverview({
|
|
projectId: 'default',
|
|
});
|
|
overview.forEach((f) => {
|
|
expect(f.environments).toEqual([
|
|
{
|
|
name: 'test-connection',
|
|
enabled: false,
|
|
sortOrder: 9999,
|
|
type: 'production',
|
|
variantCount: 0,
|
|
lastSeenAt: null,
|
|
hasStrategies: false,
|
|
hasEnabledStrategies: false,
|
|
},
|
|
]);
|
|
});
|
|
const { events } = await eventService.getEvents();
|
|
expect(events[0]).toMatchObject({
|
|
type: 'project-environment-added',
|
|
project: 'default',
|
|
environment: 'test-connection',
|
|
createdBy: SYSTEM_USER.username,
|
|
createdByUserId: SYSTEM_USER.id,
|
|
});
|
|
});
|
|
|
|
test('Can remove environment from project', async () => {
|
|
await db.stores.environmentStore.create({
|
|
name: 'removal-test',
|
|
type: 'production',
|
|
});
|
|
await stores.featureToggleStore.create('default', {
|
|
name: 'removal-test',
|
|
createdByUserId: 9999,
|
|
});
|
|
await service.removeEnvironmentFromProject(
|
|
'test-connection',
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
await service.addEnvironmentToProject(
|
|
'removal-test',
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
let overview = await stores.featureStrategiesStore.getFeatureOverview({
|
|
projectId: 'default',
|
|
});
|
|
expect(overview.length).toBeGreaterThan(0);
|
|
overview.forEach((f) => {
|
|
expect(f.environments).toEqual([
|
|
{
|
|
name: 'removal-test',
|
|
enabled: false,
|
|
sortOrder: 9999,
|
|
type: 'production',
|
|
variantCount: 0,
|
|
lastSeenAt: null,
|
|
hasStrategies: false,
|
|
hasEnabledStrategies: false,
|
|
},
|
|
]);
|
|
});
|
|
await service.removeEnvironmentFromProject(
|
|
'removal-test',
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
overview = await stores.featureStrategiesStore.getFeatureOverview({
|
|
projectId: 'default',
|
|
});
|
|
expect(overview.length).toBeGreaterThan(0);
|
|
overview.forEach((o) => {
|
|
expect(o.environments).toEqual([]);
|
|
});
|
|
const { events } = await eventService.getEvents();
|
|
expect(events[0]).toMatchObject({
|
|
type: 'project-environment-removed',
|
|
project: 'default',
|
|
environment: 'removal-test',
|
|
createdBy: SYSTEM_USER.username,
|
|
createdByUserId: SYSTEM_USER.id,
|
|
});
|
|
});
|
|
|
|
test('Adding same environment twice should throw a NameExistsError', async () => {
|
|
await db.stores.environmentStore.create({
|
|
name: 'uniqueness-test',
|
|
type: 'production',
|
|
});
|
|
await service.addEnvironmentToProject(
|
|
'uniqueness-test',
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
|
|
await service.removeEnvironmentFromProject(
|
|
'test-connection',
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
await service.removeEnvironmentFromProject(
|
|
'removal-test',
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
|
|
return expect(async () =>
|
|
service.addEnvironmentToProject(
|
|
'uniqueness-test',
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
),
|
|
).rejects.errorWithMessage(
|
|
new NameExistsError(
|
|
'default already has the environment uniqueness-test enabled',
|
|
),
|
|
);
|
|
});
|
|
|
|
test('Removing environment not connected to project should be a noop', async () => {
|
|
await expect(
|
|
service.removeEnvironmentFromProject(
|
|
'some-non-existing-environment',
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
),
|
|
).resolves;
|
|
});
|
|
|
|
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.errorWithMessage(
|
|
new NotFoundError(`Could not find environment with name: ${envName}`),
|
|
);
|
|
});
|
|
|
|
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',
|
|
});
|
|
|
|
//Set these to the wrong state so we can assert that overriding them flips their state
|
|
await service.toggleEnvironment(disabledEnvName, true);
|
|
await service.toggleEnvironment(enabledEnvName, false);
|
|
|
|
await service.overrideEnabledProjects([enabledEnvName]);
|
|
|
|
const environments = await service.getAll();
|
|
const targetedEnvironment = environments.find(
|
|
(env) => env.name === enabledEnvName,
|
|
);
|
|
|
|
const allOtherEnvironments = environments
|
|
.filter((x) => x.name !== enabledEnvName)
|
|
.map((env) => env.enabled);
|
|
|
|
expect(targetedEnvironment?.enabled).toBe(true);
|
|
expect(allOtherEnvironments.every((x) => !x)).toBe(true);
|
|
});
|
|
|
|
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(
|
|
(env) => env.name === enabledEnvName,
|
|
);
|
|
|
|
expect(targetedEnvironment?.enabled).toBe(true);
|
|
});
|
|
|
|
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,
|
|
createdByUserId: 9999,
|
|
});
|
|
|
|
await service.addEnvironmentToProject(
|
|
disabledEnvName,
|
|
'default',
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
|
|
await service.overrideEnabledProjects([enabledEnvName]);
|
|
|
|
const projects = (
|
|
await stores.projectStore.getEnvironmentsForProject('default')
|
|
).map((e) => e.environment);
|
|
|
|
expect(projects).toContain('enabled');
|
|
expect(projects).not.toContain('default');
|
|
});
|
|
|
|
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(
|
|
(env) => env.name === defaultEnvironment,
|
|
);
|
|
|
|
const allOtherEnvironments = environments
|
|
.filter((x) => x.name !== defaultEnvironment)
|
|
.map((env) => env.enabled);
|
|
const envNames = environments.map((x) => x.name);
|
|
|
|
expect(envNames).toContain('production');
|
|
expect(envNames).toContain('development');
|
|
expect(targetedEnvironment?.enabled).toBe(true);
|
|
expect(allOtherEnvironments.every((x) => !x)).toBe(true);
|
|
});
|
|
|
|
test('getProjectEnvironments also includes whether or not a given project is visible on a given environment', async () => {
|
|
const assertContains = (environments, envName, visible) => {
|
|
const env = environments.find((e) => e.name === envName);
|
|
expect(env).toBeDefined();
|
|
expect(env.visible).toBe(visible);
|
|
};
|
|
|
|
const assertContainsVisible = (environments, envName) => {
|
|
assertContains(environments, envName, true);
|
|
};
|
|
|
|
const assertContainsNotVisible = (environments, envName) => {
|
|
assertContains(environments, envName, false);
|
|
};
|
|
|
|
const projectId = 'default';
|
|
const firstEnvTest = 'some-connected-environment';
|
|
const secondEnvTest = 'some-also-connected-environment';
|
|
await db.stores.environmentStore.create({
|
|
name: firstEnvTest,
|
|
type: 'production',
|
|
});
|
|
await db.stores.environmentStore.create({
|
|
name: secondEnvTest,
|
|
type: 'production',
|
|
});
|
|
|
|
await service.addEnvironmentToProject(
|
|
firstEnvTest,
|
|
projectId,
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
await service.addEnvironmentToProject(
|
|
secondEnvTest,
|
|
projectId,
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
let environments = await service.getProjectEnvironments(projectId);
|
|
assertContainsVisible(environments, firstEnvTest);
|
|
assertContainsVisible(environments, secondEnvTest);
|
|
|
|
await service.removeEnvironmentFromProject(
|
|
firstEnvTest,
|
|
projectId,
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
environments = await service.getProjectEnvironments(projectId);
|
|
assertContainsNotVisible(environments, firstEnvTest);
|
|
assertContainsVisible(environments, secondEnvTest);
|
|
});
|