UNPKG

unleash-server

Version:

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

239 lines • 11.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const database_init_1 = __importDefault(require("../../helpers/database-init")); const no_logger_1 = __importDefault(require("../../../fixtures/no-logger")); const test_helper_1 = require("../../helpers/test-helper"); const random_id_1 = require("../../../../lib/util/random-id"); const user_1 = __importDefault(require("../../../../lib/types/user")); const segments_1 = require("../../../../lib/util/segments"); const collect_ids_1 = require("../../../../lib/util/collect-ids"); const arraysHaveSameItems_1 = require("../../../../lib/util/arraysHaveSameItems"); let db; let app; const FEATURES_ADMIN_BASE_PATH = '/api/admin/features'; const FEATURES_CLIENT_BASE_PATH = '/api/client/features'; const fetchSegments = () => { return app.services.segmentService.getAll(); }; const fetchFeatures = () => { return app.request .get(FEATURES_ADMIN_BASE_PATH) .expect(200) .then((res) => res.body.features); }; const fetchClientFeatures = () => { return app.request .get(FEATURES_CLIENT_BASE_PATH) .expect(200) .then((res) => res.body.features); }; const createSegment = (postData) => { return app.services.segmentService.create(postData, { email: 'test@example.com', }); }; const createFeatureToggle = (postData, expectStatusCode = 201) => { return app.request .post(FEATURES_ADMIN_BASE_PATH) .send(postData) .expect(expectStatusCode); }; const addSegmentToStrategy = (segmentId, strategyId) => { return app.services.segmentService.addToStrategy(segmentId, strategyId); }; const mockFeatureToggle = () => { return { name: (0, random_id_1.randomId)(), strategies: [{ name: (0, random_id_1.randomId)(), constraints: [], parameters: {} }], }; }; const mockConstraints = () => { return Array.from({ length: 5 }).map(() => ({ values: ['x', 'y', 'z'], operator: 'IN', contextName: 'a', })); }; const mockConstraintValues = (length) => { return Array.from({ length }).map(() => { return (0, random_id_1.randomId)(); }); }; const fetchClientResponse = () => { return app.request .get(FEATURES_CLIENT_BASE_PATH) .set('Unleash-Client-Spec', '4.2.0') .expect(200) .then((res) => res.body); }; const createTestSegments = async () => { const constraints = mockConstraints(); await createSegment({ name: 'S1', constraints }); await createSegment({ name: 'S2', constraints }); await createSegment({ name: 'S3', constraints }); await createFeatureToggle(mockFeatureToggle()); await createFeatureToggle(mockFeatureToggle()); await createFeatureToggle(mockFeatureToggle()); const [feature1, feature2] = await fetchFeatures(); const [segment1, segment2] = await fetchSegments(); await addSegmentToStrategy(segment1.id, feature1.strategies[0].id); await addSegmentToStrategy(segment2.id, feature1.strategies[0].id); await addSegmentToStrategy(segment2.id, feature2.strategies[0].id); }; beforeAll(async () => { db = await (0, database_init_1.default)('segments', no_logger_1.default); app = await (0, test_helper_1.setupApp)(db.stores); }); afterAll(async () => { await app.destroy(); await db.destroy(); }); afterEach(async () => { await db.stores.segmentStore.deleteAll(); await db.stores.featureToggleStore.deleteAll(); await db.stores.eventStore.deleteAll(); }); test('should validate segment constraint values limit', async () => { const constraints = [ { contextName: (0, random_id_1.randomId)(), operator: 'IN', values: mockConstraintValues(segments_1.DEFAULT_SEGMENT_VALUES_LIMIT + 1), }, ]; await expect(createSegment({ name: (0, random_id_1.randomId)(), constraints })).rejects.toThrow(`Segments may not have more than ${segments_1.DEFAULT_SEGMENT_VALUES_LIMIT} values`); }); test('should validate segment constraint values limit with multiple constraints', async () => { const constraints = [ { contextName: (0, random_id_1.randomId)(), operator: 'IN', values: mockConstraintValues(segments_1.DEFAULT_SEGMENT_VALUES_LIMIT), }, { contextName: (0, random_id_1.randomId)(), operator: 'IN', values: mockConstraintValues(1), }, ]; await expect(createSegment({ name: (0, random_id_1.randomId)(), constraints })).rejects.toThrow(`Segments may not have more than ${segments_1.DEFAULT_SEGMENT_VALUES_LIMIT} values`); }); test('should validate feature strategy segment limit', async () => { await createSegment({ name: 'S1', constraints: [] }); await createSegment({ name: 'S2', constraints: [] }); await createSegment({ name: 'S3', constraints: [] }); await createSegment({ name: 'S4', constraints: [] }); await createSegment({ name: 'S5', constraints: [] }); await createSegment({ name: 'S6', constraints: [] }); await createFeatureToggle(mockFeatureToggle()); const [feature1] = await fetchFeatures(); const segments = await fetchSegments(); await addSegmentToStrategy(segments[0].id, feature1.strategies[0].id); await addSegmentToStrategy(segments[1].id, feature1.strategies[0].id); await addSegmentToStrategy(segments[2].id, feature1.strategies[0].id); await addSegmentToStrategy(segments[3].id, feature1.strategies[0].id); await addSegmentToStrategy(segments[4].id, feature1.strategies[0].id); await expect(addSegmentToStrategy(segments[5].id, feature1.strategies[0].id)).rejects.toThrow(`Strategies may not have more than ${segments_1.DEFAULT_STRATEGY_SEGMENTS_LIMIT} segments`); }); test('should clone feature strategy segments', async () => { const constraints = mockConstraints(); await createSegment({ name: 'S1', constraints }); await createFeatureToggle(mockFeatureToggle()); await createFeatureToggle(mockFeatureToggle()); const [feature1, feature2] = await fetchFeatures(); const strategy1 = feature1.strategies[0].id; const strategy2 = feature2.strategies[0].id; const [segment1] = await fetchSegments(); await addSegmentToStrategy(segment1.id, feature1.strategies[0].id); let segments1 = await app.services.segmentService.getByStrategy(strategy1); let segments2 = await app.services.segmentService.getByStrategy(strategy2); expect((0, collect_ids_1.collectIds)(segments1)).toEqual([segment1.id]); expect((0, collect_ids_1.collectIds)(segments2)).toEqual([]); await app.services.segmentService.cloneStrategySegments(strategy1, strategy2); segments1 = await app.services.segmentService.getByStrategy(strategy1); segments2 = await app.services.segmentService.getByStrategy(strategy2); expect((0, collect_ids_1.collectIds)(segments1)).toEqual([segment1.id]); expect((0, collect_ids_1.collectIds)(segments2)).toEqual([segment1.id]); }); test('should store segment-created and segment-deleted events', async () => { const constraints = mockConstraints(); const user = new user_1.default({ id: 1, email: 'test@example.com' }); await createSegment({ name: 'S1', constraints }); const [segment1] = await fetchSegments(); await app.services.segmentService.delete(segment1.id, user); const events = await db.stores.eventStore.getEvents(); expect(events[0].type).toEqual('segment-deleted'); expect(events[0].data.id).toEqual(segment1.id); expect(events[1].type).toEqual('segment-created'); expect(events[1].data.id).toEqual(segment1.id); }); test('should inline segment constraints into features by default', async () => { await createTestSegments(); const [feature1, feature2, feature3] = await fetchFeatures(); const [, , segment3] = await fetchSegments(); await addSegmentToStrategy(segment3.id, feature1.strategies[0].id); await addSegmentToStrategy(segment3.id, feature2.strategies[0].id); await addSegmentToStrategy(segment3.id, feature3.strategies[0].id); const clientFeatures = await fetchClientFeatures(); const clientStrategies = clientFeatures.flatMap((f) => f.strategies); const clientConstraints = clientStrategies.flatMap((s) => s.constraints); const clientValues = clientConstraints.flatMap((c) => c.values); const uniqueValues = [...new Set(clientValues)]; expect(clientFeatures.length).toEqual(3); expect(clientStrategies.length).toEqual(3); expect(clientConstraints.length).toEqual(5 * 6); expect(clientValues.length).toEqual(5 * 6 * 3); expect(uniqueValues.length).toEqual(3); }); test('should only return segments to clients that support the spec', async () => { await createTestSegments(); const [segment1, segment2] = await fetchSegments(); const segmentIds = (0, collect_ids_1.collectIds)([segment1, segment2]); const unknownClientResponse = await app.request .get(FEATURES_CLIENT_BASE_PATH) .expect(200) .then((res) => res.body); const unknownClientConstraints = unknownClientResponse.features .flatMap((f) => f.strategies) .flatMap((s) => s.constraints); expect(unknownClientResponse.segments).toEqual(undefined); expect(unknownClientConstraints.length).toEqual(15); const supportedClientResponse = await app.request .get(FEATURES_CLIENT_BASE_PATH) .set('Unleash-Client-Spec', '4.2.0') .expect(200) .then((res) => res.body); const supportedClientConstraints = supportedClientResponse.features .flatMap((f) => f.strategies) .flatMap((s) => s.constraints); expect((0, collect_ids_1.collectIds)(supportedClientResponse.segments)).toEqual(segmentIds); expect(supportedClientConstraints.length).toEqual(0); }); test('should return segments in base of toggle response if inline is disabled', async () => { await createTestSegments(); const clientFeatures = await fetchClientResponse(); expect(clientFeatures.segments.length).toBeDefined(); }); test('should only send segments that are in use', async () => { await createTestSegments(); const clientFeatures = await fetchClientResponse(); expect(clientFeatures.segments.length).toEqual(2); }); test('should send all segments that are in use by feature', async () => { await createTestSegments(); const clientFeatures = await fetchClientResponse(); const globalSegments = clientFeatures.segments; expect(globalSegments).toHaveLength(2); const globalSegmentIds = globalSegments.map((segment) => segment.id); const allSegmentIds = clientFeatures.features .map((feat) => feat.strategies.map((strategy) => strategy.segments)) .flat() .flat() .filter((x) => !!x); const toggleSegmentIds = [...new Set(allSegmentIds)]; expect((0, arraysHaveSameItems_1.arraysHaveSameItems)(globalSegmentIds, toggleSegmentIds)).toEqual(true); }); //# sourceMappingURL=segment.e2e.test.js.map