"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const random_id_1 = require("../../util/random-id");
const collect_ids_1 = require("../../util/collect-ids");
const database_init_1 = __importDefault(require("../../../test/e2e/helpers/database-init"));
const no_logger_1 = __importDefault(require("../../../test/fixtures/no-logger"));
const app_utils_1 = require("../../../test/e2e/helpers/app.utils");
const test_helper_1 = require("../../../test/e2e/helpers/test-helper");
let app;
let db;
const SEGMENTS_BASE_PATH = '/api/admin/segments';
const FEATURES_LIST_BASE_PATH = '/api/admin/features';
const fetchSegments = () => app.request
    .get(SEGMENTS_BASE_PATH)
    .expect(200)
    .then((res) => res.body.segments);
const fetchSegmentsByStrategy = (strategyId) => app.request
    .get(`${SEGMENTS_BASE_PATH}/strategies/${strategyId}`)
    .expect(200)
    .then((res) => res.body.segments);
const fetchFeatures = () => app.request
    .get(FEATURES_LIST_BASE_PATH)
    .expect(200)
    .then((res) => res.body.features);
const fetchSegmentStrategies = (segmentId) => app.request
    .get(`${SEGMENTS_BASE_PATH}/${segmentId}/strategies`)
    .expect(200)
    .then((res) => res.body);
const updateSegment = (id, postData, expectStatusCode = 204) => app.request
    .put(`${SEGMENTS_BASE_PATH}/${id}`)
    .send(postData)
    .expect(expectStatusCode);
const addSegmentsToStrategy = (segmentIds, strategyId, expectStatusCode = 201) => app.request
    .post(`${SEGMENTS_BASE_PATH}/strategies`)
    .set('Content-type', 'application/json')
    .send({
    strategyId,
    segmentIds,
    projectId: 'default',
    environmentId: 'default',
    additional: 'property',
})
    .expect(expectStatusCode);
const mockFeatureToggle = () => ({
    name: (0, random_id_1.randomId)(),
    strategies: [
        {
            name: 'flexibleRollout',
            constraints: [],
            parameters: {},
        },
    ],
});
const validateSegment = (postData, expectStatusCode = 204) => app.request
    .post(`${SEGMENTS_BASE_PATH}/validate`)
    .set('Content-type', 'application/json')
    .send(postData)
    .expect(expectStatusCode);
