diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 5d7dd96361ff..481faa66c6f9 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -200,6 +200,7 @@ exports[`should create default config 1`] = ` "actionSetsPerProject": 5, "apiTokens": 2000, "constraintValues": 250, + "constraints": 30, "environments": 50, "featureEnvironmentStrategies": 30, "featureFlags": 5000, diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index 6af7cab43a45..3c1920e5d3ae 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -663,6 +663,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { options?.resourceLimits?.constraintValues || 250, ), ), + constraints: Math.max( + 0, + parseEnvVarNumber(process.env.UNLEASH_CONSTRAINTS_LIMIT, 30), + ), environments: parseEnvVarNumber( process.env.UNLEASH_ENVIRONMENTS_LIMIT, 50, diff --git a/src/lib/features/feature-toggle/feature-toggle-service.ts b/src/lib/features/feature-toggle/feature-toggle-service.ts index 4d51fbf39fe0..728e5696b147 100644 --- a/src/lib/features/feature-toggle/feature-toggle-service.ts +++ b/src/lib/features/feature-toggle/feature-toggle-service.ts @@ -390,16 +390,22 @@ class FeatureToggleService { validateConstraintValuesLimit(updatedConstrains: IConstraint[]) { if (!this.flagResolver.isEnabled('resourceLimits')) return; - const limit = this.resourceLimits.constraintValues; + const constraintsLimit = this.resourceLimits.constraints; + if (updatedConstrains.length > constraintsLimit) { + throw new ExceedsLimitError(`constraints`, constraintsLimit); + } + + const constraintValuesLimit = this.resourceLimits.constraintValues; + const constraintOverLimit = updatedConstrains.find( (constraint) => Array.isArray(constraint.values) && - constraint.values?.length > limit, + constraint.values?.length > constraintValuesLimit, ); if (constraintOverLimit) { throw new ExceedsLimitError( `content values for ${constraintOverLimit.contextName}`, - limit, + constraintValuesLimit, ); } } diff --git a/src/lib/features/feature-toggle/tests/feature-toggle-service.limit.test.ts b/src/lib/features/feature-toggle/tests/feature-toggle-service.limit.test.ts index 111692cd3ff8..cb0a572da7b3 100644 --- a/src/lib/features/feature-toggle/tests/feature-toggle-service.limit.test.ts +++ b/src/lib/features/feature-toggle/tests/feature-toggle-service.limit.test.ts @@ -47,6 +47,50 @@ describe('Strategy limits', () => { ); }); + test('Should not allow to exceed constraints limit', async () => { + const LIMIT = 1; + const { featureToggleService, featureToggleStore } = + createFakeFeatureToggleService({ + getLogger, + flagResolver: alwaysOnFlagResolver, + resourceLimits: { + constraints: LIMIT, + }, + } as unknown as IUnleashConfig); + + const addStrategy = (constraints: IConstraint[]) => + featureToggleService.unprotectedCreateStrategy( + { + name: 'default', + featureName: 'feature', + constraints: constraints, + } as IStrategyConfig, + { projectId: 'default', featureName: 'feature' } as any, + {} as IAuditUser, + ); + await featureToggleStore.create('default', { + name: 'feature', + createdByUserId: 1, + }); + + await expect( + addStrategy([ + { + values: ['1'], + operator: 'IN', + contextName: 'accountId', + }, + { + values: ['2'], + operator: 'IN', + contextName: 'accountId', + }, + ]), + ).rejects.toThrow( + "Failed to create constraints. You can't create more than the established limit of 1", + ); + }); + test('Should not allow to exceed constraint values limit', async () => { const LIMIT = 3; const { featureToggleService, featureToggleStore } = diff --git a/src/lib/openapi/spec/resource-limits-schema.ts b/src/lib/openapi/spec/resource-limits-schema.ts index f52281cb7f78..9eefc3042d39 100644 --- a/src/lib/openapi/spec/resource-limits-schema.ts +++ b/src/lib/openapi/spec/resource-limits-schema.ts @@ -20,6 +20,7 @@ export const resourceLimitsSchema = { 'apiTokens', 'segments', 'featureFlags', + 'constraints', ], additionalProperties: false, properties: { @@ -80,6 +81,12 @@ export const resourceLimitsSchema = { description: 'The maximum number of values for a single constraint.', }, + constraints: { + type: 'integer', + example: 30, + description: + 'The maximum number of constraints in a single strategy.', + }, environments: { type: 'integer', minimum: 1,