UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

278 lines • 10.1 kB
import { setupAppWithCustomConfig, } from '../../helpers/test-helper.js'; import dbInit from '../../helpers/database-init.js'; import getLogger from '../../../fixtures/no-logger.js'; import { DEFAULT_ENV } from '../../../../lib/util/constants.js'; import { SYSTEM_USER_AUDIT, TEST_AUDIT_USER, } from '../../../../lib/types/index.js'; let app; let db; const testUser = { name: 'test', id: -9999 }; beforeAll(async () => { db = await dbInit('feature_api_client', getLogger); app = await setupAppWithCustomConfig(db.stores, { experimental: { flags: { strictSchemaValidation: true, }, }, }, db.rawDatabase); await app.services.featureToggleService.createFeatureToggle('default', { name: 'featureX', description: 'the #1 feature', impressionData: true, }, TEST_AUDIT_USER); await app.services.featureToggleService.createFeatureToggle('default', { name: 'featureY', description: 'soon to be the #1 feature', }, TEST_AUDIT_USER); await app.services.featureToggleService.createFeatureToggle('default', { name: 'featureZ', description: 'terrible feature', }, TEST_AUDIT_USER); await app.services.featureToggleService.createFeatureToggle('default', { name: 'featureArchivedX', description: 'the #1 feature', }, TEST_AUDIT_USER); // depend on enabled feature with variant await app.services.dependentFeaturesService.unprotectedUpsertFeatureDependency({ child: 'featureY', projectId: 'default' }, { feature: 'featureX', variants: ['featureXVariant'] }, TEST_AUDIT_USER); await app.services.featureToggleService.archiveToggle('featureArchivedX', testUser, TEST_AUDIT_USER); await app.services.featureToggleService.createFeatureToggle('default', { name: 'featureArchivedY', description: 'soon to be the #1 feature', }, TEST_AUDIT_USER); await app.services.featureToggleService.archiveToggle('featureArchivedY', testUser, TEST_AUDIT_USER); await app.services.featureToggleService.createFeatureToggle('default', { name: 'featureArchivedZ', description: 'terrible feature', }, TEST_AUDIT_USER); await app.services.featureToggleService.archiveToggle('featureArchivedZ', testUser, TEST_AUDIT_USER); await app.services.featureToggleService.createFeatureToggle('default', { name: 'feature.with.variants', description: 'A feature flag with variants', }, TEST_AUDIT_USER); await app.services.featureToggleService.saveVariants('feature.with.variants', 'default', [ { name: 'control', weight: 50, weightType: 'fix', stickiness: 'default', }, { name: 'new', weight: 50, weightType: 'variable', stickiness: 'default', }, ], TEST_AUDIT_USER); }); afterAll(async () => { await app.destroy(); await db.destroy(); }); test('returns four feature flags', async () => { return app.request .get('/api/client/features') .expect('Content-Type', /json/) .expect(200) .expect((res) => { expect(res.body.features).toHaveLength(4); }); }); test('returns dependencies', async () => { return app.request .get('/api/client/features') .expect('Content-Type', /json/) .expect(200) .expect((res) => { expect(res.body.features[0]).toMatchObject({ name: 'featureY', dependencies: [ { feature: 'featureX', enabled: true, variants: ['featureXVariant'], }, ], }); expect(res.body.features[1].dependencies).toBe(undefined); }); }); test('returns four feature flags without createdAt', async () => { return app.request .get('/api/client/features') .expect('Content-Type', /json/) .expect(200) .expect((res) => { expect(res.body.features).toHaveLength(4); expect(res.body.features[0].createdAt).toBeFalsy(); }); }); test('gets a feature by name', async () => { return app.request .get('/api/client/features/featureX') .expect('Content-Type', /json/) .expect(200); }); test('returns a feature flags impression data', async () => { return app.request .get('/api/client/features/featureX') .expect('Content-Type', /json/) .expect((res) => { expect(res.body.impressionData).toBe(true); }); }); test('returns a false for impression data when not specified', async () => { return app.request .get('/api/client/features/featureZ') .expect('Content-Type', /json/) .expect((res) => { expect(res.body.impressionData).toBe(false); }); }); test('cant get feature that does not exist', async () => { return app.request .get('/api/client/features/myfeature') .expect('Content-Type', /json/) .expect(404); }); test('Can filter features by namePrefix', async () => { return app.request .get('/api/client/features?namePrefix=feature.') .expect('Content-Type', /json/) .expect(200) .expect((res) => { expect(res.body.features).toHaveLength(1); expect(res.body.features[0].name).toBe('feature.with.variants'); }); }); test('Can get strategies for specific environment', async () => { const featureName = 'test.feature.with.env'; const env = DEFAULT_ENV; // Create feature flag await app.request.post('/api/admin/projects/default/features').send({ name: featureName, type: 'kill-switch', }); // Add global strategy await app.request .post(`/api/admin/projects/default/features/${featureName}/environments/${env}/strategies`) .send({ name: 'default', }) .expect(200); // create new env await db.stores.environmentStore.create({ name: 'testing', type: 'test', }); await app.services.environmentService.addEnvironmentToProject('testing', 'default', SYSTEM_USER_AUDIT); await app.request .post(`/api/admin/projects/default/features/${featureName}/environments/testing/strategies`) .send({ name: 'default', }) .expect(200); await app.request .get(`/api/client/features/${featureName}?environment=testing`) .expect('Content-Type', /json/) .expect(200) .expect((res) => { expect(res.body.name).toBe(featureName); expect(res.body.strategies).toHaveLength(1); expect(res.body.strategies.find((s) => s.name === 'default')).toBeDefined(); }); }); test('Can use multiple filters', async () => { expect.assertions(3); await app.request.post('/api/admin/projects/default/features').send({ name: 'test.feature', type: 'kill-switch', enabled: true, strategies: [{ name: 'default' }], }); await app.request.post('/api/admin/projects/default/features').send({ name: 'test.feature2', type: 'kill-switch', enabled: true, strategies: [{ name: 'default' }], }); await app.request.post('/api/admin/projects/default/features').send({ name: 'notestprefix.feature3', type: 'release', enabled: true, strategies: [{ name: 'default' }], }); const tag = { value: 'Crazy', type: 'simple' }; const tag2 = { value: 'tagb', type: 'simple' }; await app.request .post('/api/admin/features/test.feature/tags') .send(tag) .expect(201); await app.request .post('/api/admin/features/test.feature2/tags') .send(tag2) .expect(201); await app.request .post('/api/admin/features/notestprefix.feature3/tags') .send(tag) .expect(201); await app.request .get('/api/client/features?tag=simple:Crazy') .expect('Content-Type', /json/) .expect(200) .expect((res) => expect(res.body.features).toHaveLength(2)); await app.request .get('/api/client/features?namePrefix=test&tag=simple:Crazy') .expect('Content-Type', /json/) .expect(200) .expect((res) => { expect(res.body.features).toHaveLength(1); expect(res.body.features[0].name).toBe('test.feature'); }); }); test('returns a feature flags impression data for a different project', async () => { const project = { id: 'impression-data-client', name: 'ImpressionData', description: '', mode: 'open', }; await db.stores.projectStore.create(project); const flag = { name: 'project-client.impression.data', impressionData: true, }; await app.request .post('/api/admin/projects/impression-data-client/features') .send(flag) .expect(201) .expect((res) => { expect(res.body.impressionData).toBe(true); }); return app.request .get('/api/client/features') .expect('Content-Type', /json/) .expect((res) => { const projectFlag = res.body.features.find((resFlag) => resFlag.project === project.id); expect(projectFlag.name).toBe(flag.name); expect(projectFlag.project).toBe(project.id); expect(projectFlag.impressionData).toBe(true); }); }); test('Can add tags while creating feature flag', async () => { const featureName = 'test.feature.with.tagss'; const tags = [{ value: 'tag1', type: 'simple' }]; await app.request.post('/api/admin/tags').send(tags[0]); await app.request.post('/api/admin/projects/default/features').send({ name: featureName, type: 'kill-switch', tags, }); const { body } = await app.request .get(`/api/admin/features/${featureName}/tags`) .expect('Content-Type', /json/) .expect(200); expect(body).toMatchObject({ tags, }); }); //# sourceMappingURL=feature.e2e.test.js.map