1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-18 11:14:57 +02:00
unleash.unleash/src/test/e2e/api/client/segment.e2e.test.ts
olav 7e3f0329ab
refactor: avoid inlining segments for supported clients (#1640)
* refactor: add semver lib types

* refactor: avoid inlining segments for supported clients

* refactor: fix FeatureController tests

* refactor: use spec version instead of client version

* refactor: improve header validation errors
2022-06-02 14:07:46 +02:00

228 lines
8.0 KiB
TypeScript

import dbInit, { ITestDb } from '../../helpers/database-init';
import getLogger from '../../../fixtures/no-logger';
import { IUnleashTest, setupApp } from '../../helpers/test-helper';
import {
IConstraint,
IFeatureToggleClient,
ISegment,
} from '../../../../lib/types/model';
import { randomId } from '../../../../lib/util/random-id';
import User from '../../../../lib/types/user';
import {
DEFAULT_SEGMENT_VALUES_LIMIT,
DEFAULT_STRATEGY_SEGMENTS_LIMIT,
} from '../../../../lib/util/segments';
import { collectIds } from '../../../../lib/util/collect-ids';
let db: ITestDb;
let app: IUnleashTest;
const FEATURES_ADMIN_BASE_PATH = '/api/admin/features';
const FEATURES_CLIENT_BASE_PATH = '/api/client/features';
const fetchSegments = (): Promise<ISegment[]> => {
return app.services.segmentService.getAll();
};
const fetchFeatures = (): Promise<IFeatureToggleClient[]> => {
return app.request
.get(FEATURES_ADMIN_BASE_PATH)
.expect(200)
.then((res) => res.body.features);
};
const fetchClientFeatures = (): Promise<IFeatureToggleClient[]> => {
return app.request
.get(FEATURES_CLIENT_BASE_PATH)
.expect(200)
.then((res) => res.body.features);
};
const createSegment = (postData: object): Promise<unknown> => {
const user = { email: 'test@example.com' } as User;
return app.services.segmentService.create(postData, user);
};
const createFeatureToggle = (
postData: object,
expectStatusCode = 201,
): Promise<unknown> => {
return app.request
.post(FEATURES_ADMIN_BASE_PATH)
.send(postData)
.expect(expectStatusCode);
};
const addSegmentToStrategy = (
segmentId: number,
strategyId: string,
): Promise<unknown> => {
return app.services.segmentService.addToStrategy(segmentId, strategyId);
};
const mockFeatureToggle = (): object => {
return {
name: randomId(),
strategies: [{ name: randomId(), constraints: [], parameters: {} }],
};
};
const mockConstraints = (): IConstraint[] => {
return Array.from({ length: 5 }).map(() => ({
values: ['x', 'y', 'z'],
operator: 'IN',
contextName: 'a',
}));
};
const mockConstraintValues = (length: number): string[] => {
return Array.from({ length }).map(() => {
return randomId();
});
};
beforeAll(async () => {
db = await dbInit('segments', getLogger);
app = await setupApp(db.stores);
});
afterAll(async () => {
await app.destroy();
await db.destroy();
});
afterEach(async () => {
await db.stores.segmentStore.deleteAll();
await db.stores.featureToggleStore.deleteAll();
});
test('should inline segment constraints into features by default', 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, feature3] = await fetchFeatures();
const [segment1, segment2, segment3] = 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);
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 validate segment constraint values limit', async () => {
const constraints: IConstraint[] = [
{
contextName: randomId(),
operator: 'IN',
values: mockConstraintValues(DEFAULT_SEGMENT_VALUES_LIMIT + 1),
},
];
await expect(
createSegment({ name: randomId(), constraints }),
).rejects.toThrow(
`Segments may not have more than ${DEFAULT_SEGMENT_VALUES_LIMIT} values`,
);
});
test('should validate segment constraint values limit with multiple constraints', async () => {
const constraints: IConstraint[] = [
{
contextName: randomId(),
operator: 'IN',
values: mockConstraintValues(DEFAULT_SEGMENT_VALUES_LIMIT),
},
{
contextName: randomId(),
operator: 'IN',
values: mockConstraintValues(1),
},
];
await expect(
createSegment({ name: randomId(), constraints }),
).rejects.toThrow(
`Segments may not have more than ${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 ${DEFAULT_STRATEGY_SEGMENTS_LIMIT} segments`,
);
});
test('should only return segments to clients with the segments capability', 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();
const segmentIds = collectIds([segment1, segment2]);
await addSegmentToStrategy(segment1.id, feature1.strategies[0].id);
await addSegmentToStrategy(segment2.id, feature1.strategies[0].id);
await addSegmentToStrategy(segment2.id, feature2.strategies[0].id);
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(collectIds(supportedClientResponse.segments)).toEqual(segmentIds);
expect(supportedClientConstraints.length).toEqual(0);
});