1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

feat: feature admin API returns dependencies and children (#4848)

This commit is contained in:
Mateusz Kwasniewski 2023-09-27 15:07:20 +02:00 committed by GitHub
parent fd8775f13d
commit 87a81120d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 190 additions and 17 deletions

View File

@ -1,5 +1,7 @@
import { IDependency } from '../../types';
export interface IDependentFeaturesReadModel { export interface IDependentFeaturesReadModel {
getChildren(parent: string): Promise<string[]>; getChildren(parent: string): Promise<string[]>;
getParents(child: string): Promise<string[]>; getParents(child: string): Promise<IDependency[]>;
getParentOptions(child: string): Promise<string[]>; getParentOptions(child: string): Promise<string[]>;
} }

View File

@ -1,5 +1,6 @@
import { Db } from '../../db/db'; import { Db } from '../../db/db';
import { IDependentFeaturesReadModel } from './dependent-features-read-model-type'; import { IDependentFeaturesReadModel } from './dependent-features-read-model-type';
import { IDependency } from '../../types';
export class DependentFeaturesReadModel implements IDependentFeaturesReadModel { export class DependentFeaturesReadModel implements IDependentFeaturesReadModel {
private db: Db; private db: Db;
@ -17,10 +18,14 @@ export class DependentFeaturesReadModel implements IDependentFeaturesReadModel {
return rows.map((row) => row.child); return rows.map((row) => row.child);
} }
async getParents(child: string): Promise<string[]> { async getParents(child: string): Promise<IDependency[]> {
const rows = await this.db('dependent_features').where('child', child); const rows = await this.db('dependent_features').where('child', child);
return rows.map((row) => row.parent); return rows.map((row) => ({
feature: row.parent,
enabled: row.enabled,
variants: row.variants,
}));
} }
async getParentOptions(child: string): Promise<string[]> { async getParentOptions(child: string): Promise<string[]> {

View File

@ -1,4 +1,5 @@
import { IDependentFeaturesReadModel } from './dependent-features-read-model-type'; import { IDependentFeaturesReadModel } from './dependent-features-read-model-type';
import { IDependency } from '../../types';
export class FakeDependentFeaturesReadModel export class FakeDependentFeaturesReadModel
implements IDependentFeaturesReadModel implements IDependentFeaturesReadModel
@ -7,7 +8,7 @@ export class FakeDependentFeaturesReadModel
return Promise.resolve([]); return Promise.resolve([]);
} }
getParents(): Promise<string[]> { getParents(): Promise<IDependency[]> {
return Promise.resolve([]); return Promise.resolve([]);
} }

View File

@ -45,6 +45,8 @@ import {
createFakePrivateProjectChecker, createFakePrivateProjectChecker,
createPrivateProjectChecker, createPrivateProjectChecker,
} from '../private-project/createPrivateProjectChecker'; } from '../private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model';
import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model';
export const createFeatureToggleService = ( export const createFeatureToggleService = (
db: Db, db: Db,
@ -105,6 +107,8 @@ export const createFeatureToggleService = (
const privateProjectChecker = createPrivateProjectChecker(db, config); const privateProjectChecker = createPrivateProjectChecker(db, config);
const dependentFeaturesReadModel = new DependentFeaturesReadModel(db);
const featureToggleService = new FeatureToggleService( const featureToggleService = new FeatureToggleService(
{ {
featureStrategiesStore, featureStrategiesStore,
@ -122,6 +126,7 @@ export const createFeatureToggleService = (
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
return featureToggleService; return featureToggleService;
}; };
@ -155,7 +160,8 @@ export const createFakeFeatureToggleService = (
); );
const segmentService = createFakeSegmentService(config); const segmentService = createFakeSegmentService(config);
const changeRequestAccessReadModel = createFakeChangeRequestAccessService(); const changeRequestAccessReadModel = createFakeChangeRequestAccessService();
const fakeprivateProjectChecker = createFakePrivateProjectChecker(); const fakePrivateProjectChecker = createFakePrivateProjectChecker();
const dependentFeaturesReadModel = new FakeDependentFeaturesReadModel();
const featureToggleService = new FeatureToggleService( const featureToggleService = new FeatureToggleService(
{ {
featureStrategiesStore, featureStrategiesStore,
@ -172,7 +178,8 @@ export const createFakeFeatureToggleService = (
segmentService, segmentService,
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
fakeprivateProjectChecker, fakePrivateProjectChecker,
dependentFeaturesReadModel,
); );
return featureToggleService; return featureToggleService;
}; };

View File

@ -121,6 +121,47 @@ export const featureSchema = {
nullable: true, nullable: true,
description: 'The list of feature tags', description: 'The list of feature tags',
}, },
children: {
type: 'array',
description:
'The list of child feature names. This is an experimental field and may change.',
items: {
type: 'string',
example: 'some-feature',
},
},
dependencies: {
type: 'array',
items: {
type: 'object',
additionalProperties: false,
required: ['feature'],
properties: {
feature: {
description: 'The name of the parent feature',
type: 'string',
example: 'some-feature',
},
enabled: {
description:
'Whether the parent feature is enabled or not',
type: 'boolean',
example: true,
},
variants: {
description:
'The list of variants the parent feature should resolve to. Only valid when feature is enabled.',
type: 'array',
items: {
example: 'some-feature-blue-variant',
type: 'string',
},
},
},
},
description:
'The list of parent dependencies. This is an experimental field and may change.',
},
}, },
components: { components: {
schemas: { schemas: {

View File

@ -10,6 +10,7 @@ import { AccessService } from './access-service';
import { IChangeRequestAccessReadModel } from 'lib/features/change-request-access-service/change-request-access-read-model'; import { IChangeRequestAccessReadModel } from 'lib/features/change-request-access-service/change-request-access-read-model';
import { ISegmentService } from 'lib/segments/segment-service-interface'; import { ISegmentService } from 'lib/segments/segment-service-interface';
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type';
test('Should only store events for potentially stale on', async () => { test('Should only store events for potentially stale on', async () => {
expect.assertions(2); expect.assertions(2);
@ -51,6 +52,7 @@ test('Should only store events for potentially stale on', async () => {
{} as AccessService, {} as AccessService,
{} as IChangeRequestAccessReadModel, {} as IChangeRequestAccessReadModel,
{} as IPrivateProjectChecker, {} as IPrivateProjectChecker,
{} as IDependentFeaturesReadModel,
); );
await featureToggleService.updatePotentiallyStaleFeatures(); await featureToggleService.updatePotentiallyStaleFeatures();

View File

@ -16,9 +16,11 @@ import {
FeatureToggle, FeatureToggle,
FeatureToggleDTO, FeatureToggleDTO,
FeatureToggleLegacy, FeatureToggleLegacy,
FeatureToggleWithDependencies,
FeatureToggleWithEnvironment, FeatureToggleWithEnvironment,
FeatureVariantEvent, FeatureVariantEvent,
IConstraint, IConstraint,
IDependency,
IEventStore, IEventStore,
IFeatureEnvironmentInfo, IFeatureEnvironmentInfo,
IFeatureEnvironmentStore, IFeatureEnvironmentStore,
@ -96,6 +98,7 @@ import { ISegmentService } from 'lib/segments/segment-service-interface';
import { IChangeRequestAccessReadModel } from '../features/change-request-access-service/change-request-access-read-model'; import { IChangeRequestAccessReadModel } from '../features/change-request-access-service/change-request-access-read-model';
import { checkFeatureFlagNamesAgainstPattern } from '../features/feature-naming-pattern/feature-naming-validation'; import { checkFeatureFlagNamesAgainstPattern } from '../features/feature-naming-pattern/feature-naming-validation';
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type';
interface IFeatureContext { interface IFeatureContext {
featureName: string; featureName: string;
@ -157,6 +160,8 @@ class FeatureToggleService {
private privateProjectChecker: IPrivateProjectChecker; private privateProjectChecker: IPrivateProjectChecker;
private dependentFeaturesReadModel: IDependentFeaturesReadModel;
constructor( constructor(
{ {
featureStrategiesStore, featureStrategiesStore,
@ -188,6 +193,7 @@ class FeatureToggleService {
accessService: AccessService, accessService: AccessService,
changeRequestAccessReadModel: IChangeRequestAccessReadModel, changeRequestAccessReadModel: IChangeRequestAccessReadModel,
privateProjectChecker: IPrivateProjectChecker, privateProjectChecker: IPrivateProjectChecker,
dependentFeaturesReadModel: IDependentFeaturesReadModel,
) { ) {
this.logger = getLogger('services/feature-toggle-service.ts'); this.logger = getLogger('services/feature-toggle-service.ts');
this.featureStrategiesStore = featureStrategiesStore; this.featureStrategiesStore = featureStrategiesStore;
@ -204,6 +210,7 @@ class FeatureToggleService {
this.flagResolver = flagResolver; this.flagResolver = flagResolver;
this.changeRequestAccessReadModel = changeRequestAccessReadModel; this.changeRequestAccessReadModel = changeRequestAccessReadModel;
this.privateProjectChecker = privateProjectChecker; this.privateProjectChecker = privateProjectChecker;
this.dependentFeaturesReadModel = dependentFeaturesReadModel;
} }
async validateFeaturesContext( async validateFeaturesContext(
@ -921,7 +928,7 @@ class FeatureToggleService {
projectId, projectId,
environmentVariants, environmentVariants,
userId, userId,
}: IGetFeatureParams): Promise<FeatureToggleWithEnvironment> { }: IGetFeatureParams): Promise<FeatureToggleWithDependencies> {
if (projectId) { if (projectId) {
await this.validateFeatureBelongsToProject({ await this.validateFeatureBelongsToProject({
featureName, featureName,
@ -929,18 +936,31 @@ class FeatureToggleService {
}); });
} }
let dependencies: IDependency[] = [];
let children: string[] = [];
if (this.flagResolver.isEnabled('dependentFeatures')) {
[dependencies, children] = await Promise.all([
this.dependentFeaturesReadModel.getParents(featureName),
this.dependentFeaturesReadModel.getChildren(featureName),
]);
}
if (environmentVariants) { if (environmentVariants) {
return this.featureStrategiesStore.getFeatureToggleWithVariantEnvs( const result =
await this.featureStrategiesStore.getFeatureToggleWithVariantEnvs(
featureName, featureName,
userId, userId,
archived, archived,
); );
return { ...result, dependencies, children };
} else { } else {
return this.featureStrategiesStore.getFeatureToggleWithEnvs( const result =
await this.featureStrategiesStore.getFeatureToggleWithEnvs(
featureName, featureName,
userId, userId,
archived, archived,
); );
return { ...result, dependencies, children };
} }
} }

View File

@ -73,6 +73,8 @@ import {
createDependentFeaturesService, createDependentFeaturesService,
createFakeDependentFeaturesService, createFakeDependentFeaturesService,
} from '../features/dependent-features/createDependentFeaturesService'; } from '../features/dependent-features/createDependentFeaturesService';
import { DependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model';
import { FakeDependentFeaturesReadModel } from '../features/dependent-features/fake-dependent-features-read-model';
// TODO: will be moved to scheduler feature directory // TODO: will be moved to scheduler feature directory
export const scheduleServices = async ( export const scheduleServices = async (
@ -175,6 +177,9 @@ export const createServices = (
const privateProjectChecker = db const privateProjectChecker = db
? createPrivateProjectChecker(db, config) ? createPrivateProjectChecker(db, config)
: createFakePrivateProjectChecker(); : createFakePrivateProjectChecker();
const dependentFeaturesReadModel = db
? new DependentFeaturesReadModel(db)
: new FakeDependentFeaturesReadModel();
const contextService = new ContextService( const contextService = new ContextService(
stores, stores,
@ -227,6 +232,7 @@ export const createServices = (
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
const environmentService = new EnvironmentService(stores, config); const environmentService = new EnvironmentService(stores, config);
const featureTagService = new FeatureTagService(stores, config); const featureTagService = new FeatureTagService(stores, config);

View File

@ -96,6 +96,12 @@ export interface FeatureToggleWithEnvironment extends FeatureToggle {
environments: IEnvironmentDetail[]; environments: IEnvironmentDetail[];
} }
export interface FeatureToggleWithDependencies
extends FeatureToggleWithEnvironment {
dependencies: IDependency[];
children: string[];
}
// @deprecated // @deprecated
export interface FeatureToggleLegacy extends FeatureToggle { export interface FeatureToggleLegacy extends FeatureToggle {
strategies: IStrategyConfig[]; strategies: IStrategyConfig[];

View File

@ -91,6 +91,7 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
dependentFeatures: true,
}, },
}, },
}, },
@ -214,6 +215,32 @@ test('Can get project overview', async () => {
}); });
}); });
test('should list dependencies and children', async () => {
const parent = uuidv4();
const child = uuidv4();
await app.createFeature(parent, 'default');
await app.createFeature(child, 'default');
await app.addDependency(child, parent);
const { body: childFeature } = await app.getProjectFeatures(
'default',
child,
);
const { body: parentFeature } = await app.getProjectFeatures(
'default',
parent,
);
expect(childFeature).toMatchObject({
children: [],
dependencies: [{ feature: parent, enabled: true, variants: [] }],
});
expect(parentFeature).toMatchObject({
children: [child],
dependencies: [],
});
});
test('Can get features for project', async () => { test('Can get features for project', async () => {
await app.request await app.request
.post('/api/admin/projects/default/features') .post('/api/admin/projects/default/features')

View File

@ -20,6 +20,7 @@ beforeAll(async () => {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
featureNamingPattern: true, featureNamingPattern: true,
dependentFeatures: true,
}, },
}, },
}, },

View File

@ -63,6 +63,8 @@ export interface IUnleashHttpAPI {
importPayload: ImportTogglesSchema, importPayload: ImportTogglesSchema,
expectedResponseCode?: number, expectedResponseCode?: number,
): supertest.Test; ): supertest.Test;
addDependency(child: string, parent: string): supertest.Test;
} }
function httpApis( function httpApis(
@ -161,6 +163,21 @@ function httpApis(
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.expect(expectedResponseCode); .expect(expectedResponseCode);
}, },
addDependency(
child: string,
parent: string,
project = DEFAULT_PROJECT,
expectedResponseCode: number = 200,
): supertest.Test {
return request
.post(
`/api/admin/projects/${project}/features/${child}/dependencies`,
)
.send({ feature: parent })
.set('Content-Type', 'application/json')
.expect(expectedResponseCode);
},
}; };
} }

View File

@ -23,6 +23,7 @@ import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services'; import { FavoritesService } from '../../../lib/services';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
let db: ITestDb; let db: ITestDb;
let stores: IUnleashStores; let stores: IUnleashStores;
@ -250,6 +251,9 @@ beforeAll(async () => {
db.rawDatabase, db.rawDatabase,
config, config,
); );
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
featureToggleService = new FeatureToggleService( featureToggleService = new FeatureToggleService(
stores, stores,
config, config,
@ -262,6 +266,7 @@ beforeAll(async () => {
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
favoritesService = new FavoritesService(stores, config); favoritesService = new FavoritesService(stores, config);
projectService = new ProjectService( projectService = new ProjectService(

View File

@ -13,6 +13,7 @@ import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services'; import { FavoritesService } from '../../../lib/services';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
let db; let db;
let stores; let stores;
@ -36,6 +37,9 @@ beforeAll(async () => {
db.rawDatabase, db.rawDatabase,
config, config,
); );
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
const featureToggleService = new FeatureToggleService( const featureToggleService = new FeatureToggleService(
stores, stores,
config, config,
@ -48,6 +52,7 @@ beforeAll(async () => {
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
const project = { const project = {
id: 'test-project', id: 'test-project',

View File

@ -24,6 +24,7 @@ import {
import { ISegmentService } from '../../../lib/segments/segment-service-interface'; import { ISegmentService } from '../../../lib/segments/segment-service-interface';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
let stores: IUnleashStores; let stores: IUnleashStores;
let db; let db;
@ -63,6 +64,9 @@ beforeAll(async () => {
db.rawDatabase, db.rawDatabase,
config, config,
); );
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
segmentService = new SegmentService( segmentService = new SegmentService(
stores, stores,
changeRequestAccessReadModel, changeRequestAccessReadModel,
@ -77,6 +81,7 @@ beforeAll(async () => {
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
}); });
@ -466,6 +471,9 @@ test('If change requests are enabled, cannot change variants without going via C
db.rawDatabase, db.rawDatabase,
unleashConfig, unleashConfig,
); );
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
// Force all feature flags on to make sure we have Change requests on // Force all feature flags on to make sure we have Change requests on
const customFeatureService = new FeatureToggleService( const customFeatureService = new FeatureToggleService(
stores, stores,
@ -479,6 +487,7 @@ test('If change requests are enabled, cannot change variants without going via C
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
const newVariant: IVariant = { const newVariant: IVariant = {
@ -554,6 +563,9 @@ test('If CRs are protected for any environment in the project stops bulk update
db.rawDatabase, db.rawDatabase,
unleashConfig, unleashConfig,
); );
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
// Force all feature flags on to make sure we have Change requests on // Force all feature flags on to make sure we have Change requests on
const customFeatureService = new FeatureToggleService( const customFeatureService = new FeatureToggleService(
stores, stores,
@ -567,6 +579,7 @@ test('If CRs are protected for any environment in the project stops bulk update
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
const toggle = await service.createFeatureToggle( const toggle = await service.createFeatureToggle(

View File

@ -26,6 +26,7 @@ import { AccessService } from '../../../lib/services/access-service';
import { ISegmentService } from '../../../lib/segments/segment-service-interface'; import { ISegmentService } from '../../../lib/segments/segment-service-interface';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
let stores: IUnleashStores; let stores: IUnleashStores;
let db: ITestDb; let db: ITestDb;
@ -47,6 +48,9 @@ beforeAll(async () => {
db.rawDatabase, db.rawDatabase,
config, config,
); );
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
segmentService = new SegmentService( segmentService = new SegmentService(
stores, stores,
changeRequestAccessReadModel, changeRequestAccessReadModel,
@ -61,6 +65,7 @@ beforeAll(async () => {
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
service = new PlaygroundService(config, { service = new PlaygroundService(config, {
featureToggleServiceV2: featureToggleService, featureToggleServiceV2: featureToggleService,

View File

@ -12,6 +12,7 @@ import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services'; import { FavoritesService } from '../../../lib/services';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
let stores: IUnleashStores; let stores: IUnleashStores;
let db: ITestDb; let db: ITestDb;
@ -41,6 +42,9 @@ beforeAll(async () => {
db.rawDatabase, db.rawDatabase,
config, config,
); );
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
featureToggleService = new FeatureToggleService( featureToggleService = new FeatureToggleService(
stores, stores,
config, config,
@ -53,6 +57,7 @@ beforeAll(async () => {
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
favoritesService = new FavoritesService(stores, config); favoritesService = new FavoritesService(stores, config);

View File

@ -16,6 +16,7 @@ import { FeatureEnvironmentEvent } from '../../../lib/types/events';
import { addDays, subDays } from 'date-fns'; import { addDays, subDays } from 'date-fns';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
let stores; let stores;
let db: ITestDb; let db: ITestDb;
@ -62,6 +63,9 @@ beforeAll(async () => {
db.rawDatabase, db.rawDatabase,
config, config,
); );
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
featureToggleService = new FeatureToggleService( featureToggleService = new FeatureToggleService(
stores, stores,
config, config,
@ -74,6 +78,7 @@ beforeAll(async () => {
accessService, accessService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel,
); );
favoritesService = new FavoritesService(stores, config); favoritesService = new FavoritesService(stores, config);