From 56892c54d948ab67892ac38ecc9d9cfb01bd90e2 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Thu, 19 Oct 2023 11:11:05 +0200 Subject: [PATCH] feat: check if child and parent are in the same project (#5093) --- .../dependent-features-service.ts | 9 ++++++- .../dependent.features.e2e.test.ts | 27 +++++++++++++++++++ .../fakes/fake-features-read-model.ts | 7 +++++ .../feature-toggle/features-read-model.ts | 10 +++++++ .../types/features-read-model-type.ts | 4 +++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/lib/features/dependent-features/dependent-features-service.ts b/src/lib/features/dependent-features/dependent-features-service.ts index 318b7740e4..7cc740945f 100644 --- a/src/lib/features/dependent-features/dependent-features-service.ts +++ b/src/lib/features/dependent-features/dependent-features-service.ts @@ -96,9 +96,10 @@ export class DependentFeaturesService { ); } - const [children, parentExists] = await Promise.all([ + const [children, parentExists, sameProject] = await Promise.all([ this.dependentFeaturesReadModel.getChildren([child]), this.featuresReadModel.featureExists(parent), + this.featuresReadModel.featuresInTheSameProject(child, parent), ]); if (children.length > 0) { @@ -113,6 +114,12 @@ export class DependentFeaturesService { ); } + if (!sameProject) { + throw new InvalidOperationError( + 'Parent and child features should be in the same project', + ); + } + const featureDependency: FeatureDependency = enabled === false ? { diff --git a/src/lib/features/dependent-features/dependent.features.e2e.test.ts b/src/lib/features/dependent-features/dependent.features.e2e.test.ts index 6e135b758e..7aeb78867f 100644 --- a/src/lib/features/dependent-features/dependent.features.e2e.test.ts +++ b/src/lib/features/dependent-features/dependent.features.e2e.test.ts @@ -11,7 +11,9 @@ import { FEATURE_DEPENDENCY_ADDED, FEATURE_DEPENDENCY_REMOVED, IEventStore, + IUser, } from '../../types'; +import { ProjectService } from '../../services'; let app: IUnleashTest; let db: ITestDb; @@ -34,6 +36,15 @@ beforeAll(async () => { eventStore = db.stores.eventStore; }); +const createProject = async (name: string) => { + await db.stores.projectStore.create({ + name: name, + description: '', + id: name, + mode: 'open' as const, + }); +}; + const getRecordedEventTypesForDependencies = async () => (await eventStore.getEvents()) .map((event) => event.type) @@ -207,3 +218,19 @@ test('should not allow to add dependency to self', async () => { 403, ); }); + +test('should not allow to add dependency to feature from another project', async () => { + const child = uuidv4(); + const parent = uuidv4(); + await app.createFeature(parent); + await createProject('another-project'); + await app.createFeature(child, 'another-project'); + + await addFeatureDependency( + child, + { + feature: parent, + }, + 403, + ); +}); diff --git a/src/lib/features/feature-toggle/fakes/fake-features-read-model.ts b/src/lib/features/feature-toggle/fakes/fake-features-read-model.ts index 3aa8943a41..5667f530b0 100644 --- a/src/lib/features/feature-toggle/fakes/fake-features-read-model.ts +++ b/src/lib/features/feature-toggle/fakes/fake-features-read-model.ts @@ -4,4 +4,11 @@ export class FakeFeaturesReadModel implements IFeaturesReadModel { featureExists(): Promise { return Promise.resolve(false); } + + featuresInTheSameProject( + featureA: string, + featureB: string, + ): Promise { + return Promise.resolve(true); + } } diff --git a/src/lib/features/feature-toggle/features-read-model.ts b/src/lib/features/feature-toggle/features-read-model.ts index 6007f60e70..32c1150d68 100644 --- a/src/lib/features/feature-toggle/features-read-model.ts +++ b/src/lib/features/feature-toggle/features-read-model.ts @@ -16,4 +16,14 @@ export class FeaturesReadModel implements IFeaturesReadModel { return rows.length > 0; } + + async featuresInTheSameProject( + featureA: string, + featureB: string, + ): Promise { + const rows = await this.db('features') + .countDistinct('project as count') + .whereIn('name', [featureA, featureB]); + return Number(rows[0].count) === 1; + } } diff --git a/src/lib/features/feature-toggle/types/features-read-model-type.ts b/src/lib/features/feature-toggle/types/features-read-model-type.ts index 2455c4ed30..98b3b854ba 100644 --- a/src/lib/features/feature-toggle/types/features-read-model-type.ts +++ b/src/lib/features/feature-toggle/types/features-read-model-type.ts @@ -1,3 +1,7 @@ export interface IFeaturesReadModel { featureExists(parent: string): Promise; + featuresInTheSameProject( + featureA: string, + featureB: string, + ): Promise; }