diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/EditableConstraint/EditableConstraint.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/EditableConstraint/EditableConstraint.tsx index 937dc0a034..f70a2cf7f4 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/EditableConstraint/EditableConstraint.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/EditableConstraint/EditableConstraint.tsx @@ -258,9 +258,19 @@ export const EditableConstraint: FC = ({ return null; } - const constraintNameOptions = context.map((context) => { - return { key: context.name, label: context.name }; - }); + const extantContextFieldNames = context.map((context) => context.name); + const contextFieldHasBeenDeleted = !extantContextFieldNames.includes( + localConstraint.contextName, + ); + + const availableContextFieldNames = contextFieldHasBeenDeleted + ? [...extantContextFieldNames, localConstraint.contextName].toSorted() + : extantContextFieldNames; + + const contextFieldOptions = availableContextFieldNames.map((option) => ({ + key: option, + label: option, + })); return ( @@ -272,7 +282,7 @@ export const EditableConstraint: FC = ({ id='context-field-select' name='contextName' label='Context Field' - options={constraintNameOptions} + options={contextFieldOptions} value={contextName || ''} onChange={(contextField) => updateConstraint({ diff --git a/src/lib/features/context/context.ts b/src/lib/features/context/context.ts index b33d7e71ee..2d719504e1 100644 --- a/src/lib/features/context/context.ts +++ b/src/lib/features/context/context.ts @@ -245,7 +245,7 @@ export class ContextController extends Controller { tags: ['Context'], summary: 'Validate a context field', description: - 'Check whether the provided data can be used to create a context field. If the data is not valid, ...?', + 'Check whether the provided data can be used to create a context field. If the data is not valid, returns a 400 status code with the reason why it is not valid.', operationId: 'validate', requestBody: createRequestSchema('nameSchema'), responses: { diff --git a/src/lib/features/feature-toggle/feature-toggle-service.ts b/src/lib/features/feature-toggle/feature-toggle-service.ts index 350c46d3f6..7d4c483c66 100644 --- a/src/lib/features/feature-toggle/feature-toggle-service.ts +++ b/src/lib/features/feature-toggle/feature-toggle-service.ts @@ -497,10 +497,6 @@ export class FeatureToggleService { async validateConstraint(input: IConstraint): Promise { const constraint = await constraintSchema.validateAsync(input); const { operator } = constraint; - const contextDefinition = await this.contextFieldStore.get( - constraint.contextName, - ); - if (oneOf(NUM_OPERATORS, operator)) { await validateNumber(constraint.value); } @@ -519,20 +515,26 @@ export class FeatureToggleService { await validateDate(constraint.value); } - if ( - contextDefinition?.legalValues && - contextDefinition.legalValues.length > 0 - ) { - const valuesToValidate = oneOf( - [...DATE_OPERATORS, ...SEMVER_OPERATORS, ...NUM_OPERATORS], - operator, - ) - ? constraint.value - : constraint.values; - validateLegalValues( - contextDefinition.legalValues, - valuesToValidate, + if (await this.contextFieldStore.exists(constraint.contextName)) { + const contextDefinition = await this.contextFieldStore.get( + constraint.contextName, ); + + if ( + contextDefinition?.legalValues && + contextDefinition.legalValues.length > 0 + ) { + const valuesToValidate = oneOf( + [...DATE_OPERATORS, ...SEMVER_OPERATORS, ...NUM_OPERATORS], + operator, + ) + ? constraint.value + : constraint.values; + validateLegalValues( + contextDefinition.legalValues, + valuesToValidate, + ); + } } return constraint; diff --git a/src/test/e2e/api/admin/constraints.e2e.test.ts b/src/test/e2e/api/admin/constraints.e2e.test.ts index bf7c398cac..2e337dec73 100644 --- a/src/test/e2e/api/admin/constraints.e2e.test.ts +++ b/src/test/e2e/api/admin/constraints.e2e.test.ts @@ -43,3 +43,25 @@ test('should accept valid constraints', async () => { .send({ contextName: 'environment', operator: 'IN', values: ['a'] }) .expect(204); }); + +test('should allow unknown constraints if their values are valid', async () => { + await app.request + .post(PATH) + .send({ + contextName: 'not-a-default-context-value', + operator: 'NUM_EQ', + value: 1, + }) + .expect(204); +}); + +test('should block unknown constraints if their values are invalid', async () => { + await app.request + .post(PATH) + .send({ + contextName: 'not-a-default-context-value', + operator: 'IN', + value: 1, + }) + .expect(400); +});