From 50408cd63b83beef49bc699b2d206dadbc791a26 Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Thu, 10 Mar 2022 14:05:16 +0100 Subject: [PATCH] feat: validate strategies --- src/lib/services/feature-toggle-service.ts | 17 ++++++++ .../util/validators/constraint-types.test.ts | 13 +++++++ src/lib/util/validators/constraint-types.ts | 4 +- .../feature-toggle-service-v2.e2e.test.ts | 39 +++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/lib/services/feature-toggle-service.ts b/src/lib/services/feature-toggle-service.ts index 3445a7629a..bde597735a 100644 --- a/src/lib/services/feature-toggle-service.ts +++ b/src/lib/services/feature-toggle-service.ts @@ -165,6 +165,14 @@ class FeatureToggleService { } } + async validateConstraints(constraints: IConstraint[]): Promise { + const validations = constraints.map((constraint) => { + return this.validateConstraint(constraint); + }); + + await Promise.all(validations); + } + async validateConstraint(constraint: IConstraint): Promise { const { operator } = constraint; await constraintSchema.validateAsync(constraint); @@ -272,6 +280,11 @@ class FeatureToggleService { ): Promise { const { featureName, projectId, environment } = context; await this.validateFeatureContext(context); + + if (strategyConfig.constraints.length > 0) { + await this.validateConstraints(strategyConfig.constraints); + } + try { const newFeatureStrategy = await this.featureStrategiesStore.createStrategyFeatureEnv({ @@ -334,6 +347,10 @@ class FeatureToggleService { updates, ); + if (updates.constraints?.length > 0) { + await this.validateConstraints(updates.constraints); + } + // Store event! const tags = await this.tagStore.getAllTagsForFeature(featureName); const data = this.featureStrategyToPublic(strategy); diff --git a/src/lib/util/validators/constraint-types.test.ts b/src/lib/util/validators/constraint-types.test.ts index 74945e27b2..3a73824c6f 100644 --- a/src/lib/util/validators/constraint-types.test.ts +++ b/src/lib/util/validators/constraint-types.test.ts @@ -39,6 +39,19 @@ test('semver validation should fail partial semver', () => { } }); +test('semver validation should fail with leading v', () => { + const leadingV = 'v1.2.0'; + expect.assertions(1); + + try { + validateSemver(leadingV); + } catch (e) { + expect(e.message).toBe( + `the provided value is not a valid semver format. The value provided was: ${leadingV}`, + ); + } +}); + /* Legal values tests */ test('should fail validation if value does not exist in single legal value', () => { const legalValues = ['100', '200', '300']; diff --git a/src/lib/util/validators/constraint-types.ts b/src/lib/util/validators/constraint-types.ts index 6cbf56e704..588dd38e4b 100644 --- a/src/lib/util/validators/constraint-types.ts +++ b/src/lib/util/validators/constraint-types.ts @@ -16,9 +16,11 @@ export const validateString = async (value: unknown): Promise => { }; export const validateSemver = (value: unknown): void => { + const cleanValue = semver.clean(value) === value; + const result = semver.valid(value); - if (result) return; + if (result && cleanValue) return; throw new BadDataError( `the provided value is not a valid semver format. The value provided was: ${value}`, ); 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 a7b7fd065f..f7d42ae498 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 @@ -149,3 +149,42 @@ test('should ignore name in the body when updating feature toggle', async () => expect(featureOne.description).toBe(`I'm changed`); expect(featureTwo.description).toBe('Second toggle'); }); + +test('Should validate co', async () => { + expect.assertions(1); + const projectId = 'default'; + const username = 'co-strategy'; + const featureName = 'co-validation'; + const config: Omit = { + name: 'default', + constraints: [ + { + operator: 'INVALID', + // @ts-expect-error + values: NaN, + contextName: 'Hello', + }, + ], + parameters: {}, + }; + + await service.createFeatureToggle( + projectId, + { + name: featureName, + }, + 'test', + ); + + try { + await service.createStrategy( + config, + { projectId, featureName, environment: DEFAULT_ENV }, + username, + ); + } catch (e) { + expect(e.details[0].message).toBe( + '"operator" must be one of [NOT_IN, IN, STR_ENDS_WITH, STR_STARTS_WITH, STR_CONTAINS, NUM_EQ, NUM_GT, NUM_GTE, NUM_LT, NUM_LTE, DATE_AFTER, DATE_BEFORE, SEMVER_EQ, SEMVER_GT, SEMVER_LT]', + ); + } +});