beforeAll(async () => {
    const customOptions = {
        experimental: {
            flags: {
                anonymiseEventLog: true,
            },
        },
    };
    db = await (0, database_init_1.default)('segments_api_serial', no_logger_1.default, customOptions);
    app = await (0, test_helper_1.setupAppWithCustomConfig)(db.stores, customOptions, db.rawDatabase);
});
afterAll(async () => {
    await app.destroy();
    await db.destroy();
});
afterEach(async () => {
    await db.stores.segmentStore.deleteAll();
    await db.stores.featureToggleStore.deleteAll();
});
test('should validate segments', async () => {
    await app.createSegment({ something: 'a' }, 400);
    await app.createSegment({
        name: (0, random_id_1.randomId)(),
        something: 'b',
    }, 400);
    await app.createSegment({
        name: (0, random_id_1.randomId)(),
        constraints: 'b',
    }, 400);
    await app.createSegment({ constraints: [] }, 400);
    await app.createSegment({
        name: (0, random_id_1.randomId)(),
        constraints: [{}],
    }, 400);
    await app.createSegment({
        name: (0, random_id_1.randomId)(),
        constraints: [],
    });
    await app.createSegment({
        name: (0, random_id_1.randomId)(),
        description: '',
        constraints: [],
    });
});
test('should fail on missing properties', async () => {
    const res = await app.request
        .post(`${SEGMENTS_BASE_PATH}/strategies`)
        .set('Content-type', 'application/json')
        .send({
        projectId: 'default',
        environmentId: 'default',
        additional: 'property',
    });
    expect(res.status).toBe(400);
});
test('should create segments', async () => {
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    await app.createSegment({
        name: 'c',
        constraints: [],
    });
    await app.createSegment({
        name: 'b',
        constraints: [],
    });
    const segments = await fetchSegments();
    expect(segments.map((s) => s.name)).toEqual(['a', 'b', 'c']);
});
test('should return 400 for non numeeric segment ID', async () => {
    const { body } = await app.request
        .get(`${SEGMENTS_BASE_PATH}/stringName`)
        .expect(400);
    expect(body).toMatchObject({
        message: 'Request validation failed: your request body or params contain invalid data: ID should be an integer',
    });
});
test('should update segments', async () => {
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    const [segmentA] = await fetchSegments();
    expect(segmentA.id).toBeGreaterThan(0);
    expect(segmentA.name).toEqual('a');
    expect(segmentA.createdAt).toBeDefined();
    expect(segmentA.constraints.length).toEqual(0);
    await updateSegment(segmentA.id, {
        ...segmentA,
        name: 'b',
    });
    const [segmentB] = await fetchSegments();
    expect(segmentB.id).toEqual(segmentA.id);
    expect(segmentB.name).toEqual('b');
    expect(segmentB.createdAt).toBeDefined();
    expect(segmentB.constraints.length).toEqual(0);
});
test('should update segment constraints', async () => {
    const constraintA = {
        contextName: 'a',
        operator: 'IN',
        values: ['x'],
    };
    const constraintB = {
        contextName: 'b',
        operator: 'IN',
        values: ['y'],
    };
    await app.createSegment({
        name: 'a',
        constraints: [constraintA],
    });
    const [segmentA] = await fetchSegments();
    expect(segmentA.constraints).toEqual([constraintA]);
    await app.request
        .put(`${SEGMENTS_BASE_PATH}/${segmentA.id}`)
        .send({
        ...segmentA,
        constraints: [constraintB, constraintA],
    })
        .expect(204);
    const [segmentB] = await fetchSegments();
    expect(segmentB.constraints).toEqual([constraintB, constraintA]);
});
test('should delete segments', async () => {
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    const segments = await fetchSegments();
    expect(segments.length).toEqual(1);
    await app.request
        .delete(`${SEGMENTS_BASE_PATH}/${segments[0].id}`)
        .expect(204);
    expect((await fetchSegments()).length).toEqual(0);
});
test('should not delete segments used by strategies', async () => {
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    const toggle = mockFeatureToggle();
    await (0, app_utils_1.createFeatureToggle)(app, toggle);
    const [segment] = await fetchSegments();
    await (0, app_utils_1.addStrategyToFeatureEnv)(app, { ...toggle.strategies[0] }, 'default', toggle.name);
    const [feature] = await fetchFeatures();
    //@ts-ignore
    await addSegmentsToStrategy([segment.id], feature.strategies[0].id);
    const segments = await fetchSegments();
    expect(segments.length).toEqual(1);
    await app.request
        .delete(`${SEGMENTS_BASE_PATH}/${segments[0].id}`)
        .expect(409);
    expect((await fetchSegments()).length).toEqual(1);
});
test('should delete segments used by strategies in archived feature toggles', async () => {
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    const toggle = mockFeatureToggle();
    await (0, app_utils_1.createFeatureToggle)(app, toggle);
    const [segment] = await fetchSegments();
    await (0, app_utils_1.addStrategyToFeatureEnv)(app, { ...toggle.strategies[0] }, 'default', toggle.name);
    const [feature] = await fetchFeatures();
    //@ts-ignore
    await addSegmentsToStrategy([segment.id], feature.strategies[0].id);
    const segments = await fetchSegments();
    expect(segments.length).toEqual(1);
    await app.archiveFeature(feature.name);
    await app.request
        .delete(`${SEGMENTS_BASE_PATH}/${segments[0].id}`)
        .expect(204);
    expect((await fetchSegments()).length).toEqual(0);
});
test('should list strategies by segment', async () => {
    await app.createSegment({
        name: 'S1',
        constraints: [],
    });
    await app.createSegment({
        name: 'S2',
        constraints: [],
    });
    await app.createSegment({
        name: 'S3',
        constraints: [],
    });
    const toggle1 = mockFeatureToggle();
    const toggle2 = mockFeatureToggle();
    const toggle3 = mockFeatureToggle();
    await (0, app_utils_1.createFeatureToggle)(app, toggle1);
    await (0, app_utils_1.createFeatureToggle)(app, toggle2);
    await (0, app_utils_1.createFeatureToggle)(app, toggle3);
    await (0, app_utils_1.addStrategyToFeatureEnv)(app, { ...toggle1.strategies[0] }, 'default', toggle1.name);
    await (0, app_utils_1.addStrategyToFeatureEnv)(app, { ...toggle1.strategies[0] }, 'default', toggle2.name);
    await (0, app_utils_1.addStrategyToFeatureEnv)(app, { ...toggle3.strategies[0] }, 'default', toggle3.name);
    const [feature1, feature2, feature3] = await fetchFeatures();
    const [segment1, segment2, segment3] = await fetchSegments();
    await addSegmentsToStrategy([segment1.id, segment2.id, segment3.id], 
    //@ts-ignore
    feature1.strategies[0].id);
    await addSegmentsToStrategy([segment2.id, segment3.id], 
    //@ts-ignore
    feature2.strategies[0].id);
    //@ts-ignore
    await addSegmentsToStrategy([segment3.id], feature3.strategies[0].id);
    const segmentStrategies1 = await fetchSegmentStrategies(segment1.id);
    const segmentStrategies2 = await fetchSegmentStrategies(segment2.id);
    const segmentStrategies3 = await fetchSegmentStrategies(segment3.id);
    expect((0, collect_ids_1.collectIds)(segmentStrategies1.strategies)).toEqual((0, collect_ids_1.collectIds)(feature1.strategies));
    expect((0, collect_ids_1.collectIds)(segmentStrategies2.strategies)).toEqual((0, collect_ids_1.collectIds)([...feature1.strategies, ...feature2.strategies]));
    expect((0, collect_ids_1.collectIds)(segmentStrategies3.strategies)).toEqual((0, collect_ids_1.collectIds)([
        ...feature1.strategies,
        ...feature2.strategies,
        ...feature3.strategies,
    ]));
});
test('should list segments by strategy', async () => {
    await app.createSegment({
        name: 'S1',
        constraints: [],
    });
    await app.createSegment({
        name: 'S2',
        constraints: [],
    });
    await app.createSegment({
        name: 'S3',
        constraints: [],
    });
    const toggle1 = mockFeatureToggle();
    const toggle2 = mockFeatureToggle();
    const toggle3 = mockFeatureToggle();
    await (0, app_utils_1.createFeatureToggle)(app, toggle1);
    await (0, app_utils_1.createFeatureToggle)(app, toggle2);
    await (0, app_utils_1.createFeatureToggle)(app, toggle3);
    await (0, app_utils_1.addStrategyToFeatureEnv)(app, { ...toggle1.strategies[0] }, 'default', toggle1.name);
    await (0, app_utils_1.addStrategyToFeatureEnv)(app, { ...toggle1.strategies[0] }, 'default', toggle2.name);
    await (0, app_utils_1.addStrategyToFeatureEnv)(app, { ...toggle3.strategies[0] }, 'default', toggle3.name);
    const [feature1, feature2, feature3] = await fetchFeatures();
    const [segment1, segment2, segment3] = await fetchSegments();
    await addSegmentsToStrategy([segment1.id, segment2.id, segment3.id], 
    //@ts-ignore
    feature1.strategies[0].id);
    await addSegmentsToStrategy([segment2.id, segment3.id], 
    //@ts-ignore
    feature2.strategies[0].id);
    //@ts-ignore
    await addSegmentsToStrategy([segment3.id], feature3.strategies[0].id);
    const strategySegments1 = await fetchSegmentsByStrategy(
    //@ts-ignore
    feature1.strategies[0].id);
    const strategySegments2 = await fetchSegmentsByStrategy(
    //@ts-ignore
    feature2.strategies[0].id);
    const strategySegments3 = await fetchSegmentsByStrategy(
    //@ts-ignore
    feature3.strategies[0].id);
    expect((0, collect_ids_1.collectIds)(strategySegments1)).toEqual((0, collect_ids_1.collectIds)([segment1, segment2, segment3]));
    expect((0, collect_ids_1.collectIds)(strategySegments2)).toEqual((0, collect_ids_1.collectIds)([segment2, segment3]));
    expect((0, collect_ids_1.collectIds)(strategySegments3)).toEqual((0, collect_ids_1.collectIds)([segment3]));
});
test('should reject duplicate segment names', async () => {
    await validateSegment({ name: 'a' });
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    await validateSegment({ name: 'a' }, 409);
    await validateSegment({ name: 'b' });
});
test('should reject empty segment names', async () => {
    await validateSegment({ name: 'a' });
    await validateSegment({}, 400);
    await validateSegment({ name: '' }, 400);
});
test('should reject duplicate segment names on create', async () => {
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    await app.createSegment({
        name: 'a',
        constraints: [],
    }, 409);
    await validateSegment({ name: 'b' });
});
test('should reject duplicate segment names on update', async () => {
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    await app.createSegment({
        name: 'b',
        constraints: [],
    });
    const [segmentA, segmentB] = await fetchSegments();
    await updateSegment(segmentA.id, {
        name: 'b',
        constraints: [],
    }, 409);
    await updateSegment(segmentB.id, {
        name: 'a',
        constraints: [],
    }, 409);
    await updateSegment(segmentA.id, {
        name: 'a',
        constraints: [],
    });
    await updateSegment(segmentA.id, {
        name: 'c',
        constraints: [],
    });
});
test('Should anonymise createdBy field if anonymiseEventLog flag is set', async () => {
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    await app.createSegment({
        name: 'b',
        constraints: [],
    });
    const segments = await fetchSegments();
    expect(segments).toHaveLength(2);
    expect(segments[0].createdBy).toContain('unleash.run');
});
test('Should show usage in features and projects', async () => {
    await app.createSegment({
        name: 'a',
        constraints: [],
    });
    const toggle = mockFeatureToggle();
    await (0, app_utils_1.createFeatureToggle)(app, toggle);
    const [segment] = await fetchSegments();
    await (0, app_utils_1.addStrategyToFeatureEnv)(app, { ...toggle.strategies[0] }, 'default', toggle.name);
    const [feature] = await fetchFeatures();
    //@ts-ignore
    await addSegmentsToStrategy([segment.id], feature.strategies[0].id);
    const segments = await fetchSegments();
    expect(segments).toMatchObject([
        {
            usedInFeatures: 1,
            usedInProjects: 1,
        },
    ]);
});
describe('detect strategy usage in change requests', () => {
    const CR_TITLE = 'My change request';
    const CR_ID = 54321;
    let user;
    // Change request data is only counted for enterprise
    // instances, so we'll instantiate our own version of the app
    // for that.
    let enterpriseApp;
    // likewise, we want to fetch from the right app to make sure
    // we get the right data
    const enterpriseFetchSegments = () => enterpriseApp.request
        .get(SEGMENTS_BASE_PATH)
        .expect(200)
        .then((res) => res.body.segments);
    const enterpriseFetchSegmentStrategies = (segmentId) => enterpriseApp.request
        .get(`${SEGMENTS_BASE_PATH}/${segmentId}/strategies`)
        .expect(200)
        .then((res) => res.body);
    beforeAll(async () => {
        enterpriseApp = await (0, test_helper_1.setupAppWithCustomConfig)(db.stores, {
            enterpriseVersion: '5.3.0',
            ui: { environment: 'Enterprise' },
            isEnterprise: true,
            experimental: {
                flags: {},
            },
        }, db.rawDatabase);
        user = await db.stores.userStore.insert({
            username: 'test',
        });
        await db.rawDatabase.table('change_requests').insert({
            id: CR_ID,
            environment: 'default',
            state: 'In review',
            project: 'default',
            created_by: user.id,
            created_at: '2023-01-01 00:00:00',
            min_approvals: 1,
            title: CR_TITLE,
        });
    });
    afterAll(async () => {
        await db.stores.userStore.delete(user.id);
        await db.rawDatabase.table('change_requests').delete();
    });
    afterEach(async () => {
        await db.rawDatabase.table('change_request_events').delete();
    });
    test('should not delete segments used by strategies in CRs', async () => {
        await app.createSegment({
            name: 'a',
            constraints: [],
        });
        const toggle = mockFeatureToggle();
        await (0, app_utils_1.createFeatureToggle)(enterpriseApp, toggle);
        const [segment] = await enterpriseFetchSegments();
        await db.rawDatabase.table('change_request_events').insert({
            feature: toggle.name,
            action: 'addStrategy',
            payload: {
                name: 'flexibleRollout',
                title: '',
                disabled: false,
                segments: [segment.id],
                variants: [],
                parameters: {
                    groupId: toggle.name,
                    rollout: '100',
                    stickiness: 'default',
                },
                constraints: [],
            },
            created_at: '2023-01-01 00:01:00',
            change_request_id: CR_ID,
            created_by: user.id,
        });
        expect((await enterpriseFetchSegments()).length).toEqual(1);
        await enterpriseApp.request
            .delete(`${SEGMENTS_BASE_PATH}/${segment.id}`)
            .expect(409);
        expect((await enterpriseFetchSegments()).length).toEqual(1);
        // check that it can be deleted in OSS
        await app.request
            .delete(`${SEGMENTS_BASE_PATH}/${segment.id}`)
            .expect(204);
    });
    test('Should show segment usage in addStrategy events', async () => {
        await app.createSegment({
            name: 'a',
            constraints: [],
        });
        const toggle = mockFeatureToggle();
        await (0, app_utils_1.createFeatureToggle)(enterpriseApp, toggle);
        const [segment] = await enterpriseFetchSegments();
        await db.rawDatabase.table('change_request_events').insert({
            feature: toggle.name,
            action: 'addStrategy',
            payload: {
                name: 'flexibleRollout',
                title: '',
                disabled: false,
                segments: [segment.id],
                variants: [],
                parameters: {
                    groupId: toggle.name,
                    rollout: '100',
                    stickiness: 'default',
                },
                constraints: [],
            },
            created_at: '2023-01-01 00:01:00',
            change_request_id: CR_ID,
            created_by: user.id,
        });
        const { strategies, changeRequestStrategies } = await enterpriseFetchSegmentStrategies(segment.id);
        expect(changeRequestStrategies).toMatchObject([
            {
                environment: 'default',
                featureName: toggle.name,
                projectId: 'default',
                strategyName: 'flexibleRollout',
                changeRequest: { id: CR_ID, title: CR_TITLE },
            },
        ]);
        expect(strategies).toStrictEqual([]);
        // check that OSS gets no CR strategies
        const ossResult = await fetchSegmentStrategies(segment.id);
        expect(ossResult.strategies).toStrictEqual([]);
        expect(ossResult.changeRequestStrategies ?? []).toStrictEqual([]);
    });
    test('Should show segment usage in updateStrategy events', async () => {
        await app.createSegment({
            name: 'a',
            constraints: [],
        });
        const toggle = mockFeatureToggle();
        await (0, app_utils_1.createFeatureToggle)(enterpriseApp, toggle);
        const [segment] = await enterpriseFetchSegments();
        await (0, app_utils_1.addStrategyToFeatureEnv)(enterpriseApp, { ...toggle.strategies[0] }, 'default', toggle.name);
        const [feature] = await fetchFeatures();
        const strategyId = feature.strategies[0].id;
        await db.rawDatabase.table('change_request_events').insert({
            feature: toggle.name,
            action: 'updateStrategy',
            payload: {
                id: strategyId,
                name: 'flexibleRollout',
                title: '',
                disabled: false,
                segments: [segment.id],
                variants: [],
                parameters: {
                    groupId: toggle.name,
                    rollout: '100',
                    stickiness: 'default',
                },
                constraints: [],
            },
            created_at: '2023-01-01 00:01:00',
            change_request_id: CR_ID,
            created_by: user.id,
        });
        const { strategies, changeRequestStrategies } = await enterpriseFetchSegmentStrategies(segment.id);
        expect(changeRequestStrategies).toMatchObject([
            {
                id: strategyId,
                changeRequest: { id: CR_ID, title: CR_TITLE },
            },
        ]);
        expect(strategies).toStrictEqual([]);
        // check that OSS gets no CR strategies
        const ossResult = await fetchSegmentStrategies(segment.id);
        expect(ossResult.strategies).toStrictEqual([]);
        expect(ossResult.changeRequestStrategies ?? []).toStrictEqual([]);
    });
    test('If a segment is used in an existing strategy and in a CR for the same strategy, the strategy should be listed both places', async () => {
        await app.createSegment({
            name: 'a',
            constraints: [],
        });
        const toggle = mockFeatureToggle();
        await (0, app_utils_1.createFeatureToggle)(enterpriseApp, toggle);
        const [segment] = await enterpriseFetchSegments();
        await (0, app_utils_1.addStrategyToFeatureEnv)(enterpriseApp, { ...toggle.strategies[0] }, 'default', toggle.name);
        const [feature] = await fetchFeatures();
        const strategyId = feature.strategies[0].id;
        await addSegmentsToStrategy([segment.id], strategyId);
        await db.rawDatabase.table('change_request_events').insert({
            feature: toggle.name,
            action: 'updateStrategy',
            payload: {
                id: strategyId,
                name: 'flexibleRollout',
                title: '',
                disabled: false,
                segments: [segment.id],
                variants: [],
                parameters: {
                    groupId: toggle.name,
                    rollout: '100',
                    stickiness: 'default',
                },
                constraints: [],
            },
            created_at: '2023-01-01 00:01:00',
            change_request_id: CR_ID,
            created_by: user.id,
        });
        const { strategies, changeRequestStrategies } = await enterpriseFetchSegmentStrategies(segment.id);
        expect(strategies).toMatchObject([{ id: strategyId }]);
        expect(changeRequestStrategies).toMatchObject([{ id: strategyId }]);
        // check that OSS gets no CR strategies
        const ossResult = await fetchSegmentStrategies(segment.id);
        expect(ossResult.strategies).toMatchObject([{ id: strategyId }]);
        expect(ossResult.changeRequestStrategies ?? []).toStrictEqual([]);
    });
    test('Should show usage in features and projects in CRs', async () => {
        // because they use the same db, we can use the regular app
        // (through `createSegment` and `createFeatureToggle`) to
        // create the segment and the flag
        await app.createSegment({ name: 'a', constraints: [] });
        const toggle = mockFeatureToggle();
        await (0, app_utils_1.createFeatureToggle)(app, toggle);
        const [segment] = await enterpriseFetchSegments();
        expect(segment).toMatchObject({ usedInFeatures: 0, usedInProjects: 0 });
        await db.rawDatabase.table('change_request_events').insert({
            feature: toggle.name,
            action: 'addStrategy',
            payload: {
                name: 'flexibleRollout',
                title: '',
                disabled: false,
                segments: [segment.id],
                variants: [],
                parameters: {
                    groupId: toggle.name,
                    rollout: '100',
                    stickiness: 'default',
                },
                constraints: [],
            },
            created_at: '2023-01-01 00:01:00',
            change_request_id: CR_ID,
            created_by: user.id,
        });
        const segments = await enterpriseFetchSegments();
        expect(segments).toMatchObject([
            { usedInFeatures: 1, usedInProjects: 1 },
        ]);
        // check that OSS gets no CR usage
        const ossSegments = await fetchSegments();
        expect(ossSegments).toMatchObject([
            { usedInFeatures: 0, usedInProjects: 0 },
        ]);
    });
});
//# sourceMappingURL=admin-segment.e2e.test.js.map