unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
1,133 lines • 34.8 kB
JavaScript
import { setupAppWithCustomConfig, } from '../../../test/e2e/helpers/test-helper.js';
import dbInit from '../../../test/e2e/helpers/database-init.js';
import getLogger from '../../../test/fixtures/no-logger.js';
import { DEFAULT_PROJECT, TEST_AUDIT_USER, } from '../../types/index.js';
import { DEFAULT_ENV } from '../../util/index.js';
let app;
let db;
let eventStore;
let environmentStore;
let contextFieldStore;
let projectStore;
let toggleStore;
let tagStore;
let featureLinkStore;
const defaultStrategy = {
name: 'default',
parameters: {},
constraints: [],
};
const defaultContext = {
name: 'region',
description: 'A region',
legalValues: [
{ value: 'north' },
{
value: 'south',
description: 'south-desc',
},
],
};
const defaultFeatureName = 'first_feature';
const createFlag = async (flag, strategy = defaultStrategy, tags = [], projectId = 'default') => {
await app.services.featureToggleService.createFeatureToggle(projectId, flag, TEST_AUDIT_USER);
if (strategy) {
await app.services.featureToggleService.createStrategy(strategy, {
projectId,
featureName: flag.name,
environment: DEFAULT_ENV,
}, TEST_AUDIT_USER);
}
await Promise.all(tags.map(async (tag) => {
return app.services.featureTagService.addTag(flag.name, {
type: 'simple',
value: tag,
}, TEST_AUDIT_USER);
}));
};
const createContext = async (context = defaultContext) => {
await app.request
.post('/api/admin/context')
.send(context)
.set('Content-Type', 'application/json')
.expect(201);
};
const createVariants = async (feature, variants) => {
await app.services.featureToggleService.legacySaveVariantsOnEnv(DEFAULT_PROJECT, feature, DEFAULT_ENV, variants, TEST_AUDIT_USER);
};
const addLink = async (feature, link) => {
await app.services.transactionalFeatureLinkService.createLink(DEFAULT_ENV, { ...link, featureName: feature }, TEST_AUDIT_USER);
};
const createProjects = async (projects = [DEFAULT_PROJECT], featureLimit = 2) => {
await db.stores.environmentStore.create({
name: DEFAULT_ENV,
type: 'production',
});
for (const project of projects) {
const storedProject = {
name: project,
description: '',
id: project,
mode: 'open',
featureLimit,
};
await db.stores.projectStore.create(storedProject);
await db.stores.projectStore.update(storedProject);
await app.linkProjectToEnvironment(project, DEFAULT_ENV);
}
};
const createSegment = (postData) => {
return app.services.segmentService.create(postData, TEST_AUDIT_USER);
};
const unArchiveFeature = async (featureName) => {
await app.request
.post(`/api/admin/archive/revive/${featureName}`)
.set('Content-Type', 'application/json')
.expect(200);
};
const getContextField = (name) => app.request.get(`/api/admin/context/${name}`).expect(200);
beforeAll(async () => {
db = await dbInit('export_import_api_serial', getLogger);
app = await setupAppWithCustomConfig(db.stores, {
experimental: {
flags: {
featureLinks: true,
},
},
}, db.rawDatabase);
eventStore = db.stores.eventStore;
environmentStore = db.stores.environmentStore;
projectStore = db.stores.projectStore;
contextFieldStore = db.stores.contextFieldStore;
toggleStore = db.stores.featureToggleStore;
tagStore = db.stores.tagStore;
featureLinkStore = db.stores.featureLinkStore;
});
beforeEach(async () => {
await eventStore.deleteAll();
await toggleStore.deleteAll();
await projectStore.deleteAll();
await environmentStore.deleteAll();
await tagStore.deleteAll();
await featureLinkStore.deleteAll();
await contextFieldStore.deleteAll();
await app.createContextField({ name: 'appName' });
});
afterAll(async () => {
await app.destroy();
await db.destroy();
});
describe('import-export for project-specific segments', () => {
test('exports features with project-specific-segments', async () => {
const segmentName = 'my-segment';
const project = 'with-segments';
await createProjects([project]);
const segment = await createSegment({
name: segmentName,
project,
constraints: [],
});
const strategy = {
name: 'default',
parameters: {
rollout: '100',
stickiness: 'default',
},
constraints: [
{
contextName: 'appName',
values: ['test'],
operator: 'IN',
},
],
segments: [segment.id],
};
await createFlag({
name: defaultFeatureName,
description: 'the #1 feature',
}, strategy, [], project);
const { body } = await app.request
.post('/api/admin/features-batch/export')
.send({
features: [defaultFeatureName],
environment: DEFAULT_ENV,
})
.set('Content-Type', 'application/json')
.expect(200);
const { name, ...resultStrategy } = strategy;
expect(body).toMatchObject({
features: [
{
name: defaultFeatureName,
},
],
featureStrategies: [resultStrategy],
featureEnvironments: [
{
enabled: false,
environment: DEFAULT_ENV,
featureName: defaultFeatureName,
},
],
segments: [
{
id: segment.id,
name: segmentName,
},
],
});
});
});
test('exports features', async () => {
const segmentName = 'my-segment';
await createProjects();
const segment = await createSegment({
name: segmentName,
constraints: [],
});
const strategy = {
name: 'default',
parameters: {
rollout: '100',
stickiness: 'default',
},
constraints: [
{
contextName: 'appName',
values: ['test'],
operator: 'IN',
},
],
segments: [segment.id],
};
await createFlag({
name: defaultFeatureName,
description: 'the #1 feature',
}, strategy);
await createFlag({
name: 'second_feature',
description: 'the #1 feature',
}, strategy);
await app.addDependency(defaultFeatureName, 'second_feature');
await addLink(defaultFeatureName, {
url: 'http://example1.com',
title: 'link title 1',
});
await addLink(defaultFeatureName, {
url: 'http://example2.com',
title: 'link title 2',
});
const { body } = await app.request
.post('/api/admin/features-batch/export')
.send({
features: [defaultFeatureName],
environment: DEFAULT_ENV,
})
.set('Content-Type', 'application/json')
.expect(200);
const { name, ...resultStrategy } = strategy;
expect(body).toMatchObject({
features: [
{
name: defaultFeatureName,
},
],
featureStrategies: [resultStrategy],
featureEnvironments: [
{
enabled: false,
environment: DEFAULT_ENV,
featureName: defaultFeatureName,
},
],
segments: [
{
id: segment.id,
name: segmentName,
},
],
dependencies: [
{
feature: defaultFeatureName,
dependencies: [
{
feature: 'second_feature',
enabled: true,
},
],
},
],
links: [
{
feature: defaultFeatureName,
links: [
{ url: 'http://example1.com', title: 'link title 1' },
{ url: 'http://example2.com', title: 'link title 2' },
],
},
],
});
});
test('exports features by tag', async () => {
await createProjects();
const strategy = {
name: 'default',
parameters: {
rollout: '100',
stickiness: 'default',
},
constraints: [
{
contextName: 'appName',
values: ['test'],
operator: 'IN',
},
],
};
await createFlag({
name: defaultFeatureName,
description: 'the #1 feature',
}, strategy, ['mytag']);
await createFlag({
name: 'second_feature',
description: 'the #1 feature',
}, strategy, ['anothertag']);
const { body } = await app.request
.post('/api/admin/features-batch/export')
.send({
tag: 'mytag',
environment: DEFAULT_ENV,
})
.set('Content-Type', 'application/json')
.expect(200);
const { name, ...resultStrategy } = strategy;
expect(body).toMatchObject({
features: [
{
name: defaultFeatureName,
},
],
featureStrategies: [resultStrategy],
featureEnvironments: [
{
enabled: false,
environment: DEFAULT_ENV,
featureName: defaultFeatureName,
},
],
});
});
test('should export custom context fields from strategies and variants', async () => {
await createProjects();
const strategyContext = {
name: 'strategy-context',
legalValues: [
{ value: 'strategy-context-1' },
{ value: 'strategy-context-2' },
{ value: 'strategy-context-3' },
],
};
const strategyStickinessContext = {
name: 'strategy-stickiness',
legalValues: [
{ value: 'strategy-stickiness-1' },
{ value: 'strategy-stickiness-2' },
],
};
await createContext(strategyContext);
await createContext(strategyStickinessContext);
const strategy = {
name: 'default',
parameters: {
rollout: '100',
stickiness: 'strategy-stickiness',
},
constraints: [
{
contextName: strategyContext.name,
values: ['strategy-context-1', 'strategy-context-2'],
operator: 'IN',
},
],
};
await createFlag({
name: defaultFeatureName,
description: 'the #1 feature',
}, strategy);
const variantStickinessContext = {
name: 'variant-stickiness-context',
legalValues: [
{ value: 'variant-stickiness-context-1' },
{ value: 'variant-stickiness-context-2' },
],
};
const variantOverridesContext = {
name: 'variant-overrides-context',
legalValues: [
{ value: 'variant-overrides-context-1' },
{ value: 'variant-overrides-context-2' },
],
};
await createContext(variantStickinessContext);
await createContext(variantOverridesContext);
await createVariants(defaultFeatureName, [
{
name: 'irrelevant',
weight: 1000,
stickiness: 'variant-stickiness-context',
weightType: 'variable',
overrides: [
{
contextName: 'variant-overrides-context',
values: ['variant-overrides-context-1'],
},
],
},
]);
const { body } = await app.request
.post('/api/admin/features-batch/export')
.send({
features: [defaultFeatureName],
environment: DEFAULT_ENV,
})
.set('Content-Type', 'application/json')
.expect(200);
const { name, ...resultStrategy } = strategy;
expect(body).toMatchObject({
features: [
{
name: defaultFeatureName,
},
],
featureStrategies: [resultStrategy],
featureEnvironments: [
{
enabled: false,
environment: DEFAULT_ENV,
featureName: defaultFeatureName,
},
],
contextFields: [
strategyContext,
strategyStickinessContext,
variantOverridesContext,
variantStickinessContext,
],
});
});
test('should export tags', async () => {
const featureName = defaultFeatureName;
await createProjects();
await createFlag({
name: featureName,
description: 'the #1 feature',
}, defaultStrategy, ['tag1']);
const { body } = await app.request
.post('/api/admin/features-batch/export')
.send({
features: [defaultFeatureName],
environment: DEFAULT_ENV,
})
.set('Content-Type', 'application/json')
.expect(200);
const { name, ...resultStrategy } = defaultStrategy;
expect(body).toMatchObject({
features: [
{
name: defaultFeatureName,
},
],
featureStrategies: [resultStrategy],
featureEnvironments: [
{
enabled: false,
environment: DEFAULT_ENV,
featureName: defaultFeatureName,
},
],
featureTags: [
{
featureName,
tagValue: 'tag1',
},
],
});
});
test('returns all features, when no explicit feature was requested', async () => {
await createProjects();
await createFlag({
name: defaultFeatureName,
description: 'the #1 feature',
});
await createFlag({
name: 'second_feature',
description: 'the #1 feature',
});
const { body } = await app.request
.post('/api/admin/features-batch/export')
.send({
features: [],
environment: DEFAULT_ENV,
})
.set('Content-Type', 'application/json')
.expect(200);
expect(body.features).toHaveLength(2);
});
test('returns all project features', async () => {
await createProjects();
await createFlag({
name: defaultFeatureName,
description: 'the #1 feature',
});
await createFlag({
name: 'second_feature',
description: 'the #1 feature',
});
const { body } = await app.request
.post('/api/admin/features-batch/export')
.send({
environment: DEFAULT_ENV,
project: DEFAULT_PROJECT,
})
.set('Content-Type', 'application/json')
.expect(200);
expect(body.features).toHaveLength(2);
const { body: otherProject } = await app.request
.post('/api/admin/features-batch/export')
.send({
environment: DEFAULT_ENV,
features: [], // should be ignored because we have project
project: 'other_project',
})
.set('Content-Type', 'application/json')
.expect(200);
expect(otherProject.features).toHaveLength(0);
});
const variants = [
{
name: 'variantA',
weight: 500,
payload: {
type: 'string',
value: 'payloadA',
},
overrides: [],
stickiness: 'default',
weightType: 'variable',
},
{
name: 'variantB',
weight: 500,
payload: {
type: 'string',
value: 'payloadB',
},
overrides: [],
stickiness: 'default',
weightType: 'variable',
},
];
const exportedFeature = {
project: 'old_project',
name: defaultFeatureName,
type: 'release',
};
const anotherExportedFeature = {
project: 'old_project',
name: 'second_feature',
};
const constraints = [
{
values: ['conduit'],
inverted: false,
operator: 'IN',
contextName: 'appName',
caseInsensitive: false,
},
];
const exportedStrategy = {
featureName: defaultFeatureName,
id: '798cb25a-2abd-47bd-8a95-40ec13472309',
name: 'default',
parameters: {},
constraints,
};
const tags = [
{
featureName: defaultFeatureName,
tagType: 'simple',
tagValue: 'tag1',
},
{
featureName: defaultFeatureName,
tagType: 'simple',
tagValue: 'tag2',
},
{
featureName: defaultFeatureName,
tagType: 'special_tag',
tagValue: 'feature_tagged',
},
];
const resultTags = [
{
value: 'tag1',
type: 'simple',
},
{
value: 'tag2',
type: 'simple',
},
{
value: 'feature_tagged',
type: 'special_tag',
},
];
const tagTypes = [
{
name: 'bestt',
description: 'test',
},
{
name: 'special_tag',
description: 'this is my special tag',
},
{
name: 'special_tag',
description: 'this is my special tag',
}, // deliberate duplicate
];
const defaultImportPayload = {
data: {
features: [exportedFeature],
featureStrategies: [exportedStrategy],
featureEnvironments: [
{
enabled: true,
environment: 'irrelevant',
featureName: defaultFeatureName,
name: defaultFeatureName,
variants,
},
],
featureTags: tags,
tagTypes,
contextFields: [],
segments: [],
},
project: DEFAULT_PROJECT,
environment: DEFAULT_ENV,
};
const importWithMultipleFeatures = {
data: {
features: [exportedFeature, anotherExportedFeature],
featureStrategies: [],
featureEnvironments: [],
featureTags: [
{
featureName: exportedFeature.name,
tagType: 'simple',
tagValue: 'tag1',
},
{
featureName: anotherExportedFeature.name,
tagType: 'simple',
tagValue: 'tag1',
},
],
tagTypes,
contextFields: [],
segments: [],
},
project: DEFAULT_PROJECT,
environment: DEFAULT_ENV,
};
const getFeature = async (feature) => app.request
.get(`/api/admin/projects/${DEFAULT_PROJECT}/features/${feature}`)
.expect(200);
const getFeatureEnvironment = (feature) => app.request
.get(`/api/admin/projects/${DEFAULT_PROJECT}/features/${feature}/environments/${DEFAULT_ENV}`)
.expect(200);
const getTags = (feature) => app.request.get(`/api/admin/features/${feature}/tags`).expect(200);
const validateImport = (importPayload, status = 200) => app.request
.post('/api/admin/features-batch/validate')
.send(importPayload)
.set('Content-Type', 'application/json')
.expect(status);
test('import features to existing project and environment', async () => {
await createProjects();
const segment = await createSegment({
name: 'newSegment',
constraints: [],
});
await app.importToggles({
...defaultImportPayload,
data: {
...defaultImportPayload.data,
features: [
...defaultImportPayload.data.features,
anotherExportedFeature,
],
featureStrategies: [
{
...exportedStrategy,
segments: [segment.id],
},
],
segments: [
{
id: segment.id,
name: segment.name,
},
],
dependencies: [
{
feature: exportedFeature.name,
dependencies: [
{
feature: anotherExportedFeature.name,
},
],
},
],
links: [
{
feature: exportedFeature.name,
links: [
{ url: 'http://example1.com', title: 'link title 1' },
{ url: 'http://example2.com' },
],
},
],
},
});
const { body: importedFeature } = await getFeature(defaultFeatureName);
expect(importedFeature).toMatchObject({
name: defaultFeatureName,
project: DEFAULT_PROJECT,
variants,
environments: [
{
strategies: [
{
segments: [segment.id],
},
],
},
],
dependencies: [
{
feature: anotherExportedFeature.name,
},
],
links: [
{ title: 'link title 1', url: 'http://example1.com' },
{ title: null, url: 'http://example2.com' },
],
});
const { body: importedFeatureEnvironment } = await getFeatureEnvironment(defaultFeatureName);
expect(importedFeatureEnvironment).toMatchObject({
name: defaultFeatureName,
environment: DEFAULT_ENV,
enabled: true,
strategies: [
{
featureName: defaultFeatureName,
parameters: {},
constraints,
sortOrder: 0,
name: 'default',
},
],
});
const { body: importedTags } = await getTags(defaultFeatureName);
expect(importedTags).toMatchObject({
tags: resultTags,
});
});
test('import multiple features with same tag', async () => {
await createProjects();
await app.importToggles(importWithMultipleFeatures);
const { body: tags1 } = await getTags(exportedFeature.name);
const { body: tags2 } = await getTags(anotherExportedFeature.name);
expect(tags1).toMatchObject({
version: 1,
tags: [
{
value: 'tag1',
type: 'simple',
},
],
});
expect(tags2).toMatchObject({
version: 1,
tags: [
{
value: 'tag1',
type: 'simple',
},
],
});
});
test('import too many feature exceeding limit', async () => {
const featureLimit = 1;
await createProjects([DEFAULT_PROJECT], featureLimit);
await app.importToggles(importWithMultipleFeatures, 403);
});
test('can update toggles on subsequent import', async () => {
await createProjects();
await app.importToggles(defaultImportPayload);
await app.importToggles({
...defaultImportPayload,
data: {
...defaultImportPayload.data,
features: [
{
...exportedFeature,
type: 'operational',
},
],
},
});
const { body: importedFeature } = await getFeature(defaultFeatureName);
expect(importedFeature).toMatchObject({
name: defaultFeatureName,
project: DEFAULT_PROJECT,
type: 'operational',
variants,
});
const { body: importedFeatureEnvironment } = await getFeatureEnvironment(defaultFeatureName);
expect(importedFeatureEnvironment).toMatchObject({
name: defaultFeatureName,
environment: DEFAULT_ENV,
enabled: true,
strategies: [
{
featureName: defaultFeatureName,
parameters: {},
constraints,
sortOrder: 0,
name: 'default',
},
],
});
});
test('reject import with unknown context fields', async () => {
await createProjects();
const contextField = {
name: 'ContextField1',
legalValues: [
{
value: 'Value1',
description: '',
},
],
};
await app.createContextField(contextField);
const importPayloadWithContextFields = {
...defaultImportPayload,
data: {
...defaultImportPayload.data,
contextFields: [
{
...contextField,
legalValues: [
{
value: 'Value2',
description: '',
},
],
},
],
},
};
const { body } = await app.importToggles(importPayloadWithContextFields, 400);
expect(body.details[0].message).toMatch(/\bContextField1\b/);
});
test('reject import with unsupported strategies', async () => {
await createProjects();
const importPayloadWithContextFields = {
...defaultImportPayload,
data: {
...defaultImportPayload.data,
featureStrategies: [
{
name: 'customStrategy',
featureName: 'featureName',
},
],
},
};
const { body } = await app.importToggles(importPayloadWithContextFields, 400);
expect(body.details[0].message).toMatch(/\bcustomStrategy\b/);
});
test('reject import with duplicate features', async () => {
await createProjects();
const importPayloadWithContextFields = {
...defaultImportPayload,
data: {
...defaultImportPayload.data,
features: [exportedFeature, exportedFeature],
},
};
const { body } = await app.importToggles(importPayloadWithContextFields, 409);
expect(body.details[0].message).toBe('A flag with that name already exists');
});
test('validate import data', async () => {
const featureLimit = 1;
await createProjects([DEFAULT_PROJECT], featureLimit);
const contextField = {
name: 'validate_context_field',
legalValues: [{ value: 'Value1' }],
};
const createdContextField = {
name: 'created_context_field',
legalValues: [{ value: 'new_value' }],
};
await app.createFeature(defaultFeatureName);
await app.archiveFeature(defaultFeatureName);
await app.createContextField(contextField);
const importPayloadWithContextFields = {
...defaultImportPayload,
data: {
...defaultImportPayload.data,
features: [
exportedFeature,
exportedFeature,
anotherExportedFeature,
],
featureStrategies: [{ name: 'customStrategy' }],
segments: [
{
id: 1,
name: 'customSegment',
},
],
contextFields: [
{
...contextField,
legalValues: [{ value: 'Value2' }],
},
createdContextField,
],
dependencies: [
{
feature: 'childFeature',
dependencies: [
{
feature: 'parentFeature',
},
],
},
],
},
};
// note: this must be done after creating the feature on the earlier lines,
// to prevent the pattern from blocking the creation.
await projectStore.updateProjectEnterpriseSettings({
id: DEFAULT_PROJECT,
mode: 'open',
featureNaming: { pattern: 'testpattern.+' },
});
const { body } = await validateImport(importPayloadWithContextFields, 200);
expect(body).toMatchObject({
errors: [
{
message: 'We detected the following custom strategy that needs to be created first:',
affectedItems: ['customStrategy'],
},
{
message: 'We detected the following context fields that do not have matching legal values with the imported ones:',
affectedItems: [contextField.name],
},
{
message: 'We detected the following features are duplicate in your import data:',
affectedItems: [defaultFeatureName],
},
{
message: expect.stringMatching(/\btestpattern.+\b/),
affectedItems: [
defaultFeatureName,
anotherExportedFeature.name,
],
},
{
message: 'We detected you want to create 2 new features to a project that already has 0 existing features, exceeding the maximum limit of 1.',
affectedItems: [],
},
{
message: 'We detected the following segments that need to be created first:',
affectedItems: ['customSegment'],
},
{
affectedItems: ['parentFeature'],
message: 'We detected the following dependencies that need to be created first:',
},
],
warnings: [
{
message: 'The following features will not be imported as they are currently archived. To import them, please unarchive them first:',
affectedItems: [defaultFeatureName],
},
],
permissions: [],
});
});
test('should create new context', async () => {
await createProjects();
const context = {
name: 'create-new-context',
legalValues: [{ value: 'Value1' }],
};
const importPayloadWithContextFields = {
...defaultImportPayload,
data: {
...defaultImportPayload.data,
contextFields: [context],
},
};
await app.importToggles(importPayloadWithContextFields);
const { body } = await getContextField(context.name);
expect(body).toMatchObject(context);
});
test('should not import archived features tags', async () => {
await createProjects();
await app.importToggles(defaultImportPayload);
await app.archiveFeature(defaultFeatureName);
await app.importToggles({
...defaultImportPayload,
data: {
...defaultImportPayload.data,
featureTags: [
{
featureName: defaultFeatureName,
tagType: 'simple',
tagValue: 'tag2',
},
],
},
});
await unArchiveFeature(defaultFeatureName);
const { body: importedTags } = await getTags(defaultFeatureName);
expect(importedTags).toMatchObject({
tags: resultTags,
});
});
test('should not import archived parent', async () => {
await createProjects();
await app.createFeature('parent');
await app.archiveFeature('parent');
await app.importToggles({
data: {
features: [{ name: 'child' }, { name: 'parent' }],
dependencies: [
{
feature: 'child',
dependencies: [
{
feature: 'parent',
},
],
},
],
featureStrategies: [],
featureEnvironments: [],
featureTags: [],
tagTypes: [],
contextFields: [],
segments: [],
},
project: DEFAULT_PROJECT,
environment: DEFAULT_ENV,
});
const { body } = await app.getProjectFeatures(DEFAULT_PROJECT);
expect(body).toMatchObject({ features: [{ name: 'child' }] });
});
test('should not import archived child', async () => {
await createProjects();
await app.createFeature('child');
await app.archiveFeature('child');
await app.importToggles({
data: {
features: [{ name: 'child' }, { name: 'parent' }],
dependencies: [
{
feature: 'child',
dependencies: [
{
feature: 'parent',
},
],
},
],
featureStrategies: [],
featureEnvironments: [],
featureTags: [],
tagTypes: [],
contextFields: [],
segments: [],
},
project: DEFAULT_PROJECT,
environment: DEFAULT_ENV,
});
const { body } = await app.getProjectFeatures(DEFAULT_PROJECT);
expect(body).toMatchObject({ features: [{ name: 'parent' }] });
});
test(`should give errors with flag names if the flags don't match the project pattern`, async () => {
await db.stores.environmentStore.create({
name: DEFAULT_ENV,
type: 'production',
});
const pattern = 'testpattern.+';
for (const project of [DEFAULT_PROJECT]) {
await db.stores.projectStore.create({
name: project,
description: '',
id: project,
mode: 'open',
});
await db.stores.projectStore.updateProjectEnterpriseSettings({
id: project,
featureNaming: { pattern },
});
await app.linkProjectToEnvironment(project, DEFAULT_ENV);
}
const flagName = 'unusedfeaturenamethatdoesntmatchpattern';
const { body } = await app.importToggles({
...defaultImportPayload,
data: {
...defaultImportPayload.data,
features: [
{
project: 'old_project',
name: flagName,
type: 'release',
},
],
},
}, 400);
expect(body.message).toContain(pattern);
expect(body.message).toContain(flagName);
});
test('should import features from file', async () => {
await db.stores.environmentStore.create({
name: DEFAULT_ENV,
type: 'production',
});
await db.stores.projectStore.create({
name: DEFAULT_PROJECT,
description: '',
id: DEFAULT_PROJECT,
mode: 'open',
});
await app.linkProjectToEnvironment(DEFAULT_PROJECT, DEFAULT_ENV);
await app.services.importService.importFromFile('src/lib/features/export-import-toggles/import-data.json', DEFAULT_PROJECT, DEFAULT_ENV);
const { body: importedFeature } = await getFeature(defaultFeatureName);
expect(importedFeature).toMatchObject({
name: defaultFeatureName,
project: DEFAULT_PROJECT,
type: 'release',
variants,
});
});
//# sourceMappingURL=export-import.e2e.test.js.map