From 663b169c46648dc2478d8a9c2f3e36fe8b927ce5 Mon Sep 17 00:00:00 2001 From: Christopher Kolstad Date: Fri, 29 Nov 2024 09:43:43 +0100 Subject: [PATCH] task: enabled in OSS. (#8856) Hardcode project and environment names to filter by when OSS --- .../__snapshots__/create-config.test.ts.snap | 1 + src/lib/create-config.ts | 5 ++ src/lib/db/feature-environment-store.ts | 31 ++++++- src/lib/db/index.ts | 14 +--- .../features/access/createAccessService.ts | 2 +- .../api-tokens/createApiTokenService.ts | 2 +- .../client-feature-toggle-store.ts | 18 +++- .../createClientFeatureToggleService.ts | 13 +-- .../createExportImportService.ts | 9 +- .../createFeatureLifecycle.ts | 4 +- .../createFeatureToggleService.ts | 14 +--- .../feature-toggle/feature-toggle-store.ts | 10 +++ .../createInstanceStatsService.ts | 9 +- .../createEnvironmentService.ts | 11 +-- .../project-environments/environment-store.ts | 37 +++++++-- .../createProjectInsightsService.ts | 7 +- .../createProjectStatusService.ts | 7 +- .../features/project/createProjectService.ts | 11 +-- src/lib/features/project/project-store.ts | 16 +++- src/lib/types/option.ts | 2 + .../e2e/api/admin/environment-oss.e2e.test.ts | 68 +++++++++++++++ .../e2e/api/client/feature-oss.e2e.test.ts | 82 +++++++++++++++++++ 22 files changed, 284 insertions(+), 89 deletions(-) create mode 100644 src/test/e2e/api/admin/environment-oss.e2e.test.ts create mode 100644 src/test/e2e/api/client/feature-oss.e2e.test.ts diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 22f5c0f3fb11..5e6cfdb9ce6f 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -85,6 +85,7 @@ exports[`should create default config 1`] = ` }, "inlineSegmentConstraints": true, "isEnterprise": false, + "isOss": false, "listen": { "host": undefined, "port": 4242, diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index e3c386d3d01c..0aa1ad3a0fd3 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -620,6 +620,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { Boolean(options.enterpriseVersion) && ui.environment?.toLowerCase() !== 'pro'; + const isTest = process.env.NODE_ENV === 'test'; + const isOss = isTest + ? options.isOss || false + : !isEnterprise && ui.environment !== 'pro'; const metricsRateLimiting = loadMetricsRateLimitingConfig(options); const rateLimiting = loadRateLimitingConfig(options); @@ -760,6 +764,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { publicFolder: options.publicFolder, disableScheduler: options.disableScheduler, isEnterprise: isEnterprise, + isOss: isOss, metricsRateLimiting, rateLimiting, feedbackUriPath, diff --git a/src/lib/db/feature-environment-store.ts b/src/lib/db/feature-environment-store.ts index 6b3cb7392539..3bb80159bff9 100644 --- a/src/lib/db/feature-environment-store.ts +++ b/src/lib/db/feature-environment-store.ts @@ -3,13 +3,14 @@ import type { FeatureEnvironmentKey, IFeatureEnvironmentStore, } from '../types/stores/feature-environment-store'; -import type { Logger, LogProvider } from '../logger'; +import type { Logger } from '../logger'; import metricsHelper from '../util/metrics-helper'; import { DB_TIME } from '../metric-events'; import type { IFeatureEnvironment, IVariant } from '../types/model'; import NotFoundError from '../error/notfound-error'; import { v4 as uuidv4 } from 'uuid'; import type { Db } from './db'; +import type { IUnleashConfig } from '../types'; const T = { featureEnvs: 'feature_environments', @@ -36,7 +37,12 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { private readonly timer: Function; - constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) { + private readonly isOss: boolean; + constructor( + db: Db, + eventBus: EventEmitter, + { getLogger, isOss }: Pick, + ) { this.db = db; this.logger = getLogger('feature-environment-store.ts'); this.timer = (action) => @@ -44,6 +50,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { store: 'feature-environments', action, }); + this.isOss = isOss; } async delete({ @@ -96,11 +103,30 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { ); } + addOssFilterIfNeeded(queryBuilder) { + if (this.isOss) { + return queryBuilder + .join( + 'environments', + 'environments.name', + '=', + `${T.featureEnvs}.environment`, + ) + .whereIn('environments.name', [ + 'default', + 'development', + 'production', + ]); + } + return queryBuilder; + } + async getAll(query?: Object): Promise { let rows = this.db(T.featureEnvs); if (query) { rows = rows.where(query); } + this.addOssFilterIfNeeded(rows); return (await rows).map((r) => ({ enabled: r.enabled, featureName: r.feature_name, @@ -119,6 +145,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { if (environment) { rows = rows.where({ environment }); } + this.addOssFilterIfNeeded(rows); return (await rows).map((r) => ({ enabled: r.enabled, featureName: r.feature_name, diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 72ddcd4b497b..3722d91d46fd 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -94,12 +94,7 @@ export const createStores = ( settingStore: new SettingStore(db, getLogger), userStore: new UserStore(db, getLogger, config.flagResolver), accountStore: new AccountStore(db, getLogger), - projectStore: new ProjectStore( - db, - eventBus, - getLogger, - config.flagResolver, - ), + projectStore: new ProjectStore(db, eventBus, config), tagStore: new TagStore(db, eventBus, getLogger), tagTypeStore: new TagTypeStore(db, eventBus, getLogger), addonStore: new AddonStore(db, eventBus, getLogger), @@ -122,15 +117,14 @@ export const createStores = ( clientFeatureToggleStore: new FeatureToggleClientStore( db, eventBus, - getLogger, - config.flagResolver, + config, ), - environmentStore: new EnvironmentStore(db, eventBus, getLogger), + environmentStore: new EnvironmentStore(db, eventBus, config), featureTagStore: new FeatureTagStore(db, eventBus, getLogger), featureEnvironmentStore: new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ), userSplashStore: new UserSplashStore(db, eventBus, getLogger), roleStore: new RoleStore(db, eventBus, getLogger), diff --git a/src/lib/features/access/createAccessService.ts b/src/lib/features/access/createAccessService.ts index 26791fc82fac..c381c0ed278c 100644 --- a/src/lib/features/access/createAccessService.ts +++ b/src/lib/features/access/createAccessService.ts @@ -25,7 +25,7 @@ export const createAccessService = ( const groupStore = new GroupStore(db); const accountStore = new AccountStore(db, getLogger); const roleStore = new RoleStore(db, eventBus, getLogger); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const accessStore = new AccessStore(db, eventBus, getLogger); const eventService = createEventsService(db, config); const groupService = new GroupService( diff --git a/src/lib/features/api-tokens/createApiTokenService.ts b/src/lib/features/api-tokens/createApiTokenService.ts index 18b236c628c8..0dec7de7a457 100644 --- a/src/lib/features/api-tokens/createApiTokenService.ts +++ b/src/lib/features/api-tokens/createApiTokenService.ts @@ -21,7 +21,7 @@ export const createApiTokenService = ( getLogger, config.flagResolver, ); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const eventService = createEventsService(db, config); return new ApiTokenService( diff --git a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts index ed4b738a734b..2549a9ef4450 100644 --- a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts +++ b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts @@ -1,7 +1,7 @@ import { Knex } from 'knex'; import metricsHelper from '../../util/metrics-helper'; import { DB_TIME } from '../../metric-events'; -import type { Logger, LogProvider } from '../../logger'; +import type { Logger } from '../../logger'; import type { IFeatureToggleClient, IFeatureToggleClientStore, @@ -9,6 +9,7 @@ import type { IFlagResolver, IStrategyConfig, ITag, + IUnleashConfig, PartialDeep, } from '../../types'; import { @@ -46,11 +47,16 @@ export default class FeatureToggleClientStore private flagResolver: IFlagResolver; + private readonly isOss: boolean; + constructor( db: Db, eventBus: EventEmitter, - getLogger: LogProvider, - flagResolver: IFlagResolver, + { + getLogger, + flagResolver, + isOss, + }: Pick, ) { this.db = db; this.logger = getLogger('feature-toggle-client-store.ts'); @@ -60,6 +66,7 @@ export default class FeatureToggleClientStore action, }); this.flagResolver = flagResolver; + this.isOss = isOss; } private async getAll({ @@ -72,7 +79,6 @@ export default class FeatureToggleClientStore const isPlayground = requestType === 'playground'; const environment = featureQuery?.environment || DEFAULT_ENV; const stopTimer = this.timer(`getAllBy${requestType}`); - let selectColumns = [ 'features.name as name', 'features.description as description', @@ -103,6 +109,10 @@ export default class FeatureToggleClientStore let query = this.db('features') .modify(FeatureToggleStore.filterByArchived, archived) + .modify( + FeatureToggleStore.filterByProjectsAccessibleByOss, + this.isOss, + ) .leftJoin( this.db('feature_strategies') .select('*') diff --git a/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts b/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts index be72bf8026f1..8d4a8c239fa2 100644 --- a/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts +++ b/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts @@ -10,13 +10,10 @@ export const createClientFeatureToggleService = ( db: Db, config: IUnleashConfig, ): ClientFeatureToggleService => { - const { getLogger, eventBus, flagResolver } = config; - const featureToggleClientStore = new FeatureToggleClientStore( db, - eventBus, - getLogger, - flagResolver, + config.eventBus, + config, ); const segmentReadModel = new SegmentReadModel(db); @@ -26,7 +23,7 @@ export const createClientFeatureToggleService = ( clientFeatureToggleStore: featureToggleClientStore, }, segmentReadModel, - { getLogger, flagResolver }, + config, ); return clientFeatureToggleService; @@ -35,8 +32,6 @@ export const createClientFeatureToggleService = ( export const createFakeClientFeatureToggleService = ( config: IUnleashConfig, ): ClientFeatureToggleService => { - const { getLogger, flagResolver } = config; - const fakeClientFeatureToggleStore = new FakeClientFeatureToggleStore(); const fakeSegmentReadModel = new FakeSegmentReadModel(); @@ -46,7 +41,7 @@ export const createFakeClientFeatureToggleService = ( clientFeatureToggleStore: fakeClientFeatureToggleStore, }, fakeSegmentReadModel, - { getLogger, flagResolver }, + config, ); return clientFeatureToggleService; diff --git a/src/lib/features/export-import-toggles/createExportImportService.ts b/src/lib/features/export-import-toggles/createExportImportService.ts index b36b6e0fabf3..69bebf187066 100644 --- a/src/lib/features/export-import-toggles/createExportImportService.ts +++ b/src/lib/features/export-import-toggles/createExportImportService.ts @@ -150,12 +150,7 @@ export const deferredExportImportTogglesService = ( ); const tagStore = new TagStore(db, eventBus, getLogger); const tagTypeStore = new TagTypeStore(db, eventBus, getLogger); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, - ); + const projectStore = new ProjectStore(db, eventBus, config); const featureTagStore = new FeatureTagStore(db, eventBus, getLogger); const strategyStore = new StrategyStore(db, getLogger); const contextFieldStore = new ContextFieldStore( @@ -172,7 +167,7 @@ export const deferredExportImportTogglesService = ( const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ); const eventStore = new EventStore(db, getLogger); const accessService = createAccessService(db, config); diff --git a/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts b/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts index a97b540b6043..ed907e9f894b 100644 --- a/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts +++ b/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts @@ -19,11 +19,11 @@ export const createFeatureLifecycleService = const { eventBus, getLogger } = config; const eventStore = new EventStore(db, getLogger); const featureLifecycleStore = new FeatureLifecycleStore(db); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ); const eventService = createEventsService(db, config); const featureLifecycleService = new FeatureLifecycleService( diff --git a/src/lib/features/feature-toggle/createFeatureToggleService.ts b/src/lib/features/feature-toggle/createFeatureToggleService.ts index d5d9d21bae87..b12eb3adfc17 100644 --- a/src/lib/features/feature-toggle/createFeatureToggleService.ts +++ b/src/lib/features/feature-toggle/createFeatureToggleService.ts @@ -80,19 +80,13 @@ export const createFeatureToggleService = ( const featureToggleClientStore = new FeatureToggleClientStore( db, eventBus, - getLogger, - flagResolver, - ); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, + config, ); + const projectStore = new ProjectStore(db, eventBus, config); const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ); const contextFieldStore = new ContextFieldStore( db, @@ -105,7 +99,7 @@ export const createFeatureToggleService = ( const accessStore = new AccessStore(db, eventBus, getLogger); const featureTagStore = new FeatureTagStore(db, eventBus, getLogger); const roleStore = new RoleStore(db, eventBus, getLogger); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const eventService = createEventsService(db, config); const groupService = new GroupService( { groupStore, accountStore }, diff --git a/src/lib/features/feature-toggle/feature-toggle-store.ts b/src/lib/features/feature-toggle/feature-toggle-store.ts index fe412fb9986a..861d1af92c5e 100644 --- a/src/lib/features/feature-toggle/feature-toggle-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-store.ts @@ -439,6 +439,16 @@ export default class FeatureToggleStore implements IFeatureToggleStore { : queryBuilder.whereNull('archived_at'); }; + static filterByProjectsAccessibleByOss: Knex.QueryCallbackWithArgs = ( + queryBuilder: Knex.QueryBuilder, + isOss: boolean, + ) => { + if (isOss) { + return queryBuilder.andWhere('project', '=', 'default'); + } + return queryBuilder; + }; + rowToFeature(row: FeaturesTable): FeatureToggle { if (!row) { throw new NotFoundError('No feature flag found'); diff --git a/src/lib/features/instance-stats/createInstanceStatsService.ts b/src/lib/features/instance-stats/createInstanceStatsService.ts index f1718c149166..fb03d5ec8a1a 100644 --- a/src/lib/features/instance-stats/createInstanceStatsService.ts +++ b/src/lib/features/instance-stats/createInstanceStatsService.ts @@ -58,13 +58,8 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => { flagResolver, ); const userStore = new UserStore(db, getLogger, flagResolver); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, - ); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const projectStore = new ProjectStore(db, eventBus, config); + const environmentStore = new EnvironmentStore(db, eventBus, config); const strategyStore = new StrategyStore(db, getLogger); const contextFieldStore = new ContextFieldStore( db, diff --git a/src/lib/features/project-environments/createEnvironmentService.ts b/src/lib/features/project-environments/createEnvironmentService.ts index 9cbcb478c193..9a1dfbd6c626 100644 --- a/src/lib/features/project-environments/createEnvironmentService.ts +++ b/src/lib/features/project-environments/createEnvironmentService.ts @@ -21,21 +21,16 @@ export const createEnvironmentService = const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, - ); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, + config, ); + const projectStore = new ProjectStore(db, eventBus, config); const featureStrategiesStore = new FeatureStrategiesStore( db, eventBus, getLogger, flagResolver, ); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const eventService = createEventsService(db, config); return new EnvironmentService( { diff --git a/src/lib/features/project-environments/environment-store.ts b/src/lib/features/project-environments/environment-store.ts index 6838d6eb4a81..3da9522b0018 100644 --- a/src/lib/features/project-environments/environment-store.ts +++ b/src/lib/features/project-environments/environment-store.ts @@ -1,6 +1,6 @@ import type EventEmitter from 'events'; import type { Db } from '../../db/db'; -import type { Logger, LogProvider } from '../../logger'; +import type { Logger } from '../../logger'; import metricsHelper from '../../util/metrics-helper'; import { DB_TIME } from '../../metric-events'; import type { @@ -12,6 +12,7 @@ import NotFoundError from '../../error/notfound-error'; import type { IEnvironmentStore } from './environment-store-type'; import { snakeCaseKeys } from '../../util/snakeCase'; import type { CreateFeatureStrategySchema } from '../../openapi'; +import type { IUnleashConfig } from '../../types'; interface IEnvironmentsTable { name: string; @@ -104,11 +105,18 @@ export default class EnvironmentStore implements IEnvironmentStore { private db: Db; + private isOss: boolean; + private timer: (string) => any; - constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) { + constructor( + db: Db, + eventBus: EventEmitter, + { getLogger, isOss }: Pick, + ) { this.db = db; this.logger = getLogger('db/environment-store.ts'); + this.isOss = isOss; this.timer = (action) => metricsHelper.wrapTimer(eventBus, DB_TIME, { store: 'environment', @@ -148,9 +156,15 @@ export default class EnvironmentStore implements IEnvironmentStore { async get(key: string): Promise { const stopTimer = this.timer('get'); - const row = await this.db(TABLE) - .where({ name: key }) - .first(); + let keyQuery = this.db(TABLE).where({ name: key }); + if (this.isOss) { + keyQuery = keyQuery.whereIn('name', [ + 'default', + 'development', + 'production', + ]); + } + const row = await keyQuery.first(); stopTimer(); if (row) { return mapRow(row); @@ -169,6 +183,9 @@ export default class EnvironmentStore implements IEnvironmentStore { if (query) { qB = qB.where(query); } + if (this.isOss) { + qB = qB.whereIn('name', ['default', 'development', 'production']); + } const rows = await qB; stopTimer(); return rows.map(mapRow); @@ -196,6 +213,9 @@ export default class EnvironmentStore implements IEnvironmentStore { if (query) { qB = qB.where(query); } + if (this.isOss) { + qB = qB.whereIn('name', ['default', 'development', 'production']); + } const rows = await qB; stopTimer(); return rows.map(mapRowWithCounts); @@ -230,6 +250,13 @@ export default class EnvironmentStore implements IEnvironmentStore { if (query) { qB = qB.where(query); } + if (this.isOss) { + qB = qB.whereIn('environments.name', [ + 'default', + 'production', + 'development', + ]); + } const rows = await qB; stopTimer(); diff --git a/src/lib/features/project-insights/createProjectInsightsService.ts b/src/lib/features/project-insights/createProjectInsightsService.ts index 68b46ac3941f..f15c1d61ad07 100644 --- a/src/lib/features/project-insights/createProjectInsightsService.ts +++ b/src/lib/features/project-insights/createProjectInsightsService.ts @@ -16,12 +16,7 @@ export const createProjectInsightsService = ( config: IUnleashConfig, ): ProjectInsightsService => { const { eventBus, getLogger, flagResolver } = config; - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, - ); + const projectStore = new ProjectStore(db, eventBus, config); const featureToggleStore = new FeatureToggleStore( db, eventBus, diff --git a/src/lib/features/project-status/createProjectStatusService.ts b/src/lib/features/project-status/createProjectStatusService.ts index c8694775403d..165c641ce8cd 100644 --- a/src/lib/features/project-status/createProjectStatusService.ts +++ b/src/lib/features/project-status/createProjectStatusService.ts @@ -24,12 +24,7 @@ export const createProjectStatusService = ( config: IUnleashConfig, ): ProjectStatusService => { const eventStore = new EventStore(db, config.getLogger); - const projectStore = new ProjectStore( - db, - config.eventBus, - config.getLogger, - config.flagResolver, - ); + const projectStore = new ProjectStore(db, config.eventBus, config); const apiTokenStore = new ApiTokenStore( db, config.eventBus, diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts index d4a42888eba6..a753ca34224f 100644 --- a/src/lib/features/project/createProjectService.ts +++ b/src/lib/features/project/createProjectService.ts @@ -63,12 +63,7 @@ export const createProjectService = ( ): ProjectService => { const { eventBus, getLogger, flagResolver } = config; const eventStore = new EventStore(db, getLogger); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, - ); + const projectStore = new ProjectStore(db, eventBus, config); const projectOwnersReadModel = new ProjectOwnersReadModel(db); const projectFlagCreatorsReadModel = new ProjectFlagCreatorsReadModel(db); const groupStore = new GroupStore(db); @@ -79,11 +74,11 @@ export const createProjectService = ( flagResolver, ); const accountStore = new AccountStore(db, getLogger); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ); const projectStatsStore = new ProjectStatsStore(db, eventBus, getLogger); const accessService: AccessService = createAccessService(db, config); diff --git a/src/lib/features/project/project-store.ts b/src/lib/features/project/project-store.ts index 5df93193c63e..5011159825c4 100644 --- a/src/lib/features/project/project-store.ts +++ b/src/lib/features/project/project-store.ts @@ -1,4 +1,4 @@ -import type { Logger, LogProvider } from '../../logger'; +import type { Logger } from '../../logger'; import NotFoundError from '../../error/notfound-error'; import type { @@ -8,6 +8,7 @@ import type { IProjectApplication, IProjectApplications, IProjectUpdate, + IUnleashConfig, ProjectMode, } from '../../types'; import type { @@ -72,11 +73,16 @@ class ProjectStore implements IProjectStore { private timer: Function; + private isOss: boolean; + constructor( db: Db, eventBus: EventEmitter, - getLogger: LogProvider, - flagResolver: IFlagResolver, + { + getLogger, + flagResolver, + isOss, + }: Pick, ) { this.db = db; this.logger = getLogger('project-store.ts'); @@ -86,6 +92,7 @@ class ProjectStore implements IProjectStore { action, }); this.flagResolver = flagResolver; + this.isOss = isOss; } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -122,6 +129,9 @@ class ProjectStore implements IProjectStore { .orderBy('name', 'asc'); projects = projects.where(`${TABLE}.archived_at`, null); + if (this.isOss) { + projects = projects.where('id', 'default'); + } const rows = await projects; diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index 792cb22dc874..b4919ad34152 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -143,6 +143,7 @@ export interface IUnleashOptions { metricsRateLimiting?: Partial; dailyMetricsStorageDays?: number; rateLimiting?: Partial; + isOss?: boolean; resourceLimits?: Partial< Pick< ResourceLimitsSchema, @@ -273,6 +274,7 @@ export interface IUnleashConfig { publicFolder?: string; disableScheduler?: boolean; isEnterprise: boolean; + isOss: boolean; rateLimiting: IRateLimiting; feedbackUriPath?: string; openAIAPIKey?: string; diff --git a/src/test/e2e/api/admin/environment-oss.e2e.test.ts b/src/test/e2e/api/admin/environment-oss.e2e.test.ts new file mode 100644 index 000000000000..4de671b7d853 --- /dev/null +++ b/src/test/e2e/api/admin/environment-oss.e2e.test.ts @@ -0,0 +1,68 @@ +import { + type IUnleashTest, + setupAppWithCustomConfig, +} from '../../helpers/test-helper'; +import dbInit, { type ITestDb } from '../../helpers/database-init'; +import getLogger from '../../../fixtures/no-logger'; + +let app: IUnleashTest; +let db: ITestDb; + +beforeAll(async () => { + db = await dbInit('environment_api_is_oss_serial', getLogger, { + isOss: true, + }); + app = await setupAppWithCustomConfig( + db.stores, + { + experimental: { + flags: { + strictSchemaValidation: true, + }, + }, + isOss: true, + }, + db.rawDatabase, + ); + await db.stores.environmentStore.create({ + name: 'development', + type: 'development', + enabled: true, + }); + await db.stores.environmentStore.create({ + name: 'production', + type: 'production', + enabled: true, + }); + await db.stores.environmentStore.create({ + name: 'customenvironment', + type: 'production', + enabled: true, + }); + await db.stores.environmentStore.create({ + name: 'customenvironment2', + type: 'production', + enabled: true, + }); + await db.stores.environmentStore.create({ + name: 'customenvironment3', + type: 'production', + enabled: true, + }); +}); + +afterAll(async () => { + await app.destroy(); + await db.destroy(); +}); + +test('querying environments in OSS only returns environments that are included in oss', async () => { + await app.request + .get('/api/admin/environments') + .expect(200) + .expect((res) => { + expect(res.body.environments).toHaveLength(3); + const names = res.body.environments.map((env) => env.name); + expect(names).toEqual(['default', 'development', 'production']); + }); +}); diff --git a/src/test/e2e/api/client/feature-oss.e2e.test.ts b/src/test/e2e/api/client/feature-oss.e2e.test.ts new file mode 100644 index 000000000000..c54c8308980d --- /dev/null +++ b/src/test/e2e/api/client/feature-oss.e2e.test.ts @@ -0,0 +1,82 @@ +import getLogger from '../../../fixtures/no-logger'; +import { + type IUnleashTest, + setupAppWithCustomConfig, +} from '../../helpers/test-helper'; +import dbInit from '../../helpers/database-init'; +import type { ITestDb } from '../../helpers/database-init'; +import type { IAuditUser, IUser } from '../../../../lib/types'; + +let app: IUnleashTest; +let db: ITestDb; +let testUser: IUser; +const auditUser = { + username: 'audituser', + id: -42, + ip: 'localhost', +} as IAuditUser; + +let userIndex = 0; +const createUser = async (role?: number) => { + const name = `User ${userIndex}`; + const email = `user-${userIndex}@getunleash.io`; + userIndex++; + + const { userStore } = db.stores; + return userStore.insert({ name, email }); +}; + +beforeAll(async () => { + db = await dbInit('feature_api_client_is_oss', getLogger, { isOss: true }); + app = await setupAppWithCustomConfig( + db.stores, + { + experimental: { + flags: { + strictSchemaValidation: true, + }, + }, + isOss: true, + }, + db.rawDatabase, + ); + testUser = await createUser(); + await app.services.projectService.createProject( + { id: 'secondproject', name: 'Second project not returned when oss' }, + testUser, + auditUser, + ); + await app.services.featureToggleService.createFeatureToggle( + 'secondproject', + { + name: 'my.feature.toggle', + }, + auditUser, + true, + ); + await app.services.featureToggleService.createFeatureToggle( + 'default', + { + name: 'my.default.toggle', + }, + auditUser, + true, + ); +}); +afterAll(async () => { + await app.destroy(); + await db.destroy(); +}); + +describe('OSS downgrade', () => { + test('features created in projects other than default is not visible in client endpoint', async () => { + return app.request + .get('/api/client/features') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.features).toHaveLength(1); + expect(res.body.features[0].name).toBe('my.default.toggle'); + }); + }); +});