diff --git a/src/lib/app.ts b/src/lib/app.ts index 580e2b9d00..61c64de0da 100644 --- a/src/lib/app.ts +++ b/src/lib/app.ts @@ -29,6 +29,7 @@ import maintenanceMiddleware from './middleware/maintenance-middleware'; import { unless } from './middleware/unless-middleware'; import { catchAllErrorHandler } from './middleware/catch-all-error-handler'; import NotFoundError from './error/notfound-error'; +import privateProjectMiddleware from './features/private-project/privateProjectMiddleware'; export default async function getApp( config: IUnleashConfig, @@ -157,6 +158,8 @@ export default async function getApp( } } + app.use(baseUriPath, privateProjectMiddleware(config, services)); + app.use( baseUriPath, rbacMiddleware(config, stores, services.accessService), diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index e6329e3b04..4c1a08f6bb 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -36,6 +36,7 @@ import { AccountStore } from './account-store'; import ProjectStatsStore from './project-stats-store'; import { Db } from './db'; import { ImportTogglesStore } from '../features/export-import-toggles/import-toggles-store'; +import PrivateProjectStore from '../features/private-project/privateProjectStore'; export const createStores = ( config: IUnleashConfig, @@ -128,6 +129,7 @@ export const createStores = ( ), projectStatsStore: new ProjectStatsStore(db, eventBus, getLogger), importTogglesStore: new ImportTogglesStore(db), + privateProjectStore: new PrivateProjectStore(db, getLogger), }; }; diff --git a/src/lib/features/feature-toggle/createFeatureToggleService.ts b/src/lib/features/feature-toggle/createFeatureToggleService.ts index 4f616357fb..df70f9707d 100644 --- a/src/lib/features/feature-toggle/createFeatureToggleService.ts +++ b/src/lib/features/feature-toggle/createFeatureToggleService.ts @@ -41,6 +41,10 @@ import { } from '../segment/createSegmentService'; import StrategyStore from '../../db/strategy-store'; import FakeStrategiesStore from '../../../test/fixtures/fake-strategies-store'; +import { + createFakeprivateProjectChecker, + createPrivateProjectChecker, +} from '../private-project/createPrivateProjectChecker'; export const createFeatureToggleService = ( db: Db, @@ -98,6 +102,9 @@ export const createFeatureToggleService = ( db, config, ); + + const privateProjectChecker = createPrivateProjectChecker(db, config); + const featureToggleService = new FeatureToggleService( { featureStrategiesStore, @@ -114,6 +121,7 @@ export const createFeatureToggleService = ( segmentService, accessService, changeRequestAccessReadModel, + privateProjectChecker, ); return featureToggleService; }; @@ -147,6 +155,7 @@ export const createFakeFeatureToggleService = ( ); const segmentService = createFakeSegmentService(config); const changeRequestAccessReadModel = createFakeChangeRequestAccessService(); + const fakeprivateProjectChecker = createFakeprivateProjectChecker(); const featureToggleService = new FeatureToggleService( { featureStrategiesStore, @@ -163,6 +172,7 @@ export const createFakeFeatureToggleService = ( segmentService, accessService, changeRequestAccessReadModel, + fakeprivateProjectChecker, ); return featureToggleService; }; diff --git a/src/lib/features/private-project/createPrivateProjectChecker.ts b/src/lib/features/private-project/createPrivateProjectChecker.ts new file mode 100644 index 0000000000..e68f8520b6 --- /dev/null +++ b/src/lib/features/private-project/createPrivateProjectChecker.ts @@ -0,0 +1,21 @@ +import { Db, IUnleashConfig } from 'lib/server-impl'; +import PrivateProjectStore from './privateProjectStore'; +import { PrivateProjectChecker } from './privateProjectChecker'; +import { FakeprivateProjectChecker } from './fakePrivateProjectChecker'; + +export const createPrivateProjectChecker = ( + db: Db, + config: IUnleashConfig, +): PrivateProjectChecker => { + const { getLogger } = config; + const privateProjectStore = new PrivateProjectStore(db, getLogger); + + return new PrivateProjectChecker({ + privateProjectStore: privateProjectStore, + }); +}; + +export const createFakeprivateProjectChecker = + (): FakeprivateProjectChecker => { + return new FakeprivateProjectChecker(); + }; diff --git a/src/lib/features/private-project/fakePrivateProjectChecker.ts b/src/lib/features/private-project/fakePrivateProjectChecker.ts new file mode 100644 index 0000000000..4c002e66e1 --- /dev/null +++ b/src/lib/features/private-project/fakePrivateProjectChecker.ts @@ -0,0 +1,8 @@ +import { IPrivateProjectChecker } from './privateProjectCheckerType'; + +export class FakeprivateProjectChecker implements IPrivateProjectChecker { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getUserAccessibleProjects(userId: number): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/src/lib/features/private-project/privateProjectChecker.ts b/src/lib/features/private-project/privateProjectChecker.ts new file mode 100644 index 0000000000..837127edfa --- /dev/null +++ b/src/lib/features/private-project/privateProjectChecker.ts @@ -0,0 +1,17 @@ +import { IUnleashStores } from '../../types'; +import { IPrivateProjectStore } from './privateProjectStoreType'; +import { IPrivateProjectChecker } from './privateProjectCheckerType'; + +export class PrivateProjectChecker implements IPrivateProjectChecker { + private privateProjectStore: IPrivateProjectStore; + + constructor({ + privateProjectStore, + }: Pick) { + this.privateProjectStore = privateProjectStore; + } + + async getUserAccessibleProjects(userId: number): Promise { + return this.privateProjectStore.getUserAccessibleProjects(userId); + } +} diff --git a/src/lib/features/private-project/privateProjectCheckerType.ts b/src/lib/features/private-project/privateProjectCheckerType.ts new file mode 100644 index 0000000000..e4cfb53a9c --- /dev/null +++ b/src/lib/features/private-project/privateProjectCheckerType.ts @@ -0,0 +1,3 @@ +export interface IPrivateProjectChecker { + getUserAccessibleProjects(userId: number): Promise; +} diff --git a/src/lib/features/private-project/privateProjectMiddleware.ts b/src/lib/features/private-project/privateProjectMiddleware.ts new file mode 100644 index 0000000000..9af2a28f4b --- /dev/null +++ b/src/lib/features/private-project/privateProjectMiddleware.ts @@ -0,0 +1,40 @@ +import { IUnleashConfig, IUnleashServices } from '../../types'; +import { findParam } from '../../middleware'; +import { NextFunction, Response } from 'express'; + +const privateProjectMiddleware = ( + { + getLogger, + flagResolver, + }: Pick, + { projectService, accessService }: IUnleashServices, +): any => { + const logger = getLogger('/middleware/project-middleware.ts'); + logger.debug('Enabling private project middleware'); + + if (!flagResolver.isEnabled('privateProjects')) { + return (req, res, next) => next(); + } + + return async (req, res: Response, next: NextFunction) => { + req.checkPrivateProjectPermissions = async () => { + const { user } = req; + + let projectId = + findParam('projectId', req) || findParam('project', req); + + if (projectId === undefined) { + return true; + } + const permissions = await accessService.getPermissionsForUser(user); + + return ( + permissions.map((p) => p.permission).includes('ADMIN') || + projectService.isProjectUser(user.id, projectId) + ); + }; + next(); + }; +}; + +export default privateProjectMiddleware; diff --git a/src/lib/features/private-project/privateProjectStore.ts b/src/lib/features/private-project/privateProjectStore.ts new file mode 100644 index 0000000000..e1d5bcfc43 --- /dev/null +++ b/src/lib/features/private-project/privateProjectStore.ts @@ -0,0 +1,42 @@ +import { Db } from '../../db/db'; +import { Logger, LogProvider } from '../../logger'; +import { IPrivateProjectStore } from './privateProjectStoreType'; + +class PrivateProjectStore implements IPrivateProjectStore { + private db: Db; + + private logger: Logger; + + constructor(db: Db, getLogger: LogProvider) { + this.db = db; + this.logger = getLogger('project-permission-store.ts'); + } + + destroy(): void {} + + async getUserAccessibleProjects(userId: number): Promise { + const projects = await this.db + .from((db) => { + db.select('project') + .from('role_user') + .leftJoin('roles', 'role_user.role_id', 'roles.id') + .where('user_id', userId) + .union((queryBuilder) => { + queryBuilder + .select('project') + .from('group_role') + .leftJoin( + 'group_user', + 'group_user.group_id', + 'group_role.group_id', + ) + .where('user_id', userId); + }) + .as('query'); + }) + .pluck('project'); + return projects; + } +} + +export default PrivateProjectStore; diff --git a/src/lib/features/private-project/privateProjectStoreType.ts b/src/lib/features/private-project/privateProjectStoreType.ts new file mode 100644 index 0000000000..a554aaab5a --- /dev/null +++ b/src/lib/features/private-project/privateProjectStoreType.ts @@ -0,0 +1,3 @@ +export interface IPrivateProjectStore { + getUserAccessibleProjects(userId: number): Promise; +} diff --git a/src/lib/middleware/rbac-middleware.ts b/src/lib/middleware/rbac-middleware.ts index 72061c3c0a..e9b857682f 100644 --- a/src/lib/middleware/rbac-middleware.ts +++ b/src/lib/middleware/rbac-middleware.ts @@ -7,6 +7,7 @@ import { import { IUnleashConfig } from '../types/option'; import { IUnleashStores } from '../types/stores'; import User from '../types/user'; +import { Request } from 'express'; interface PermissionChecker { hasPermission( @@ -17,9 +18,9 @@ interface PermissionChecker { ): Promise; } -function findParam( +export function findParam( name: string, - { params, body }: any, + { params, body }: Request, defaultValue?: string, ): string | undefined { let found = params ? params[name] : undefined; diff --git a/src/lib/routes/controller.ts b/src/lib/routes/controller.ts index 8588042c8a..413756991d 100644 --- a/src/lib/routes/controller.ts +++ b/src/lib/routes/controller.ts @@ -1,7 +1,6 @@ import { IRouter, Router, Request, Response, RequestHandler } from 'express'; import { Logger } from 'lib/logger'; -import { IUnleashConfig } from '../types/option'; -import { NONE } from '../types/permissions'; +import { IUnleashConfig, NONE } from '../types'; import { handleErrors } from './util'; import requireContentType from '../middleware/content_type_checker'; import { PermissionError } from '../error'; @@ -55,6 +54,17 @@ const checkPermission = return res.status(403).json(new PermissionError(permissions)).end(); }; +const checkPrivateProjectPermissions = () => async (req, res, next) => { + if ( + !req.checkPrivateProjectPermissions || + (await req.checkPrivateProjectPermissions()) + ) { + return next(); + } + + return res.status(404).end(); +}; + /** * Base class for Controllers to standardize binding to express Router. * @@ -100,6 +110,7 @@ export default class Controller { this.app[options.method]( options.path, checkPermission(options.permission), + checkPrivateProjectPermissions(), this.useContentTypeMiddleware(options), this.useRouteErrorHandler(options.handler.bind(this)), ); @@ -186,6 +197,7 @@ export default class Controller { this.app.post( path, checkPermission(permission), + checkPrivateProjectPermissions(), filehandler.bind(this), this.useRouteErrorHandler(handler.bind(this)), ); diff --git a/src/lib/services/feature-service-potentially-stale.test.ts b/src/lib/services/feature-service-potentially-stale.test.ts index c1d25b7d09..126286c14f 100644 --- a/src/lib/services/feature-service-potentially-stale.test.ts +++ b/src/lib/services/feature-service-potentially-stale.test.ts @@ -9,6 +9,7 @@ import FeatureToggleService from './feature-toggle-service'; import { AccessService } from './access-service'; import { IChangeRequestAccessReadModel } from 'lib/features/change-request-access-service/change-request-access-read-model'; import { ISegmentService } from 'lib/segments/segment-service-interface'; +import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; test('Should only store events for potentially stale on', async () => { expect.assertions(2); @@ -49,6 +50,7 @@ test('Should only store events for potentially stale on', async () => { {} as ISegmentService, {} as AccessService, {} as IChangeRequestAccessReadModel, + {} as IPrivateProjectChecker, ); await featureToggleService.updatePotentiallyStaleFeatures(); diff --git a/src/lib/services/feature-toggle-service.ts b/src/lib/services/feature-toggle-service.ts index fee121e1af..b5224cf010 100644 --- a/src/lib/services/feature-toggle-service.ts +++ b/src/lib/services/feature-toggle-service.ts @@ -95,6 +95,7 @@ import { unique } from '../util/unique'; import { ISegmentService } from 'lib/segments/segment-service-interface'; import { IChangeRequestAccessReadModel } from '../features/change-request-access-service/change-request-access-read-model'; import { checkFeatureFlagNamesAgainstPattern } from '../features/feature-naming-pattern/feature-naming-validation'; +import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; interface IFeatureContext { featureName: string; @@ -154,6 +155,8 @@ class FeatureToggleService { private changeRequestAccessReadModel: IChangeRequestAccessReadModel; + private privateProjectChecker: IPrivateProjectChecker; + constructor( { featureStrategiesStore, @@ -184,6 +187,7 @@ class FeatureToggleService { segmentService: ISegmentService, accessService: AccessService, changeRequestAccessReadModel: IChangeRequestAccessReadModel, + privateProjectChecker: IPrivateProjectChecker, ) { this.logger = getLogger('services/feature-toggle-service.ts'); this.featureStrategiesStore = featureStrategiesStore; @@ -199,6 +203,7 @@ class FeatureToggleService { this.accessService = accessService; this.flagResolver = flagResolver; this.changeRequestAccessReadModel = changeRequestAccessReadModel; + this.privateProjectChecker = privateProjectChecker; } async validateFeaturesContext( @@ -1017,11 +1022,20 @@ class FeatureToggleService { userId?: number, archived: boolean = false, ): Promise { - return this.featureToggleClientStore.getAdmin({ + const features = await this.featureToggleClientStore.getAdmin({ featureQuery: query, userId, archived, }); + + if (this.flagResolver.isEnabled('privateProjects') && userId) { + const projects = + await this.privateProjectChecker.getUserAccessibleProjects( + userId, + ); + return features.filter((f) => projects.includes(f.project)); + } + return features; } async getFeatureOverview( diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index 161e8339a7..c3a2fe018a 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -60,6 +60,10 @@ import ConfigurationRevisionService from '../features/feature-toggle/configurati import { createFeatureToggleService } from '../features'; import EventAnnouncerService from './event-announcer-service'; import { createGroupService } from '../features/group/createGroupService'; +import { + createFakeprivateProjectChecker, + createPrivateProjectChecker, +} from '../features/private-project/createPrivateProjectChecker'; // TODO: will be moved to scheduler feature directory export const scheduleServices = async ( @@ -184,12 +188,16 @@ export const createServices = ( changeRequestAccessReadModel, config, ); + const privateProjectChecker = db + ? createPrivateProjectChecker(db, config) + : createFakeprivateProjectChecker(); const featureToggleServiceV2 = new FeatureToggleService( stores, config, segmentService, accessService, changeRequestAccessReadModel, + privateProjectChecker, ); const environmentService = new EnvironmentService(stores, config); const featureTagService = new FeatureTagService(stores, config); diff --git a/src/lib/types/stores.ts b/src/lib/types/stores.ts index 547e4e0921..7dd9f0c64f 100644 --- a/src/lib/types/stores.ts +++ b/src/lib/types/stores.ts @@ -33,6 +33,7 @@ import { IFavoriteProjectsStore } from './stores/favorite-projects'; import { IAccountStore } from './stores/account-store'; import { IProjectStatsStore } from './stores/project-stats-store-type'; import { IImportTogglesStore } from '../features/export-import-toggles/import-toggles-store-type'; +import { IPrivateProjectStore } from '../features/private-project/privateProjectStoreType'; export interface IUnleashStores { accessStore: IAccessStore; @@ -70,6 +71,7 @@ export interface IUnleashStores { favoriteProjectsStore: IFavoriteProjectsStore; projectStatsStore: IProjectStatsStore; importTogglesStore: IImportTogglesStore; + privateProjectStore: IPrivateProjectStore; } export { @@ -107,4 +109,5 @@ export { IFavoriteFeaturesStore, IFavoriteProjectsStore, IImportTogglesStore, + IPrivateProjectStore, }; diff --git a/src/server-dev.ts b/src/server-dev.ts index 4e5547de6b..a041f20eea 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -43,7 +43,7 @@ process.nextTick(async () => { featureNamingPattern: true, doraMetrics: true, variantTypeNumber: true, - privateProjects: true, + privateProjects: false, accessOverview: true, }, }, diff --git a/src/test/e2e/services/access-service.e2e.test.ts b/src/test/e2e/services/access-service.e2e.test.ts index da6500ceff..0696e97e03 100644 --- a/src/test/e2e/services/access-service.e2e.test.ts +++ b/src/test/e2e/services/access-service.e2e.test.ts @@ -21,6 +21,7 @@ import { SegmentService } from '../../../lib/services/segment-service'; import { GroupService } from '../../../lib/services/group-service'; import { FavoritesService } from '../../../lib/services'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; +import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; let db: ITestDb; let stores: IUnleashStores; @@ -244,12 +245,17 @@ beforeAll(async () => { db.rawDatabase, accessService, ); + const privateProjectChecker = createPrivateProjectChecker( + db.rawDatabase, + config, + ); featureToggleService = new FeatureToggleService( stores, config, new SegmentService(stores, changeRequestAccessReadModel, config), accessService, changeRequestAccessReadModel, + privateProjectChecker, ); favoritesService = new FavoritesService(stores, config); projectService = new ProjectService( diff --git a/src/test/e2e/services/api-token-service.e2e.test.ts b/src/test/e2e/services/api-token-service.e2e.test.ts index 8bef71f323..4362d325fd 100644 --- a/src/test/e2e/services/api-token-service.e2e.test.ts +++ b/src/test/e2e/services/api-token-service.e2e.test.ts @@ -12,6 +12,7 @@ import { SegmentService } from '../../../lib/services/segment-service'; import { GroupService } from '../../../lib/services/group-service'; import { FavoritesService } from '../../../lib/services'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; +import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; let db; let stores; @@ -31,12 +32,17 @@ beforeAll(async () => { db.rawDatabase, accessService, ); + const privateProjectChecker = createPrivateProjectChecker( + db.rawDatabase, + config, + ); const featureToggleService = new FeatureToggleService( stores, config, new SegmentService(stores, changeRequestAccessReadModel, config), accessService, changeRequestAccessReadModel, + privateProjectChecker, ); const project = { id: 'test-project', diff --git a/src/test/e2e/services/feature-toggle-service-v2.e2e.test.ts b/src/test/e2e/services/feature-toggle-service-v2.e2e.test.ts index ecfbfa72c5..6bbf855073 100644 --- a/src/test/e2e/services/feature-toggle-service-v2.e2e.test.ts +++ b/src/test/e2e/services/feature-toggle-service-v2.e2e.test.ts @@ -18,6 +18,7 @@ import { } from '../../../lib/error'; import { ISegmentService } from '../../../lib/segments/segment-service-interface'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; +import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; let stores; let db; @@ -58,12 +59,17 @@ beforeAll(async () => { changeRequestAccessReadModel, config, ); + const privateProjectChecker = createPrivateProjectChecker( + db.rawDatabase, + config, + ); service = new FeatureToggleService( stores, config, segmentService, accessService, changeRequestAccessReadModel, + privateProjectChecker, ); }); @@ -449,6 +455,10 @@ test('If change requests are enabled, cannot change variants without going via C db.rawDatabase, accessService, ); + const privateProjectChecker = createPrivateProjectChecker( + db.rawDatabase, + unleashConfig, + ); // Force all feature flags on to make sure we have Change requests on const customFeatureService = new FeatureToggleService( stores, @@ -461,6 +471,7 @@ test('If change requests are enabled, cannot change variants without going via C segmentService, accessService, changeRequestAccessReadModel, + privateProjectChecker, ); const newVariant: IVariant = { @@ -532,6 +543,10 @@ test('If CRs are protected for any environment in the project stops bulk update db.rawDatabase, accessService, ); + const privateProjectChecker = createPrivateProjectChecker( + db.rawDatabase, + unleashConfig, + ); // Force all feature flags on to make sure we have Change requests on const customFeatureService = new FeatureToggleService( stores, @@ -544,6 +559,7 @@ test('If CRs are protected for any environment in the project stops bulk update segmentService, accessService, changeRequestAccessReadModel, + privateProjectChecker, ); const toggle = await service.createFeatureToggle( diff --git a/src/test/e2e/services/playground-service.test.ts b/src/test/e2e/services/playground-service.test.ts index 2c8c7edd68..058e713b6f 100644 --- a/src/test/e2e/services/playground-service.test.ts +++ b/src/test/e2e/services/playground-service.test.ts @@ -25,6 +25,7 @@ import { GroupService } from '../../../lib/services/group-service'; import { AccessService } from '../../../lib/services/access-service'; import { ISegmentService } from '../../../lib/segments/segment-service-interface'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; +import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; let stores: IUnleashStores; let db: ITestDb; @@ -47,12 +48,17 @@ beforeAll(async () => { changeRequestAccessReadModel, config, ); + const privateProjectChecker = createPrivateProjectChecker( + db.rawDatabase, + config, + ); featureToggleService = new FeatureToggleService( stores, config, segmentService, accessService, changeRequestAccessReadModel, + privateProjectChecker, ); service = new PlaygroundService(config, { featureToggleServiceV2: featureToggleService, diff --git a/src/test/e2e/services/project-health-service.e2e.test.ts b/src/test/e2e/services/project-health-service.e2e.test.ts index 6fca2c1ca6..0aaeff0424 100644 --- a/src/test/e2e/services/project-health-service.e2e.test.ts +++ b/src/test/e2e/services/project-health-service.e2e.test.ts @@ -11,6 +11,7 @@ import { SegmentService } from '../../../lib/services/segment-service'; import { GroupService } from '../../../lib/services/group-service'; import { FavoritesService } from '../../../lib/services'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; +import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; let stores: IUnleashStores; let db: ITestDb; @@ -36,12 +37,17 @@ beforeAll(async () => { db.rawDatabase, accessService, ); + const privateProjectChecker = createPrivateProjectChecker( + db.rawDatabase, + config, + ); featureToggleService = new FeatureToggleService( stores, config, new SegmentService(stores, changeRequestAccessReadModel, config), accessService, changeRequestAccessReadModel, + privateProjectChecker, ); favoritesService = new FavoritesService(stores, config); diff --git a/src/test/e2e/services/project-service.e2e.test.ts b/src/test/e2e/services/project-service.e2e.test.ts index 3cd3cfe34e..5bbddeb3b5 100644 --- a/src/test/e2e/services/project-service.e2e.test.ts +++ b/src/test/e2e/services/project-service.e2e.test.ts @@ -15,6 +15,7 @@ import { FavoritesService } from '../../../lib/services'; import { FeatureEnvironmentEvent } from '../../../lib/types/events'; import { addDays, subDays } from 'date-fns'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; +import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; let stores; let db: ITestDb; @@ -57,12 +58,17 @@ beforeAll(async () => { db.rawDatabase, accessService, ); + const privateProjectChecker = createPrivateProjectChecker( + db.rawDatabase, + config, + ); featureToggleService = new FeatureToggleService( stores, config, new SegmentService(stores, changeRequestAccessReadModel, config), accessService, changeRequestAccessReadModel, + privateProjectChecker, ); favoritesService = new FavoritesService(stores, config); diff --git a/src/test/fixtures/store.ts b/src/test/fixtures/store.ts index 5e0bccd225..4ac4ff4508 100644 --- a/src/test/fixtures/store.ts +++ b/src/test/fixtures/store.ts @@ -15,7 +15,11 @@ import FakeUserFeedbackStore from './fake-user-feedback-store'; import FakeFeatureTagStore from './fake-feature-tag-store'; import FakeEnvironmentStore from './fake-environment-store'; import FakeStrategiesStore from './fake-strategies-store'; -import { IImportTogglesStore, IUnleashStores } from '../../lib/types'; +import { + IImportTogglesStore, + IPrivateProjectStore, + IUnleashStores, +} from '../../lib/types'; import FakeSessionStore from './fake-session-store'; import FakeFeatureEnvironmentStore from './fake-feature-environment-store'; import FakeApiTokenStore from './fake-api-token-store'; @@ -78,6 +82,7 @@ const createStores: () => IUnleashStores = () => { favoriteProjectsStore: new FakeFavoriteProjectsStore(), projectStatsStore: new FakeProjectStatsStore(), importTogglesStore: {} as IImportTogglesStore, + privateProjectStore: {} as IPrivateProjectStore, }; };