1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-12 13:48:35 +02:00

chore!: remove deprecated default env from new installs (#10080)

**BREAKING CHANGE**: DEFAULT_ENV changed from `default` (should not be
used anymore) to `development`

## About the changes
- Only delete default env if the install is fresh new.
- Consider development the new default. The main consequence of this
change is that the default is no longer considered `type=production`
environment but also for frontend tokens due to this assumption:
724c4b78a2/src/lib/schema/api-token-schema.test.ts (L54-L59)
(I believe this is mostly due to the [support for admin
tokens](https://github.com/Unleash/unleash/pull/10080#discussion_r2126871567))
- `feature_toggle_update_total` metric reports `n/a` in environment and
environment type as it's not environment specific
This commit is contained in:
Gastón Fournier 2025-06-06 12:02:21 +02:00 committed by GitHub
parent d6f76a098e
commit bdb763c9d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 186 additions and 380 deletions

View File

@ -63,12 +63,12 @@ exports[`should match snapshot from /api/client/features 1`] = `
},
],
"meta": {
"etag": ""61824cd0:19"",
"queryHash": "61824cd0",
"etag": ""76d8bb0e:19"",
"queryHash": "76d8bb0e",
"revisionId": 19,
},
"query": {
"environment": "default",
"environment": "development",
"inlineSegmentConstraints": true,
},
"version": 2,

View File

@ -21,9 +21,7 @@ let db: ITestDb;
let eventStore: IEventStore;
beforeAll(async () => {
db = await dbInit('dependent_features', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('dependent_features', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{

View File

@ -53,9 +53,9 @@ test('querying environments in OSS only returns environments that are included i
.get('/api/admin/environments')
.expect(200)
.expect((res) => {
expect(res.body.environments).toHaveLength(3);
expect(res.body.environments).toHaveLength(2);
const names = res.body.environments.map((env) => env.name);
expect(names).toEqual(['default', 'development', 'production']);
expect(names).toEqual(['development', 'production']);
});
});
@ -64,8 +64,8 @@ test('querying project environments in OSS only returns environments that are in
.get('/api/admin/environments/project/default')
.expect(200)
.expect((res) => {
expect(res.body.environments).toHaveLength(3);
expect(res.body.environments).toHaveLength(2);
const names = res.body.environments.map((env) => env.name);
expect(names).toEqual(['default', 'development', 'production']);
expect(names).toEqual(['development', 'production']);
});
});

View File

@ -12,9 +12,7 @@ let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('environment_api_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('environment_api_serial', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{
@ -34,24 +32,33 @@ afterAll(async () => {
});
test('Can list all existing environments', async () => {
await app.request
const { body } = await app.request
.get('/api/admin/environments')
.expect(200)
.expect('Content-Type', /json/)
.expect((res) => {
expect(res.body.version).toBe(1);
expect(res.body.environments[0]).toStrictEqual({
name: DEFAULT_ENV,
enabled: true,
sortOrder: 1,
type: 'production',
protected: true,
requiredApprovals: null,
projectCount: 1,
apiTokenCount: 0,
enabledToggleCount: 0,
});
});
.expect('Content-Type', /json/);
expect(body.version).toBe(1);
expect(body.environments[0]).toStrictEqual({
name: DEFAULT_ENV,
enabled: true,
sortOrder: 2,
type: 'development',
protected: false,
requiredApprovals: null,
projectCount: 1,
apiTokenCount: 0,
enabledToggleCount: 0,
});
expect(body.environments[1]).toStrictEqual({
name: 'production',
enabled: true,
sortOrder: 3,
type: 'production',
protected: false,
requiredApprovals: null,
projectCount: 1,
apiTokenCount: 0,
enabledToggleCount: 0,
});
});
test('Can update sort order', async () => {

View File

@ -23,7 +23,6 @@ import { FeatureLifecycleReadModel } from './feature-lifecycle-read-model.js';
import type { IFeatureLifecycleReadModel } from './feature-lifecycle-read-model-type.js';
import { STAGE_ENTERED } from '../../metric-events.js';
import type ClientInstanceService from '../metrics/instance/instance-service.js';
import { DEFAULT_ENV } from '../../server-impl.js';
let app: IUnleashTest;
let db: ITestDb;
@ -34,9 +33,7 @@ let featureLifecycleReadModel: IFeatureLifecycleReadModel;
let clientInstanceService: ClientInstanceService;
beforeAll(async () => {
db = await dbInit('feature_lifecycle', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('feature_lifecycle', getLogger);
app = await setupAppWithAuth(
db.stores,
{
@ -127,26 +124,27 @@ const getFeaturesLifecycleCount = async () => {
};
test('should return lifecycle stages', async () => {
const environment = 'production'; // prod environment moves lifecycle to live stage
await app.createFeature('my_feature_a');
await app.enableFeature('my_feature_a', DEFAULT_ENV);
await app.enableFeature('my_feature_a', environment);
eventStore.emit(FEATURE_CREATED, { featureName: 'my_feature_a' });
await reachedStage('my_feature_a', 'initial');
await expectFeatureStage('my_feature_a', 'initial');
eventBus.emit(CLIENT_METRICS_ADDED, [
{
featureName: 'my_feature_a',
environment: DEFAULT_ENV,
environment: environment,
},
{
featureName: 'non_existent_feature',
environment: DEFAULT_ENV,
environment: environment,
},
]);
// missing feature
eventBus.emit(CLIENT_METRICS_ADDED, [
{
environment: DEFAULT_ENV,
environment: environment,
yes: 0,
no: 0,
},
@ -241,13 +239,14 @@ test('should backfill archived feature', async () => {
});
test('should not backfill for existing lifecycle', async () => {
const environment = 'production'; // prod environment moves lifecycle to live stage
await app.createFeature('my_feature_e');
await app.enableFeature('my_feature_e', DEFAULT_ENV);
await app.enableFeature('my_feature_e', environment);
eventStore.emit(FEATURE_CREATED, { featureName: 'my_feature_e' });
eventBus.emit(CLIENT_METRICS_ADDED, [
{
featureName: 'my_feature_e',
environment: DEFAULT_ENV,
environment: environment,
},
]);
await reachedStage('my_feature_e', 'live');

View File

@ -18,9 +18,7 @@ let eventStore: IEventStore;
let featureLinkReadModel: IFeatureLinksReadModel;
beforeAll(async () => {
db = await dbInit('feature_link', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('feature_link', getLogger);
app = await setupAppWithAuth(
db.stores,
{

View File

@ -9,11 +9,9 @@ import {
import getLogger from '../../../test/fixtures/no-logger.js';
import type { FeatureSearchQueryParameters } from '../../openapi/spec/feature-search-query-parameters.js';
import {
CREATE_FEATURE_STRATEGY,
DEFAULT_PROJECT,
type IUnleashStores,
TEST_AUDIT_USER,
UPDATE_FEATURE_ENVIRONMENT,
} from '../../types/index.js';
import { DEFAULT_ENV } from '../../util/index.js';
@ -22,9 +20,7 @@ let db: ITestDb;
let stores: IUnleashStores;
beforeAll(async () => {
db = await dbInit('feature_search', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('feature_search', getLogger);
app = await setupAppWithAuth(
db.stores,
{
@ -46,38 +42,6 @@ beforeAll(async () => {
})
.expect(200);
await stores.environmentStore.create({
name: 'development',
type: 'development',
});
await app.linkProjectToEnvironment('default', 'development');
await stores.accessStore.addPermissionsToRole(
body.rootRole,
[
{ name: UPDATE_FEATURE_ENVIRONMENT },
{ name: CREATE_FEATURE_STRATEGY },
],
'development',
);
await stores.environmentStore.create({
name: 'production',
type: 'production',
});
await app.linkProjectToEnvironment('default', 'production');
await stores.accessStore.addPermissionsToRole(
body.rootRole,
[
{ name: UPDATE_FEATURE_ENVIRONMENT },
{ name: CREATE_FEATURE_STRATEGY },
],
'production',
);
await app.services.userService.createUser(
{
username: 'admin@test.com',
@ -865,11 +829,6 @@ test('should return segments in payload with no duplicates/nulls', async () => {
name: 'my_feature_a',
segments: [mySegment.name],
environments: [
{
name: 'default',
hasStrategies: true,
hasEnabledStrategies: true,
},
{
name: 'development',
hasStrategies: true,
@ -1183,11 +1142,6 @@ test('should return environment usage metrics and lifecycle', async () => {
name: 'my_feature_b',
lifecycle: { stage: 'completed', status: 'discarded' },
environments: [
{
name: 'default',
yes: 0,
no: 0,
},
{
name: 'development',
yes: 10,
@ -1448,7 +1402,6 @@ test('should return change request ids per environment', async () => {
{
name: 'my_feature_a',
environments: [
{ name: 'default', changeRequestIds: [] },
{ name: 'development', changeRequestIds: [5, 6, 7] },
{ name: 'production', changeRequestIds: [1] },
],
@ -1456,7 +1409,6 @@ test('should return change request ids per environment', async () => {
{
name: 'my_feature_b',
environments: [
{ name: 'default', changeRequestIds: [] },
{ name: 'development', changeRequestIds: [8] },
{ name: 'production', changeRequestIds: [] },
],
@ -1557,7 +1509,6 @@ test('should return release plan milestones', async () => {
{
name: 'my_feature_a',
environments: [
{ name: 'default' },
{
name: 'development',
totalMilestones: 3,
@ -1569,6 +1520,5 @@ test('should return release plan milestones', async () => {
},
],
});
expect(body.features[0].environments[0].milestoneName).toBeUndefined();
expect(body.features[0].environments[2].milestoneName).toBeUndefined();
expect(body.features[0].environments[1].milestoneName).toBeUndefined();
});

View File

@ -11,9 +11,7 @@ let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('archive_test_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('archive_test_serial', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{
@ -125,26 +123,6 @@ test('Should disable all environments when reviving a toggle', async () => {
archived: true,
createdByUserId: 9999,
});
await db.stores.environmentStore.create({
name: 'development',
enabled: true,
type: 'development',
sortOrder: 1,
});
await db.stores.environmentStore.create({
name: 'production',
enabled: true,
type: 'production',
sortOrder: 2,
});
await db.stores.featureEnvironmentStore.addEnvironmentToFeature(
'feat-proj-1',
'default',
true,
);
await db.stores.featureEnvironmentStore.addEnvironmentToFeature(
'feat-proj-1',
'production',

View File

@ -64,9 +64,6 @@ beforeAll(async () => {
db = await dbInit(
'feature_toggle_service_v2_service_serial',
config.getLogger,
{
dbInitMethod: 'legacy' as const,
},
);
unleashConfig = config;
stores = db.stores;

View File

@ -17,9 +17,7 @@ let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('feature_strategy_auth_api_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('feature_strategy_auth_api_serial', getLogger);
app = await setupAppWithAuth(
db.stores,
{

View File

@ -87,9 +87,7 @@ const updateStrategy = async (
};
beforeAll(async () => {
db = await dbInit('feature_strategy_api_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('feature_strategy_api_serial', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{

View File

@ -27,9 +27,7 @@ let app: IUnleashTest;
let db: ITestDb;
let frontendApiService: FrontendApiService;
beforeAll(async () => {
db = await dbInit('frontend_api', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('frontend_api', getLogger);
app = await setupAppWithAuth(
db.stores,
{

View File

@ -417,7 +417,7 @@ describe('bulk metrics', () => {
});
});
test('filters out metrics for environments we do not have access for. No auth setup so we can only access default env', async () => {
test('without access to production environment due to no auth setup, we can only access the default env', async () => {
const now = new Date();
await request
@ -437,7 +437,7 @@ describe('bulk metrics', () => {
{
featureName: 'test_feature_two',
appName: 'test_application',
environment: 'development',
environment: 'production',
timestamp: startOfHour(now),
yes: 1000,
no: 800,

View File

@ -13,9 +13,7 @@ let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('advanced_playground', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('advanced_playground', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{

View File

@ -5,6 +5,7 @@ import dbInit, {
} from '../../../test/e2e/helpers/database-init.js';
import NotFoundError from '../../error/notfound-error.js';
import {
type IEnvironment,
type IUnleashStores,
SYSTEM_USER,
SYSTEM_USER_AUDIT,
@ -13,54 +14,48 @@ 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';
import { DEFAULT_ENV } from '../../server-impl.js';
let stores: IUnleashStores;
let db: ITestDb;
let service: EnvironmentService;
let eventService: EventService;
let createdEnvironment: IEnvironment;
let withApprovals: IEnvironment;
beforeAll(async () => {
const config = createTestConfig();
db = await dbInit('environment_service_serial', config.getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('environment_service_serial', config.getLogger);
stores = db.stores;
eventService = createEventsService(db.rawDatabase, config);
service = new EnvironmentService(stores, config, eventService);
createdEnvironment = await db.stores.environmentStore.create({
name: 'testenv',
type: 'production',
});
withApprovals = await db.stores.environmentStore.create({
name: 'approval_env',
type: 'production',
requiredApprovals: 1,
});
});
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);
const retrieved = await service.get(createdEnvironment.name);
expect(retrieved).toEqual(createdEnvironment);
});
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'
expect(environments).toHaveLength(4); // the 2 created plus 'development' and 'production''
});
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');
const retrieved = await service.get(withApprovals.name);
await db.stores.environmentStore.update(
{
type: 'production',
@ -77,13 +72,16 @@ test('Can manage required approvals', async () => {
const changeRequestEnvs =
await db.stores.environmentStore.getChangeRequestEnvironments([
'approval_env',
DEFAULT_ENV,
'development',
'other',
]);
expect(retrieved).toEqual(created);
expect(updated).toEqual({ ...created, requiredApprovals: 2 });
expect(groupRetrieved).toMatchObject({ ...created, requiredApprovals: 2 });
expect(retrieved).toEqual(withApprovals);
expect(updated).toEqual({ ...withApprovals, requiredApprovals: 2 });
expect(groupRetrieved).toMatchObject({
...withApprovals,
requiredApprovals: 2,
});
expect(changeRequestEnvs).toEqual([
{ name: 'approval_env', requiredApprovals: 2 },
]);
@ -345,32 +343,22 @@ test('When given overrides should remap projects to override environments', asyn
expect(projects).not.toContain('default');
});
test('Override works correctly when enabling default and disabling prod and dev', async () => {
const defaultEnvironment = DEFAULT_ENV;
const prodEnvironment = 'production';
const devEnvironment = 'development';
test('Override works correctly when enabling a custom environment and disabling prod and dev', async () => {
const newEnvironment = 'custom';
await db.stores.environmentStore.create({
name: prodEnvironment,
name: newEnvironment,
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]);
await service.toggleEnvironment(newEnvironment, true);
await service.overrideEnabledProjects([newEnvironment]);
const environments = await service.getAll();
const targetedEnvironment = environments.find(
(env) => env.name === defaultEnvironment,
(env) => env.name === newEnvironment,
);
const allOtherEnvironments = environments
.filter((x) => x.name !== defaultEnvironment)
.filter((x) => x.name !== newEnvironment)
.map((env) => env.enabled);
const envNames = environments.map((x) => x.name);

View File

@ -12,9 +12,7 @@ let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('project_environments_api_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('project_environments_api_serial', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{
@ -65,7 +63,7 @@ test('Should add environment to project', async () => {
const environment = envs.find((env) => env.environment === 'test');
expect(environment).toBeDefined();
expect(envs).toHaveLength(2); // test + default
expect(envs).toHaveLength(3); // test + development + production
});
test('Should validate environment', async () => {

View File

@ -16,9 +16,7 @@ let projectStore: IProjectStore;
const testDate = '2023-10-01T12:34:56.000Z';
beforeAll(async () => {
db = await dbInit('projects_api_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('projects_api_serial', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{

View File

@ -121,7 +121,6 @@ beforeAll(async () => {
anonymiseEventLog: true,
},
},
dbInitMethod: 'legacy' as const,
};
db = await dbInit('segments_api_serial', getLogger, customOptions);

View File

@ -197,9 +197,7 @@ const createTestSegments = async () => {
};
beforeAll(async () => {
db = await dbInit('segments', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('segments', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{

View File

@ -128,7 +128,7 @@ test('should collect metrics for updated toggles', async () => {
const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(
/feature_toggle_update_total\{toggle="TestToggle",project="default",environment="default",environmentType="production",action="updated"\} 1/,
/feature_toggle_update_total\{toggle="TestToggle",project="default",environment="n\/a",environmentType="n\/a",action="updated"\} 1/,
);
});

View File

@ -902,8 +902,8 @@ export function registerPrometheusMetrics(
featureFlagUpdateTotal.increment({
toggle: featureName,
project,
environment: 'default',
environmentType: 'production',
environment: 'n/a',
environmentType: 'n/a',
action: 'updated',
});
});

View File

@ -27,9 +27,9 @@ export const apiTokenSchema = {
},
environment: {
type: 'string',
description:
'The environment the token has access to. `*` if it has access to all environments.',
description: 'The environment the token has access to.',
example: 'development',
default: 'development',
},
project: {
type: 'string',

View File

@ -1,7 +1,6 @@
import joi from 'joi';
import { ALL } from '../types/models/api-token.js';
import { ApiTokenType } from '../types/model.js';
import { DEFAULT_ENV } from '../util/constants.js';
export const createApiToken = joi
.object()
@ -14,10 +13,6 @@ export const createApiToken = joi
.valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND),
expiresAt: joi.date().optional(),
projects: joi.array().min(1).optional().default([ALL]),
environment: joi.when('type', {
is: joi.string().valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND),
then: joi.string().optional().default(DEFAULT_ENV),
otherwise: joi.string().optional().default(ALL),
}),
environment: joi.string().optional().default('development'),
})
.options({ stripUnknown: true, allowUnknown: false, abortEarly: false });

View File

@ -1,4 +1,4 @@
export const DEFAULT_ENV = 'default';
export const DEFAULT_ENV = 'development';
export const ALL_PROJECTS = '*';
export const ALL_ENVS = '*';

View File

@ -0,0 +1,23 @@
'use strict';
exports.up = function(db, cb) {
db.runSql(
`SELECT count(*) as count FROM events;`,
(err, results) => {
if (Number(results.rows[0].count) === 4) {
// If there are exactly 4 events, it means this is a new install
db.runSql(
`DELETE FROM environments WHERE name = 'default';`,
cb);
} else {
// If there are not exactly 4 events, do nothing
cb();
}
},
);
};
exports.down = function(db, cb) {
cb();
};

View File

@ -11,9 +11,7 @@ let db: ITestDb;
let app: IUnleashTest;
beforeAll(async () => {
db = await dbInit('context_api_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('context_api_serial', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{
@ -34,13 +32,15 @@ afterAll(async () => {
test('gets all context fields', async () => {
expect.assertions(1);
return app.request
const { body } = await app.request
.get('/api/admin/context')
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
expect(res.body.length).toBe(3);
});
.expect(200);
// because tests share the database, we might have more fields than expected
expect(body.map((field) => field.name)).toEqual(
expect.arrayContaining(['environment', 'userId', 'sessionId']),
);
});
test('get the context field', async () => {

View File

@ -15,9 +15,7 @@ let stores: IUnleashStores;
let refreshDbMetrics: () => Promise<void>;
beforeAll(async () => {
db = await dbInit('instance_admin_api_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('instance_admin_api_serial', getLogger);
stores = db.stores;
await stores.settingStore.insert('instanceInfo', { id: 'test-static' });
app = await setupAppWithCustomConfig(
@ -126,7 +124,7 @@ test('should return signed instance statistics', async () => {
.expect((res) => {
expect(res.body.instanceId).toBe('test-static');
expect(res.body.sum).toBe(
'5ba2cb7c3e29f4e5b3382c560b92b837f3603dc7db73a501ec331c7f0ed17bd0',
'd9bac94bba7afa20d98f0a9d54a84b79a6668f8103b8f89db85d05d38e84f519',
);
});
});

View File

@ -11,9 +11,7 @@ let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('metrics_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('metrics_serial', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{
@ -179,9 +177,9 @@ test('should save multiple projects from token', async () => {
.expect('Content-Type', /json/)
.expect(200);
expect(body).toMatchObject({
applications: [
{
expect(body.applications).toEqual(
expect.arrayContaining([
expect.objectContaining({
appName: 'multi-project-app',
usage: [
{
@ -193,8 +191,7 @@ test('should save multiple projects from token', async () => {
project: 'mainProject',
},
],
},
],
total: 1,
});
}),
]),
);
});

View File

@ -12,9 +12,7 @@ let db: ITestDb;
let user: IUser;
beforeAll(async () => {
db = await dbInit('project_health_api_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('project_health_api_serial', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{

View File

@ -9,9 +9,7 @@ let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('strategy_api_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('strategy_api_serial', getLogger);
app = await setupAppWithCustomConfig(db.stores, {
experimental: {
flags: {
@ -19,6 +17,16 @@ beforeAll(async () => {
},
},
});
await db.stores.strategyStore.createStrategy({
name: 'toBeDeleted',
description: 'toBeDeleted strategy',
parameters: [],
});
await db.stores.strategyStore.createStrategy({
name: 'toBeUpdated',
description: 'toBeUpdated strategy',
parameters: [],
});
});
afterAll(async () => {
@ -29,13 +37,11 @@ afterAll(async () => {
test('gets all strategies', async () => {
expect.assertions(1);
return app.request
const { body } = await app.request
.get('/api/admin/strategies')
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
expect(res.body.strategies).toHaveLength(3);
});
.expect(200);
expect(body.strategies).toHaveLength(6);
});
test('gets a strategy by name', async () => {
@ -93,9 +99,7 @@ test('refuses to create a strategy with an existing name', async () => {
test('deletes a new strategy', async () => {
expect.assertions(0);
return app.request
.delete('/api/admin/strategies/usersWithEmail')
.expect(200);
return app.request.delete('/api/admin/strategies/toBeDeleted').expect(200);
});
test("can't delete a strategy that dose not exist", async () => {
@ -108,10 +112,10 @@ test('updates a exiting strategy', async () => {
expect.assertions(0);
return app.request
.put('/api/admin/strategies/default')
.put('/api/admin/strategies/toBeUpdated')
.send({
name: 'default',
description: 'Default is the best!',
name: 'toBeUpdated',
description: 'toBeUpdated is the best!',
parameters: [],
})
.set('Content-Type', 'application/json')

View File

@ -15,9 +15,7 @@ const userId = -9999;
const projectId = 'default';
beforeAll(async () => {
db = await dbInit('feature_env_api_client', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('feature_env_api_client', getLogger);
app = await setupAppWithCustomConfig(db.stores, {}, db.rawDatabase);
await app.services.featureToggleService.createFeatureToggle(

View File

@ -152,26 +152,26 @@ describe.each([
.expect(200);
if (etagVariant.feature_enabled) {
expect(res.headers.etag).toBe(`"61824cd0:16:${etagVariant.name}"`);
expect(res.headers.etag).toBe(`"76d8bb0e:16:${etagVariant.name}"`);
expect(res.body.meta.etag).toBe(
`"61824cd0:16:${etagVariant.name}"`,
`"76d8bb0e:16:${etagVariant.name}"`,
);
} else {
expect(res.headers.etag).toBe('"61824cd0:16"');
expect(res.body.meta.etag).toBe('"61824cd0:16"');
expect(res.headers.etag).toBe('"76d8bb0e:16"');
expect(res.body.meta.etag).toBe('"76d8bb0e:16"');
}
});
test(`returns ${etagVariant.feature_enabled ? 200 : 304} for pre-calculated hash${etagVariant.feature_enabled ? ' because hash changed' : ''}`, async () => {
const res = await app.request
.get('/api/client/features')
.set('if-none-match', '"61824cd0:16"')
.set('if-none-match', '"76d8bb0e:16"')
.expect(etagVariant.feature_enabled ? 200 : 304);
if (etagVariant.feature_enabled) {
expect(res.headers.etag).toBe(`"61824cd0:16:${etagVariant.name}"`);
expect(res.headers.etag).toBe(`"76d8bb0e:16:${etagVariant.name}"`);
expect(res.body.meta.etag).toBe(
`"61824cd0:16:${etagVariant.name}"`,
`"76d8bb0e:16:${etagVariant.name}"`,
);
}
});
@ -193,13 +193,13 @@ describe.each([
.expect(200);
if (etagVariant.feature_enabled) {
expect(res.headers.etag).toBe(`"61824cd0:16:${etagVariant.name}"`);
expect(res.headers.etag).toBe(`"76d8bb0e:16:${etagVariant.name}"`);
expect(res.body.meta.etag).toBe(
`"61824cd0:16:${etagVariant.name}"`,
`"76d8bb0e:16:${etagVariant.name}"`,
);
} else {
expect(res.headers.etag).toBe('"61824cd0:16"');
expect(res.body.meta.etag).toBe('"61824cd0:16"');
expect(res.headers.etag).toBe('"76d8bb0e:16"');
expect(res.body.meta.etag).toBe('"76d8bb0e:16"');
}
});
});

View File

@ -16,9 +16,7 @@ let db: ITestDb;
let defaultToken: IApiToken;
beforeAll(async () => {
db = await dbInit('metrics_two_api_client', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('metrics_two_api_client', getLogger);
app = await setupAppWithAuth(db.stores, {}, db.rawDatabase);
defaultToken =
await app.services.apiTokenService.createApiTokenWithProjects({

View File

@ -15,9 +15,7 @@ let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('register_client', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('register_client', getLogger);
app = await setupApp(db.stores);
});

View File

@ -1,16 +1,11 @@
import { log } from 'db-migrate-shared';
import { migrateDb } from '../../../migrator.js';
import { createStores } from '../../../lib/db/index.js';
import { createDb } from '../../../lib/db/db-pool.js';
import { getDbConfig } from './database-config.js';
import { createTestConfig } from '../../config/test-config.js';
import dbState from './database.json' with { type: 'json' };
import type { LogProvider } from '../../../lib/logger.js';
import noLoggerProvider from '../../fixtures/no-logger.js';
import type EnvironmentStore from '../../../lib/features/project-environments/environment-store.js';
import type { IUnleashStores } from '../../../lib/types/index.js';
import type { IFeatureEnvironmentStore } from '../../../lib/types/stores/feature-environment-store.js';
import { DEFAULT_ENV } from '../../../lib/util/constants.js';
import type {
IUnleashConfig,
IUnleashOptions,
@ -27,72 +22,6 @@ process.setMaxListeners(0);
export const testDbPrefix = 'unleashtestdb_';
async function getDefaultEnvRolePermissions(knex) {
return knex.table('role_permission').whereIn('environment', ['default']);
}
async function restoreRolePermissions(knex, rolePermissions) {
await knex.table('role_permission').insert(rolePermissions);
}
async function resetDatabase(knex) {
return Promise.all([
knex
.table('environments')
.del(), // deletes role permissions transitively
knex.table('strategies').del(),
knex.table('features').del(),
knex.table('client_applications').del(),
knex.table('client_instances').del(),
knex.table('context_fields').del(),
knex.table('users').del(),
knex.table('projects').del(),
knex.table('tags').del(),
knex.table('tag_types').del(),
knex.table('addons').del(),
knex.table('users').del(),
knex.table('api_tokens').del(),
knex.table('api_token_project').del(),
knex
.table('reset_tokens')
.del(),
// knex.table('settings').del(),
]);
}
function createStrategies(store) {
return dbState.strategies.map((s) => store.createStrategy(s));
}
function createContextFields(store) {
return dbState.contextFields.map((c) => store.create(c));
}
function createProjects(store) {
return dbState.projects.map((i) => store.create(i));
}
function createTagTypes(store) {
return dbState.tag_types.map((t) => store.createTagType(t));
}
async function connectProject(store: IFeatureEnvironmentStore): Promise<void> {
await store.connectProject(DEFAULT_ENV, 'default');
}
async function createEnvironments(store: EnvironmentStore): Promise<void> {
await Promise.all(dbState.environments.map(async (e) => store.create(e)));
}
async function setupDatabase(stores) {
await createEnvironments(stores.environmentStore);
await Promise.all(createStrategies(stores.strategyStore));
await Promise.all(createContextFields(stores.contextFieldStore));
await Promise.all(createProjects(stores.projectStore));
await Promise.all(createTagTypes(stores.tagTypeStore));
await connectProject(stores.featureEnvironmentStore);
}
export interface ITestDb {
config: IUnleashConfig;
stores: IUnleashStores;
@ -102,7 +31,6 @@ export interface ITestDb {
}
type DBTestOptions = {
dbInitMethod?: 'legacy' | 'template';
stopMigrationAt?: string; // filename where migration should stop
};
@ -112,16 +40,12 @@ export default async function init(
configOverride: Partial<IUnleashOptions & DBTestOptions> = {},
): Promise<ITestDb> {
const testDbName = `${testDbPrefix}${uuidv4().replace(/-/g, '')}`;
const useDbTemplate =
(configOverride.dbInitMethod ?? 'template') === 'template';
const testDBTemplateName = process.env.TEST_DB_TEMPLATE_NAME;
const config = createTestConfig({
db: {
...getDbConfig(),
pool: { min: 1, max: 4 },
...(useDbTemplate
? { database: testDbName }
: { schema: databaseSchema }),
database: testDbName,
ssl: false,
},
...configOverride,
@ -130,55 +54,30 @@ export default async function init(
log.setLogLevel('error');
if (useDbTemplate) {
if (!testDBTemplateName) {
throw new Error(
'TEST_DB_TEMPLATE_NAME environment variable is not set',
);
}
const client = new Client(getDbConfig());
await client.connect();
await client.query(
`CREATE DATABASE ${testDbName} TEMPLATE ${testDBTemplateName}`,
if (!testDBTemplateName) {
throw new Error(
'TEST_DB_TEMPLATE_NAME environment variable is not set',
);
await client.query(`ALTER DATABASE ${testDbName} SET TIMEZONE TO UTC`);
await client.end();
} else {
const db = createDb(config);
await db.raw(`DROP SCHEMA IF EXISTS ${config.db.schema} CASCADE`);
await db.raw(`CREATE SCHEMA IF NOT EXISTS ${config.db.schema}`);
await migrateDb(config, configOverride.stopMigrationAt);
await db.destroy();
}
const client = new Client(getDbConfig());
await client.connect();
await client.query(
`CREATE DATABASE ${testDbName} TEMPLATE ${testDBTemplateName}`,
);
await client.query(`ALTER DATABASE ${testDbName} SET TIMEZONE TO UTC`);
await client.end();
const testDb = createDb(config);
const stores = createStores(config, testDb);
stores.eventStore.setMaxListeners(0);
if (!useDbTemplate) {
const defaultRolePermissions =
await getDefaultEnvRolePermissions(testDb);
await resetDatabase(testDb);
await setupDatabase(stores);
await restoreRolePermissions(testDb, defaultRolePermissions);
}
return {
config,
rawDatabase: testDb,
stores,
reset: async () => {
if (!useDbTemplate) {
const defaultRolePermissions =
await getDefaultEnvRolePermissions(testDb);
await resetDatabase(testDb);
await setupDatabase(stores);
await restoreRolePermissions(testDb, defaultRolePermissions);
}
},
reset: async () => {},
destroy: async () => {
return new Promise<void>((resolve, reject) => {
testDb.destroy((error) => (error ? reject(error) : resolve()));

View File

@ -8,9 +8,7 @@ let stores: IUnleashStores;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('api_token_store_serial', getLogger, {
dbInitMethod: 'legacy' as const,
});
db = await dbInit('api_token_store_serial', getLogger);
stores = db.stores;
});