1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-17 13:46:47 +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": { "meta": {
"etag": ""61824cd0:19"", "etag": ""76d8bb0e:19"",
"queryHash": "61824cd0", "queryHash": "76d8bb0e",
"revisionId": 19, "revisionId": 19,
}, },
"query": { "query": {
"environment": "default", "environment": "development",
"inlineSegmentConstraints": true, "inlineSegmentConstraints": true,
}, },
"version": 2, "version": 2,

View File

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

View File

@ -53,9 +53,9 @@ test('querying environments in OSS only returns environments that are included i
.get('/api/admin/environments') .get('/api/admin/environments')
.expect(200) .expect(200)
.expect((res) => { .expect((res) => {
expect(res.body.environments).toHaveLength(3); expect(res.body.environments).toHaveLength(2);
const names = res.body.environments.map((env) => env.name); 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') .get('/api/admin/environments/project/default')
.expect(200) .expect(200)
.expect((res) => { .expect((res) => {
expect(res.body.environments).toHaveLength(3); expect(res.body.environments).toHaveLength(2);
const names = res.body.environments.map((env) => env.name); 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; let db: ITestDb;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('environment_api_serial', getLogger, { db = await dbInit('environment_api_serial', getLogger);
dbInitMethod: 'legacy' as const,
});
app = await setupAppWithCustomConfig( app = await setupAppWithCustomConfig(
db.stores, db.stores,
{ {
@ -34,23 +32,32 @@ afterAll(async () => {
}); });
test('Can list all existing environments', async () => { test('Can list all existing environments', async () => {
await app.request const { body } = await app.request
.get('/api/admin/environments') .get('/api/admin/environments')
.expect(200) .expect(200)
.expect('Content-Type', /json/) .expect('Content-Type', /json/);
.expect((res) => { expect(body.version).toBe(1);
expect(res.body.version).toBe(1); expect(body.environments[0]).toStrictEqual({
expect(res.body.environments[0]).toStrictEqual({
name: DEFAULT_ENV, name: DEFAULT_ENV,
enabled: true, enabled: true,
sortOrder: 1, sortOrder: 2,
type: 'production', type: 'development',
protected: true, protected: false,
requiredApprovals: null, requiredApprovals: null,
projectCount: 1, projectCount: 1,
apiTokenCount: 0, apiTokenCount: 0,
enabledToggleCount: 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,
}); });
}); });

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,9 +27,7 @@ let app: IUnleashTest;
let db: ITestDb; let db: ITestDb;
let frontendApiService: FrontendApiService; let frontendApiService: FrontendApiService;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('frontend_api', getLogger, { db = await dbInit('frontend_api', getLogger);
dbInitMethod: 'legacy' as const,
});
app = await setupAppWithAuth( app = await setupAppWithAuth(
db.stores, 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(); const now = new Date();
await request await request
@ -437,7 +437,7 @@ describe('bulk metrics', () => {
{ {
featureName: 'test_feature_two', featureName: 'test_feature_two',
appName: 'test_application', appName: 'test_application',
environment: 'development', environment: 'production',
timestamp: startOfHour(now), timestamp: startOfHour(now),
yes: 1000, yes: 1000,
no: 800, no: 800,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -128,7 +128,7 @@ test('should collect metrics for updated toggles', async () => {
const metrics = await prometheusRegister.metrics(); const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch( 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({ featureFlagUpdateTotal.increment({
toggle: featureName, toggle: featureName,
project, project,
environment: 'default', environment: 'n/a',
environmentType: 'production', environmentType: 'n/a',
action: 'updated', action: 'updated',
}); });
}); });

View File

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

View File

@ -1,7 +1,6 @@
import joi from 'joi'; import joi from 'joi';
import { ALL } from '../types/models/api-token.js'; import { ALL } from '../types/models/api-token.js';
import { ApiTokenType } from '../types/model.js'; import { ApiTokenType } from '../types/model.js';
import { DEFAULT_ENV } from '../util/constants.js';
export const createApiToken = joi export const createApiToken = joi
.object() .object()
@ -14,10 +13,6 @@ export const createApiToken = joi
.valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND), .valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND),
expiresAt: joi.date().optional(), expiresAt: joi.date().optional(),
projects: joi.array().min(1).optional().default([ALL]), projects: joi.array().min(1).optional().default([ALL]),
environment: joi.when('type', { environment: joi.string().optional().default('development'),
is: joi.string().valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND),
then: joi.string().optional().default(DEFAULT_ENV),
otherwise: joi.string().optional().default(ALL),
}),
}) })
.options({ stripUnknown: true, allowUnknown: false, abortEarly: false }); .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_PROJECTS = '*';
export const ALL_ENVS = '*'; 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; let app: IUnleashTest;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('context_api_serial', getLogger, { db = await dbInit('context_api_serial', getLogger);
dbInitMethod: 'legacy' as const,
});
app = await setupAppWithCustomConfig( app = await setupAppWithCustomConfig(
db.stores, db.stores,
{ {
@ -34,13 +32,15 @@ afterAll(async () => {
test('gets all context fields', async () => { test('gets all context fields', async () => {
expect.assertions(1); expect.assertions(1);
return app.request const { body } = await app.request
.get('/api/admin/context') .get('/api/admin/context')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200);
.expect((res) => {
expect(res.body.length).toBe(3); // 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 () => { test('get the context field', async () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,11 @@
import { log } from 'db-migrate-shared'; import { log } from 'db-migrate-shared';
import { migrateDb } from '../../../migrator.js';
import { createStores } from '../../../lib/db/index.js'; import { createStores } from '../../../lib/db/index.js';
import { createDb } from '../../../lib/db/db-pool.js'; import { createDb } from '../../../lib/db/db-pool.js';
import { getDbConfig } from './database-config.js'; import { getDbConfig } from './database-config.js';
import { createTestConfig } from '../../config/test-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 type { LogProvider } from '../../../lib/logger.js';
import noLoggerProvider from '../../fixtures/no-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 { 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 { import type {
IUnleashConfig, IUnleashConfig,
IUnleashOptions, IUnleashOptions,
@ -27,72 +22,6 @@ process.setMaxListeners(0);
export const testDbPrefix = 'unleashtestdb_'; 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 { export interface ITestDb {
config: IUnleashConfig; config: IUnleashConfig;
stores: IUnleashStores; stores: IUnleashStores;
@ -102,7 +31,6 @@ export interface ITestDb {
} }
type DBTestOptions = { type DBTestOptions = {
dbInitMethod?: 'legacy' | 'template';
stopMigrationAt?: string; // filename where migration should stop stopMigrationAt?: string; // filename where migration should stop
}; };
@ -112,16 +40,12 @@ export default async function init(
configOverride: Partial<IUnleashOptions & DBTestOptions> = {}, configOverride: Partial<IUnleashOptions & DBTestOptions> = {},
): Promise<ITestDb> { ): Promise<ITestDb> {
const testDbName = `${testDbPrefix}${uuidv4().replace(/-/g, '')}`; const testDbName = `${testDbPrefix}${uuidv4().replace(/-/g, '')}`;
const useDbTemplate =
(configOverride.dbInitMethod ?? 'template') === 'template';
const testDBTemplateName = process.env.TEST_DB_TEMPLATE_NAME; const testDBTemplateName = process.env.TEST_DB_TEMPLATE_NAME;
const config = createTestConfig({ const config = createTestConfig({
db: { db: {
...getDbConfig(), ...getDbConfig(),
pool: { min: 1, max: 4 }, pool: { min: 1, max: 4 },
...(useDbTemplate database: testDbName,
? { database: testDbName }
: { schema: databaseSchema }),
ssl: false, ssl: false,
}, },
...configOverride, ...configOverride,
@ -130,7 +54,6 @@ export default async function init(
log.setLogLevel('error'); log.setLogLevel('error');
if (useDbTemplate) {
if (!testDBTemplateName) { if (!testDBTemplateName) {
throw new Error( throw new Error(
'TEST_DB_TEMPLATE_NAME environment variable is not set', 'TEST_DB_TEMPLATE_NAME environment variable is not set',
@ -145,40 +68,16 @@ export default async function init(
await client.query(`ALTER DATABASE ${testDbName} SET TIMEZONE TO UTC`); await client.query(`ALTER DATABASE ${testDbName} SET TIMEZONE TO UTC`);
await client.end(); 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 testDb = createDb(config); const testDb = createDb(config);
const stores = createStores(config, testDb); const stores = createStores(config, testDb);
stores.eventStore.setMaxListeners(0); stores.eventStore.setMaxListeners(0);
if (!useDbTemplate) {
const defaultRolePermissions =
await getDefaultEnvRolePermissions(testDb);
await resetDatabase(testDb);
await setupDatabase(stores);
await restoreRolePermissions(testDb, defaultRolePermissions);
}
return { return {
config, config,
rawDatabase: testDb, rawDatabase: testDb,
stores, stores,
reset: async () => { reset: async () => {},
if (!useDbTemplate) {
const defaultRolePermissions =
await getDefaultEnvRolePermissions(testDb);
await resetDatabase(testDb);
await setupDatabase(stores);
await restoreRolePermissions(testDb, defaultRolePermissions);
}
},
destroy: async () => { destroy: async () => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
testDb.destroy((error) => (error ? reject(error) : resolve())); testDb.destroy((error) => (error ? reject(error) : resolve()));

View File

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