1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-17 13:46:47 +02:00
unleash.unleash/src/lib/features/dependent-features/dependent.features.e2e.test.ts
Gastón Fournier bdb763c9d5
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
2025-06-06 12:02:21 +02:00

385 lines
10 KiB
TypeScript

import { v4 as uuidv4 } from 'uuid';
import dbInit, {
type ITestDb,
} from '../../../test/e2e/helpers/database-init.js';
import {
type IUnleashTest,
setupAppWithCustomConfig,
} from '../../../test/e2e/helpers/test-helper.js';
import getLogger from '../../../test/fixtures/no-logger.js';
import type { CreateDependentFeatureSchema } from '../../openapi/index.js';
import {
FEATURE_DEPENDENCIES_REMOVED,
FEATURE_DEPENDENCY_ADDED,
FEATURE_DEPENDENCY_REMOVED,
} from '../../events/index.js';
import { DEFAULT_ENV } from '../../util/index.js';
import type { IEventStore } from '../../server-impl.js';
let app: IUnleashTest;
let db: ITestDb;
let eventStore: IEventStore;
beforeAll(async () => {
db = await dbInit('dependent_features', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{
experimental: {
flags: {
strictSchemaValidation: true,
enableLegacyVariants: true,
},
},
},
db.rawDatabase,
);
eventStore = db.stores.eventStore;
});
const createProject = async (name: string) => {
await db.stores.projectStore.create({
name: name,
description: '',
id: name,
mode: 'open' as const,
});
};
const getRecordedEventTypesForDependencies = async () =>
(await eventStore.getEvents())
.map((event) => event.type)
.filter((type) => type.includes('depend'));
afterAll(async () => {
await app.destroy();
await db.destroy();
});
beforeEach(async () => {
await db.stores.dependentFeaturesStore.deleteAll();
await db.stores.featureToggleStore.deleteAll();
await db.stores.featureEnvironmentStore.deleteAll();
await db.stores.eventStore.deleteAll();
});
const addFeatureDependency = async (
childFeature: string,
payload: CreateDependentFeatureSchema,
expectedCode = 200,
) => {
return app.request
.post(
`/api/admin/projects/default/features/${childFeature}/dependencies`,
)
.send(payload)
.expect(expectedCode);
};
const deleteFeatureDependency = async (
childFeature: string,
parentFeature: string,
expectedCode = 200,
) => {
return app.request
.delete(
`/api/admin/projects/default/features/${childFeature}/dependencies/${parentFeature}`,
)
.expect(expectedCode);
};
const deleteFeatureDependencies = async (
childFeature: string,
expectedCode = 200,
) => {
return app.request
.delete(
`/api/admin/projects/default/features/${childFeature}/dependencies`,
)
.expect(expectedCode);
};
const getPossibleParentFeatures = async (
childFeature: string,
expectedCode = 200,
) => {
return app.request
.get(`/api/admin/projects/default/features/${childFeature}/parents`)
.expect(expectedCode);
};
const getPossibleParentVariants = async (
parentFeature: string,
expectedCode = 200,
) => {
return app.request
.get(
`/api/admin/projects/default/features/${parentFeature}/parent-variants`,
)
.expect(expectedCode);
};
const addStrategyVariants = async (parent: string, variants: string[]) => {
await app.addStrategyToFeatureEnv(
{
name: 'flexibleRollout',
constraints: [],
parameters: { rollout: '100', stickiness: 'default' },
variants: variants.map((name) => ({
name,
weight: 1000,
weightType: 'variable',
stickiness: 'default',
})),
},
DEFAULT_ENV,
parent,
);
};
const addFeatureEnvironmentVariant = async (
parent: string,
variant: string,
) => {
await app.request
.patch(
`/api/admin/projects/default/features/${parent}/environments/${DEFAULT_ENV}/variants`,
)
.set('Content-Type', 'application/json')
.send([
{
op: 'add',
path: '/0',
value: {
name: variant,
weightType: 'variable',
weight: 1000,
overrides: [],
stickiness: 'default',
},
},
])
.expect(200);
};
const checkDependenciesExist = async (expectedCode = 200) => {
return app.request
.get(`/api/admin/projects/default/dependencies`)
.expect(expectedCode);
};
test('should add and delete feature dependencies', async () => {
const parent = uuidv4();
const child = uuidv4();
const child2 = uuidv4();
await app.createFeature(parent);
await app.createFeature(child);
await app.createFeature(child2);
const { body: options } = await getPossibleParentFeatures(child);
expect(options).toMatchObject([parent, child2].sort());
// save explicit enabled and variants
await addFeatureDependency(child, {
feature: parent,
enabled: false,
});
// overwrite with implicit enabled: true and variants
await addFeatureDependency(child, {
feature: parent,
variants: ['variantB'],
});
await addFeatureDependency(child2, {
feature: parent,
enabled: false,
});
await deleteFeatureDependency(child, parent); // single
await deleteFeatureDependencies(child2); // all
const eventTypes = await getRecordedEventTypesForDependencies();
expect(eventTypes).toStrictEqual([
FEATURE_DEPENDENCIES_REMOVED,
FEATURE_DEPENDENCY_REMOVED,
FEATURE_DEPENDENCY_ADDED,
FEATURE_DEPENDENCY_ADDED,
FEATURE_DEPENDENCY_ADDED,
]);
});
test('should sort potential parent features alphabetically', async () => {
const parent1 = `a${uuidv4()}`;
const parent2 = `c${uuidv4()}`;
const parent3 = `b${uuidv4()}`;
const child = uuidv4();
await app.createFeature(parent1);
await app.createFeature(parent2);
await app.createFeature(parent3);
await app.createFeature(child);
const { body: options } = await getPossibleParentFeatures(child);
expect(options).toStrictEqual([parent1, parent3, parent2]);
});
test('should sort potential parent variants', async () => {
const parent = uuidv4();
await app.createFeature(parent);
await addFeatureEnvironmentVariant(parent, 'e');
await addStrategyVariants(parent, ['c', 'a', 'd']);
await addStrategyVariants(parent, ['b', 'd']);
const { body: variants } = await getPossibleParentVariants(parent);
expect(variants).toStrictEqual(['a', 'b', 'c', 'd', 'e']);
});
test('should not allow to add grandparent', async () => {
const grandparent = uuidv4();
const parent = uuidv4();
const child = uuidv4();
await app.createFeature(grandparent);
await app.createFeature(parent);
await app.createFeature(child);
await addFeatureDependency(child, {
feature: parent,
});
await addFeatureDependency(
parent,
{
feature: grandparent,
},
403,
);
});
test('should not allow to add grandchild', async () => {
const grandparent = uuidv4();
const parent = uuidv4();
const child = uuidv4();
await app.createFeature(grandparent);
await app.createFeature(parent);
await app.createFeature(child);
await addFeatureDependency(parent, {
feature: grandparent,
});
await addFeatureDependency(
child,
{
feature: parent,
},
403,
);
});
test('should not allow to add non-existent parent dependency', async () => {
const parent = uuidv4();
const child = uuidv4();
await app.createFeature(child);
await addFeatureDependency(
child,
{
feature: parent,
},
403,
);
});
test('should not allow to add archived parent dependency', async () => {
const parent = uuidv4();
const child = uuidv4();
await app.createFeature(child);
await app.createFeature(parent);
await app.archiveFeature(parent);
await addFeatureDependency(
child,
{
feature: parent,
},
403,
);
});
test('should check if any dependencies exist', async () => {
const parent = uuidv4();
const child = uuidv4();
await app.createFeature(child);
await app.createFeature(parent);
const { body: dependenciesExistBefore } = await checkDependenciesExist();
expect(dependenciesExistBefore).toBe(false);
await addFeatureDependency(child, {
feature: parent,
});
const { body: dependenciesExistAfter } = await checkDependenciesExist();
expect(dependenciesExistAfter).toBe(true);
});
test('should not allow to add dependency to self', async () => {
const parent = uuidv4();
await app.createFeature(parent);
await addFeatureDependency(
parent,
{
feature: parent,
},
403,
);
});
test('should not allow to add dependency to feature from another project', async () => {
const child = uuidv4();
const parent = uuidv4();
await app.createFeature(parent);
await createProject('another-project');
await app.createFeature(child, 'another-project');
await addFeatureDependency(
child,
{
feature: parent,
},
403,
);
});
test('should create feature-dependency-removed when archiving and has dependency', async () => {
const child = uuidv4();
const parent = uuidv4();
await app.createFeature(parent);
await app.createFeature(child);
await addFeatureDependency(child, {
feature: parent,
});
await app.archiveFeature(child);
const events = await eventStore.getEvents();
expect(events).toEqual(
expect.arrayContaining([
expect.objectContaining({ type: 'feature-dependencies-removed' }),
]),
);
});
test('should not create feature-dependency-removed when archiving and no dependency', async () => {
const child = uuidv4();
const parent = uuidv4();
await app.createFeature(parent);
await app.createFeature(child);
await app.archiveFeature(child);
const events = await eventStore.getEvents();
expect(events).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ type: 'feature-dependencies-removed' }),
]),
);
});