diff --git a/src/lib/openapi/index.ts b/src/lib/openapi/index.ts index 47c31e2713..5bf9660996 100644 --- a/src/lib/openapi/index.ts +++ b/src/lib/openapi/index.ts @@ -7,8 +7,8 @@ import { contextFieldSchema } from './spec/context-field-schema'; import { contextFieldsSchema } from './spec/context-fields-schema'; import { createApiTokenSchema } from './spec/create-api-token-schema'; import { createFeatureSchema } from './spec/create-feature-schema'; -import { createStrategySchema } from './spec/create-strategy-schema'; import { createUserSchema } from './spec/create-user-schema'; +import { createFeatureStrategySchema } from './spec/create-feature-strategy-schema'; import { environmentSchema } from './spec/environment-schema'; import { environmentsSchema } from './spec/environments-schema'; import { featureEnvironmentSchema } from './spec/feature-environment-schema'; @@ -40,14 +40,13 @@ import { projectsSchema } from './spec/projects-schema'; import { roleSchema } from './spec/role-schema'; import { sortOrderSchema } from './spec/sort-order-schema'; import { splashSchema } from './spec/splash-schema'; -import { strategySchema } from './spec/strategy-schema'; import { tagSchema } from './spec/tag-schema'; import { tagsSchema } from './spec/tags-schema'; import { tagTypeSchema } from './spec/tag-type-schema'; import { tagTypesSchema } from './spec/tag-types-schema'; import { uiConfigSchema } from './spec/ui-config-schema'; import { updateFeatureSchema } from './spec/update-feature-schema'; -import { updateStrategySchema } from './spec/update-strategy-schema'; +import { updateFeatureStrategySchema } from './spec/update-feature-strategy-schema'; import { updateApiTokenSchema } from './spec/update-api-token-schema'; import { updateTagTypeSchema } from './spec/update-tag-type-schema'; import { upsertContextFieldSchema } from './spec/upsert-context-field-schema'; @@ -76,6 +75,9 @@ import { stateSchema } from './spec/state-schema'; import { featureTagSchema } from './spec/feature-tag-schema'; import { exportParametersSchema } from './spec/export-parameters-schema'; import { emailSchema } from './spec/email-schema'; +import { strategySchema } from './spec/strategy-schema'; +import { strategiesSchema } from './spec/strategies-schema'; +import { upsertStrategySchema } from './spec/upsert-strategy-schema'; // All schemas in `openapi/spec` should be listed here. export const schemas = { @@ -94,7 +96,7 @@ export const schemas = { contextFieldsSchema, createApiTokenSchema, createFeatureSchema, - createStrategySchema, + createFeatureStrategySchema, createUserSchema, emailSchema, environmentSchema, @@ -132,6 +134,7 @@ export const schemas = { sortOrderSchema, splashSchema, stateSchema, + strategiesSchema, strategySchema, tagSchema, tagWithVersionSchema, @@ -141,10 +144,11 @@ export const schemas = { tokenUserSchema, uiConfigSchema, updateFeatureSchema, - updateStrategySchema, + updateFeatureStrategySchema, updateApiTokenSchema, updateTagTypeSchema, upsertContextFieldSchema, + upsertStrategySchema, validatePasswordSchema, validateTagTypeSchema, updateUserSchema, diff --git a/src/lib/openapi/spec/__snapshots__/feature-environment-schema.test.ts.snap b/src/lib/openapi/spec/__snapshots__/feature-environment-schema.test.ts.snap new file mode 100644 index 0000000000..28e0fd54af --- /dev/null +++ b/src/lib/openapi/spec/__snapshots__/feature-environment-schema.test.ts.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`featureEnvironmentSchema empty 1`] = ` +Object { + "data": Object {}, + "errors": Array [ + Object { + "instancePath": "", + "keyword": "required", + "message": "must have required property 'name'", + "params": Object { + "missingProperty": "name", + }, + "schemaPath": "#/required", + }, + ], + "schema": "#/components/schemas/featureEnvironmentSchema", +} +`; diff --git a/src/lib/openapi/spec/__snapshots__/feature-schema.test.ts.snap b/src/lib/openapi/spec/__snapshots__/feature-schema.test.ts.snap index cb7b10a263..02deeb4671 100644 --- a/src/lib/openapi/spec/__snapshots__/feature-schema.test.ts.snap +++ b/src/lib/openapi/spec/__snapshots__/feature-schema.test.ts.snap @@ -17,13 +17,13 @@ Object { }, "errors": Array [ Object { - "instancePath": "/strategies/0/constraints/0", + "instancePath": "/strategies/0", "keyword": "required", - "message": "must have required property 'operator'", + "message": "must have required property 'id'", "params": Object { - "missingProperty": "operator", + "missingProperty": "id", }, - "schemaPath": "#/components/schemas/constraintSchema/required", + "schemaPath": "#/required", }, ], "schema": "#/components/schemas/featureSchema", diff --git a/src/lib/openapi/spec/__snapshots__/strategy-schema.test.ts.snap b/src/lib/openapi/spec/__snapshots__/strategy-schema.test.ts.snap new file mode 100644 index 0000000000..cecaf55941 --- /dev/null +++ b/src/lib/openapi/spec/__snapshots__/strategy-schema.test.ts.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`strategySchema 1`] = ` +Object { + "data": Object {}, + "errors": Array [ + Object { + "instancePath": "", + "keyword": "required", + "message": "must have required property 'name'", + "params": Object { + "missingProperty": "name", + }, + "schemaPath": "#/required", + }, + ], + "schema": "#/components/schemas/strategySchema", +} +`; diff --git a/src/lib/openapi/spec/create-strategy-schema.ts b/src/lib/openapi/spec/create-feature-strategy-schema.ts similarity index 78% rename from src/lib/openapi/spec/create-strategy-schema.ts rename to src/lib/openapi/spec/create-feature-strategy-schema.ts index 177c194352..40ad81e566 100644 --- a/src/lib/openapi/spec/create-strategy-schema.ts +++ b/src/lib/openapi/spec/create-feature-strategy-schema.ts @@ -2,8 +2,8 @@ import { FromSchema } from 'json-schema-to-ts'; import { parametersSchema } from './parameters-schema'; import { constraintSchema } from './constraint-schema'; -export const createStrategySchema = { - $id: '#/components/schemas/createStrategySchema', +export const createFeatureStrategySchema = { + $id: '#/components/schemas/createFeatureStrategySchema', type: 'object', required: ['name'], properties: { @@ -31,4 +31,6 @@ export const createStrategySchema = { }, } as const; -export type CreateStrategySchema = FromSchema; +export type CreateFeatureStrategySchema = FromSchema< + typeof createFeatureStrategySchema +>; diff --git a/src/lib/openapi/spec/feature-environment-schema.test.ts b/src/lib/openapi/spec/feature-environment-schema.test.ts new file mode 100644 index 0000000000..629118d23e --- /dev/null +++ b/src/lib/openapi/spec/feature-environment-schema.test.ts @@ -0,0 +1,30 @@ +import { validateSchema } from '../validate'; +import { FeatureEnvironmentSchema } from './feature-environment-schema'; + +test('featureEnvironmentSchema', () => { + const data: FeatureEnvironmentSchema = { + name: '', + enabled: true, + strategies: [ + { + id: '', + featureName: '', + projectId: '', + environment: '', + strategyName: '', + constraints: [{ contextName: '', operator: 'IN' }], + parameters: { a: '' }, + }, + ], + }; + + expect( + validateSchema('#/components/schemas/featureEnvironmentSchema', data), + ).toBeUndefined(); +}); + +test('featureEnvironmentSchema empty', () => { + expect( + validateSchema('#/components/schemas/featureEnvironmentSchema', {}), + ).toMatchSnapshot(); +}); diff --git a/src/lib/openapi/spec/feature-environment-schema.ts b/src/lib/openapi/spec/feature-environment-schema.ts index f0cd821058..6c121094b8 100644 --- a/src/lib/openapi/spec/feature-environment-schema.ts +++ b/src/lib/openapi/spec/feature-environment-schema.ts @@ -1,5 +1,4 @@ import { FromSchema } from 'json-schema-to-ts'; -import { featureStrategySchema } from './feature-strategy-schema'; import { constraintSchema } from './constraint-schema'; import { parametersSchema } from './parameters-schema'; @@ -24,13 +23,55 @@ export const featureEnvironmentSchema = { strategies: { type: 'array', items: { - $ref: '#/components/schemas/featureStrategySchema', + type: 'object', + additionalProperties: false, + required: [ + 'id', + 'featureName', + 'projectId', + 'environment', + 'strategyName', + 'constraints', + 'parameters', + ], + properties: { + id: { + type: 'string', + }, + featureName: { + type: 'string', + }, + projectId: { + type: 'string', + }, + environment: { + type: 'string', + }, + strategyName: { + type: 'string', + }, + sortOrder: { + type: 'number', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + constraints: { + type: 'array', + items: { + $ref: '#/components/schemas/constraintSchema', + }, + }, + parameters: { + $ref: '#/components/schemas/parametersSchema', + }, + }, }, }, }, components: { schemas: { - featureStrategySchema, constraintSchema, parametersSchema, }, diff --git a/src/lib/openapi/spec/feature-schema.test.ts b/src/lib/openapi/spec/feature-schema.test.ts index f135755bfd..a1f9c7409a 100644 --- a/src/lib/openapi/spec/feature-schema.test.ts +++ b/src/lib/openapi/spec/feature-schema.test.ts @@ -6,6 +6,7 @@ test('featureSchema', () => { name: 'a', strategies: [ { + id: 'a', name: 'a', constraints: [ { diff --git a/src/lib/openapi/spec/feature-schema.ts b/src/lib/openapi/spec/feature-schema.ts index d0baceaafc..4809795136 100644 --- a/src/lib/openapi/spec/feature-schema.ts +++ b/src/lib/openapi/spec/feature-schema.ts @@ -1,10 +1,10 @@ import { FromSchema } from 'json-schema-to-ts'; import { variantSchema } from './variant-schema'; -import { strategySchema } from './strategy-schema'; import { constraintSchema } from './constraint-schema'; import { overrideSchema } from './override-schema'; import { parametersSchema } from './parameters-schema'; import { environmentSchema } from './environment-schema'; +import { featureStrategySchema } from './feature-strategy-schema'; export const featureSchema = { $id: '#/components/schemas/featureSchema', @@ -55,7 +55,7 @@ export const featureSchema = { strategies: { type: 'array', items: { - $ref: '#/components/schemas/strategySchema', + $ref: '#/components/schemas/featureStrategySchema', }, }, variants: { @@ -71,7 +71,7 @@ export const featureSchema = { environmentSchema, overrideSchema, parametersSchema, - strategySchema, + featureStrategySchema, variantSchema, }, }, diff --git a/src/lib/openapi/spec/feature-strategy-schema.ts b/src/lib/openapi/spec/feature-strategy-schema.ts index c03cd1cbda..652110661e 100644 --- a/src/lib/openapi/spec/feature-strategy-schema.ts +++ b/src/lib/openapi/spec/feature-strategy-schema.ts @@ -6,14 +6,7 @@ export const featureStrategySchema = { $id: '#/components/schemas/featureStrategySchema', type: 'object', additionalProperties: false, - required: [ - 'id', - 'featureName', - 'strategyName', - 'constraints', - 'parameters', - 'environment', - ], + required: ['name', 'id'], properties: { id: { type: 'string', @@ -21,23 +14,6 @@ export const featureStrategySchema = { name: { type: 'string', }, - createdAt: { - type: 'string', - format: 'date-time', - nullable: true, - }, - featureName: { - type: 'string', - }, - projectId: { - type: 'string', - }, - environment: { - type: 'string', - }, - strategyName: { - type: 'string', - }, sortOrder: { type: 'number', }, diff --git a/src/lib/openapi/spec/features-schema.ts b/src/lib/openapi/spec/features-schema.ts index df89c0bee0..2ba977b45a 100644 --- a/src/lib/openapi/spec/features-schema.ts +++ b/src/lib/openapi/spec/features-schema.ts @@ -4,7 +4,7 @@ import { parametersSchema } from './parameters-schema'; import { variantSchema } from './variant-schema'; import { overrideSchema } from './override-schema'; import { constraintSchema } from './constraint-schema'; -import { strategySchema } from './strategy-schema'; +import { featureStrategySchema } from './feature-strategy-schema'; import { environmentSchema } from './environment-schema'; export const featuresSchema = { @@ -30,7 +30,7 @@ export const featuresSchema = { featureSchema, overrideSchema, parametersSchema, - strategySchema, + featureStrategySchema, variantSchema, }, }, diff --git a/src/lib/openapi/spec/health-overview-schema.ts b/src/lib/openapi/spec/health-overview-schema.ts index 1b73fae3e9..ca75279126 100644 --- a/src/lib/openapi/spec/health-overview-schema.ts +++ b/src/lib/openapi/spec/health-overview-schema.ts @@ -2,7 +2,7 @@ import { FromSchema } from 'json-schema-to-ts'; import { parametersSchema } from './parameters-schema'; import { variantSchema } from './variant-schema'; import { overrideSchema } from './override-schema'; -import { strategySchema } from './strategy-schema'; +import { featureStrategySchema } from './feature-strategy-schema'; import { featureSchema } from './feature-schema'; import { constraintSchema } from './constraint-schema'; import { environmentSchema } from './environment-schema'; @@ -53,7 +53,7 @@ export const healthOverviewSchema = { featureSchema, overrideSchema, parametersSchema, - strategySchema, + featureStrategySchema, variantSchema, }, }, diff --git a/src/lib/openapi/spec/state-schema.ts b/src/lib/openapi/spec/state-schema.ts index 5292271be9..9d4261ccb7 100644 --- a/src/lib/openapi/spec/state-schema.ts +++ b/src/lib/openapi/spec/state-schema.ts @@ -1,6 +1,5 @@ import { FromSchema } from 'json-schema-to-ts'; import { featureSchema } from './feature-schema'; -import { strategySchema } from './strategy-schema'; import { tagSchema } from './tag-schema'; import { tagTypeSchema } from './tag-type-schema'; import { featureTagSchema } from './feature-tag-schema'; @@ -10,6 +9,7 @@ import { featureEnvironmentSchema } from './feature-environment-schema'; import { environmentSchema } from './environment-schema'; import { segmentSchema } from './segment-schema'; import { featureStrategySegmentSchema } from './feature-strategy-segment-schema'; +import { strategySchema } from './strategy-schema'; export const stateSchema = { $id: '#/components/schemas/stateSchema', @@ -90,7 +90,6 @@ export const stateSchema = { components: { schemas: { featureSchema, - strategySchema, tagSchema, tagTypeSchema, featureTagSchema, @@ -100,6 +99,7 @@ export const stateSchema = { environmentSchema, segmentSchema, featureStrategySegmentSchema, + strategySchema, }, }, } as const; diff --git a/src/lib/openapi/spec/strategies-schema.ts b/src/lib/openapi/spec/strategies-schema.ts new file mode 100644 index 0000000000..9d0acc3267 --- /dev/null +++ b/src/lib/openapi/spec/strategies-schema.ts @@ -0,0 +1,27 @@ +import { FromSchema } from 'json-schema-to-ts'; +import { strategySchema } from './strategy-schema'; + +export const strategiesSchema = { + $id: '#/components/schemas/strategiesSchema', + type: 'object', + additionalProperties: false, + required: ['version', 'strategies'], + properties: { + version: { + type: 'integer', + }, + strategies: { + type: 'array', + items: { + $ref: '#/components/schemas/strategySchema', + }, + }, + }, + components: { + schemas: { + strategySchema, + }, + }, +} as const; + +export type StrategiesSchema = FromSchema; diff --git a/src/lib/openapi/spec/strategy-schema.test.ts b/src/lib/openapi/spec/strategy-schema.test.ts new file mode 100644 index 0000000000..b12252891c --- /dev/null +++ b/src/lib/openapi/spec/strategy-schema.test.ts @@ -0,0 +1,28 @@ +import { validateSchema } from '../validate'; +import { StrategySchema } from './strategy-schema'; + +test('strategySchema', () => { + const data: StrategySchema = { + description: '', + name: '', + displayName: '', + editable: false, + deprecated: false, + parameters: [ + { + name: '', + type: '', + description: '', + required: true, + }, + ], + }; + + expect( + validateSchema('#/components/schemas/strategySchema', data), + ).toBeUndefined(); + + expect( + validateSchema('#/components/schemas/strategySchema', {}), + ).toMatchSnapshot(); +}); diff --git a/src/lib/openapi/spec/strategy-schema.ts b/src/lib/openapi/spec/strategy-schema.ts index 40d37b2e30..2908d48cc3 100644 --- a/src/lib/openapi/spec/strategy-schema.ts +++ b/src/lib/openapi/spec/strategy-schema.ts @@ -1,38 +1,57 @@ import { FromSchema } from 'json-schema-to-ts'; -import { constraintSchema } from './constraint-schema'; -import { parametersSchema } from './parameters-schema'; export const strategySchema = { $id: '#/components/schemas/strategySchema', type: 'object', additionalProperties: false, - required: ['name'], + required: [ + 'name', + 'displayName', + 'description', + 'editable', + 'deprecated', + 'parameters', + ], properties: { - id: { - type: 'string', - }, name: { type: 'string', }, - sortOrder: { - type: 'number', + displayName: { + type: 'string', + nullable: true, }, - constraints: { - type: 'array', - items: { - $ref: '#/components/schemas/constraintSchema', - }, + description: { + type: 'string', + }, + editable: { + type: 'boolean', + }, + deprecated: { + type: 'boolean', }, parameters: { - $ref: '#/components/schemas/parametersSchema', - }, - }, - components: { - schemas: { - constraintSchema, - parametersSchema, + type: 'array', + items: { + type: 'object', + additionalProperties: false, + properties: { + name: { + type: 'string', + }, + type: { + type: 'string', + }, + description: { + type: 'string', + }, + required: { + type: 'boolean', + }, + }, + }, }, }, + components: {}, } as const; export type StrategySchema = FromSchema; diff --git a/src/lib/openapi/spec/update-feature-strategy-schema.ts b/src/lib/openapi/spec/update-feature-strategy-schema.ts new file mode 100644 index 0000000000..ae243cb305 --- /dev/null +++ b/src/lib/openapi/spec/update-feature-strategy-schema.ts @@ -0,0 +1,35 @@ +import { FromSchema } from 'json-schema-to-ts'; +import { parametersSchema } from './parameters-schema'; +import { constraintSchema } from './constraint-schema'; + +export const updateFeatureStrategySchema = { + $id: '#/components/schemas/updateFeatureStrategySchema', + type: 'object', + properties: { + name: { + type: 'string', + }, + sortOrder: { + type: 'number', + }, + constraints: { + type: 'array', + items: { + $ref: '#/components/schemas/constraintSchema', + }, + }, + parameters: { + $ref: '#/components/schemas/parametersSchema', + }, + }, + components: { + schemas: { + constraintSchema, + parametersSchema, + }, + }, +} as const; + +export type UpdateFeatureStrategySchema = FromSchema< + typeof updateFeatureStrategySchema +>; diff --git a/src/lib/openapi/spec/update-strategy-schema.ts b/src/lib/openapi/spec/update-strategy-schema.ts deleted file mode 100644 index 69ae8f9973..0000000000 --- a/src/lib/openapi/spec/update-strategy-schema.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FromSchema } from 'json-schema-to-ts'; -import { strategySchema } from './strategy-schema'; - -export const updateStrategySchema = { - ...strategySchema, - $id: '#/components/schemas/updateStrategySchema', - required: [], - components: {}, -} as const; - -export type UpdateStrategySchema = FromSchema; diff --git a/src/lib/openapi/spec/upsert-strategy-schema.ts b/src/lib/openapi/spec/upsert-strategy-schema.ts new file mode 100644 index 0000000000..3d9fc0fb5a --- /dev/null +++ b/src/lib/openapi/spec/upsert-strategy-schema.ts @@ -0,0 +1,41 @@ +import { FromSchema } from 'json-schema-to-ts'; + +export const upsertStrategySchema = { + $id: '#/components/schemas/upsertStrategySchema', + type: 'object', + required: ['name'], + properties: { + name: { + type: 'string', + }, + description: { + type: 'string', + }, + editable: { + type: 'boolean', + }, + parameters: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + }, + type: { + type: 'string', + }, + description: { + type: 'string', + }, + required: { + type: 'boolean', + }, + }, + }, + }, + }, + components: {}, +} as const; + +export type UpsertStrategySchema = FromSchema; diff --git a/src/lib/routes/admin-api/project/features.ts b/src/lib/routes/admin-api/project/features.ts index 25d2fb23ce..20f15d803b 100644 --- a/src/lib/routes/admin-api/project/features.ts +++ b/src/lib/routes/admin-api/project/features.ts @@ -22,15 +22,15 @@ import { featureSchema, FeatureSchema, } from '../../../openapi/spec/feature-schema'; -import { StrategySchema } from '../../../openapi/spec/strategy-schema'; +import { FeatureStrategySchema } from '../../../openapi/spec/feature-strategy-schema'; import { ParametersSchema } from '../../../openapi/spec/parameters-schema'; import { featuresSchema, FeaturesSchema, } from '../../../openapi/spec/features-schema'; import { UpdateFeatureSchema } from '../../../openapi/spec/update-feature-schema'; -import { UpdateStrategySchema } from '../../../openapi/spec/update-strategy-schema'; -import { CreateStrategySchema } from '../../../openapi/spec/create-strategy-schema'; +import { UpdateFeatureStrategySchema } from '../../../openapi/spec/update-feature-strategy-schema'; +import { CreateFeatureStrategySchema } from '../../../openapi/spec/create-feature-strategy-schema'; import { serializeDates } from '../../../types/serialize-dates'; import { OpenApiService } from '../../../services/openapi-service'; import { createRequestSchema, createResponseSchema } from '../../../openapi'; @@ -133,13 +133,15 @@ export default class ProjectFeaturesController extends Controller { this.route({ method: 'get', path: PATH_STRATEGIES, - handler: this.getStrategies, + handler: this.getFeatureStrategies, permission: NONE, middleware: [ openApiService.validPath({ tags: ['admin'], - operationId: 'getStrategies', - responses: { 200: createResponseSchema('strategySchema') }, + operationId: 'getFeatureStrategies', + responses: { + 200: createResponseSchema('featureStrategySchema'), + }, }), ], }); @@ -147,13 +149,15 @@ export default class ProjectFeaturesController extends Controller { this.route({ method: 'post', path: PATH_STRATEGIES, - handler: this.addStrategy, + handler: this.addFeatureStrategy, permission: CREATE_FEATURE_STRATEGY, middleware: [ openApiService.validPath({ tags: ['admin'], - operationId: 'addStrategy', - requestBody: createRequestSchema('createStrategySchema'), + operationId: 'addFeatureStrategy', + requestBody: createRequestSchema( + 'createFeatureStrategySchema', + ), responses: { 200: createResponseSchema('featureStrategySchema'), }, @@ -164,12 +168,12 @@ export default class ProjectFeaturesController extends Controller { this.route({ method: 'get', path: PATH_STRATEGY, - handler: this.getStrategy, + handler: this.getFeatureStrategy, permission: NONE, middleware: [ openApiService.validPath({ tags: ['admin'], - operationId: 'getStrategy', + operationId: 'getFeatureStrategy', responses: { 200: createResponseSchema('featureStrategySchema'), }, @@ -180,28 +184,31 @@ export default class ProjectFeaturesController extends Controller { this.route({ method: 'put', path: PATH_STRATEGY, - handler: this.updateStrategy, + handler: this.updateFeatureStrategy, permission: UPDATE_FEATURE_STRATEGY, middleware: [ openApiService.validPath({ tags: ['admin'], - operationId: 'updateStrategy', - requestBody: createRequestSchema('updateStrategySchema'), + operationId: 'updateFeatureStrategy', + requestBody: createRequestSchema( + 'updateFeatureStrategySchema', + ), responses: { 200: createResponseSchema('featureStrategySchema'), }, }), ], }); + this.route({ method: 'patch', path: PATH_STRATEGY, - handler: this.patchStrategy, + handler: this.patchFeatureStrategy, permission: UPDATE_FEATURE_STRATEGY, middleware: [ openApiService.validPath({ tags: ['admin'], - operationId: 'patchStrategy', + operationId: 'patchFeatureStrategy', requestBody: createRequestSchema('patchesSchema'), responses: { 200: createResponseSchema('featureStrategySchema'), @@ -213,11 +220,11 @@ export default class ProjectFeaturesController extends Controller { method: 'delete', path: PATH_STRATEGY, acceptAnyContentType: true, - handler: this.deleteStrategy, + handler: this.deleteFeatureStrategy, permission: DELETE_FEATURE_STRATEGY, middleware: [ openApiService.validPath({ - operationId: 'deleteStrategy', + operationId: 'deleteFeatureStrategy', tags: ['admin'], responses: { 200: emptyResponse }, }), @@ -516,9 +523,13 @@ export default class ProjectFeaturesController extends Controller { res.status(200).end(); } - async addStrategy( - req: IAuthRequest, - res: Response, + async addFeatureStrategy( + req: IAuthRequest< + FeatureStrategyParams, + any, + CreateFeatureStrategySchema + >, + res: Response, ): Promise { const { projectId, featureName, environment } = req.params; const userName = extractUsername(req); @@ -530,9 +541,9 @@ export default class ProjectFeaturesController extends Controller { res.status(200).json(strategy); } - async getStrategies( + async getFeatureStrategies( req: Request, - res: Response, + res: Response, ): Promise { const { projectId, featureName, environment } = req.params; const featureStrategies = @@ -544,9 +555,9 @@ export default class ProjectFeaturesController extends Controller { res.status(200).json(featureStrategies); } - async updateStrategy( - req: IAuthRequest, - res: Response, + async updateFeatureStrategy( + req: IAuthRequest, + res: Response, ): Promise { const { strategyId, environment, projectId, featureName } = req.params; const userName = extractUsername(req); @@ -559,9 +570,9 @@ export default class ProjectFeaturesController extends Controller { res.status(200).json(updatedStrategy); } - async patchStrategy( + async patchFeatureStrategy( req: IAuthRequest, - res: Response, + res: Response, ): Promise { const { strategyId, projectId, environment, featureName } = req.params; const userName = extractUsername(req); @@ -577,9 +588,9 @@ export default class ProjectFeaturesController extends Controller { res.status(200).json(updatedStrategy); } - async getStrategy( + async getFeatureStrategy( req: IAuthRequest, - res: Response, + res: Response, ): Promise { this.logger.info('Getting strategy'); const { strategyId } = req.params; @@ -588,7 +599,7 @@ export default class ProjectFeaturesController extends Controller { res.status(200).json(strategy); } - async deleteStrategy( + async deleteFeatureStrategy( req: IAuthRequest, res: Response, ): Promise { @@ -612,7 +623,7 @@ export default class ProjectFeaturesController extends Controller { { name: string; value: string | number }, any >, - res: Response, + res: Response, ): Promise { const { strategyId, environment, projectId, featureName } = req.params; const userName = extractUsername(req); diff --git a/src/lib/routes/admin-api/strategy.test.ts b/src/lib/routes/admin-api/strategy.test.ts index 76d5798fc3..6f154053e8 100644 --- a/src/lib/routes/admin-api/strategy.test.ts +++ b/src/lib/routes/admin-api/strategy.test.ts @@ -54,8 +54,8 @@ test('require a name when creating a new strategy', async () => { .send({}) .expect(400) .expect((res) => { - expect(res.body.details[0].message === '"name" is required').toBe( - true, + expect(res.body.validation[0].message).toEqual( + "should have required property 'name'", ); }); }); diff --git a/src/lib/routes/admin-api/strategy.ts b/src/lib/routes/admin-api/strategy.ts index 96d197164b..4f323f588e 100644 --- a/src/lib/routes/admin-api/strategy.ts +++ b/src/lib/routes/admin-api/strategy.ts @@ -2,18 +2,28 @@ import { IUnleashConfig } from '../../types/option'; import { IUnleashServices } from '../../types/services'; import StrategyService from '../../services/strategy-service'; import { Logger } from '../../logger'; - import Controller from '../controller'; - import { extractUsername } from '../../util/extract-user'; -import { handleErrors } from '../util'; import { DELETE_STRATEGY, CREATE_STRATEGY, UPDATE_STRATEGY, + NONE, } from '../../types/permissions'; import { Request, Response } from 'express'; import { IAuthRequest } from '../unleash-types'; +import { OpenApiService } from '../../services/openapi-service'; +import { emptyResponse } from '../../openapi/spec/empty-response'; +import { createRequestSchema, createResponseSchema } from '../../openapi'; +import { + strategySchema, + StrategySchema, +} from '../../openapi/spec/strategy-schema'; +import { + strategiesSchema, + StrategiesSchema, +} from '../../openapi/spec/strategies-schema'; +import { UpsertStrategySchema } from '../../openapi/spec/upsert-strategy-schema'; const version = 1; @@ -22,112 +32,210 @@ class StrategyController extends Controller { private strategyService: StrategyService; + private openApiService: OpenApiService; + constructor( config: IUnleashConfig, - { strategyService }: Pick, + { + strategyService, + openApiService, + }: Pick, ) { super(config); this.logger = config.getLogger('/admin-api/strategy.js'); this.strategyService = strategyService; + this.openApiService = openApiService; - this.get('/', this.getAllStrategies); - this.get('/:name', this.getStrategy); - this.delete('/:name', this.removeStrategy, DELETE_STRATEGY); - this.post('/', this.createStrategy, CREATE_STRATEGY); - this.put('/:strategyName', this.updateStrategy, UPDATE_STRATEGY); - this.post( - '/:strategyName/deprecate', - this.deprecateStrategy, - UPDATE_STRATEGY, - ); - this.post( - '/:strategyName/reactivate', - this.reactivateStrategy, - UPDATE_STRATEGY, + this.route({ + method: 'get', + path: '', + handler: this.getAllStrategies, + permission: NONE, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'getAllStrategies', + responses: { + 200: createResponseSchema('strategiesSchema'), + }, + }), + ], + }); + + this.route({ + method: 'get', + path: '/:name', + handler: this.getStrategy, + permission: NONE, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'getStrategy', + responses: { 200: createResponseSchema('strategySchema') }, + }), + ], + }); + + this.route({ + method: 'delete', + path: '/:name', + handler: this.removeStrategy, + permission: DELETE_STRATEGY, + acceptAnyContentType: true, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'removeStrategy', + responses: { 200: emptyResponse }, + }), + ], + }); + + this.route({ + method: 'post', + path: '', + handler: this.createStrategy, + permission: CREATE_STRATEGY, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'createStrategy', + requestBody: createRequestSchema('upsertStrategySchema'), + responses: { 201: emptyResponse }, + }), + ], + }); + + this.route({ + method: 'put', + path: '/:strategyName', + handler: this.updateStrategy, + permission: UPDATE_STRATEGY, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'updateStrategy', + requestBody: createRequestSchema('upsertStrategySchema'), + responses: { 200: emptyResponse }, + }), + ], + }); + + this.route({ + method: 'post', + path: '/:strategyName/deprecate', + handler: this.deprecateStrategy, + permission: UPDATE_STRATEGY, + acceptAnyContentType: true, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'deprecateStrategy', + responses: { 200: emptyResponse }, + }), + ], + }); + + this.route({ + method: 'post', + path: '/:strategyName/reactivate', + handler: this.reactivateStrategy, + permission: UPDATE_STRATEGY, + acceptAnyContentType: true, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'reactivateStrategy', + responses: { 200: emptyResponse }, + }), + ], + }); + } + + async getAllStrategies( + req: Request, + res: Response, + ): Promise { + const strategies = await this.strategyService.getStrategies(); + + this.openApiService.respondWithValidation( + 200, + res, + strategiesSchema.$id, + { version, strategies }, ); } - async getAllStrategies(req: Request, res: Response): Promise { - try { - const strategies = await this.strategyService.getStrategies(); - res.json({ version, strategies }); - } catch (err) { - handleErrors(res, this.logger, err); - } - } + async getStrategy( + req: Request, + res: Response, + ): Promise { + const strategy = await this.strategyService.getStrategy( + req.params.name, + ); - async getStrategy(req: Request, res: Response): Promise { - try { - const { name } = req.params; - const strategy = await this.strategyService.getStrategy(name); - res.json(strategy).end(); - } catch (err) { - res.status(404).json({ error: 'Could not find strategy' }); - } + this.openApiService.respondWithValidation( + 200, + res, + strategySchema.$id, + strategy, + ); } async removeStrategy(req: IAuthRequest, res: Response): Promise { const strategyName = req.params.name; const userName = extractUsername(req); - try { - await this.strategyService.removeStrategy(strategyName, userName); - res.status(200).end(); - } catch (error) { - handleErrors(res, this.logger, error); - } + await this.strategyService.removeStrategy(strategyName, userName); + res.status(200).end(); } - async createStrategy(req: IAuthRequest, res: Response): Promise { + async createStrategy( + req: IAuthRequest, + res: Response, + ): Promise { const userName = extractUsername(req); - try { - await this.strategyService.createStrategy(req.body, userName); - res.status(201).end(); - } catch (error) { - handleErrors(res, this.logger, error); - } + + await this.strategyService.createStrategy(req.body, userName); + res.status(201).end(); } - async updateStrategy(req: IAuthRequest, res: Response): Promise { + async updateStrategy( + req: IAuthRequest, + res: Response, + ): Promise { const userName = extractUsername(req); - try { - await this.strategyService.updateStrategy(req.body, userName); - res.status(200).end(); - } catch (error) { - handleErrors(res, this.logger, error); - } + + await this.strategyService.updateStrategy(req.body, userName); + res.status(200).end(); } - async deprecateStrategy(req: IAuthRequest, res: Response): Promise { + async deprecateStrategy( + req: IAuthRequest, + res: Response, + ): Promise { const userName = extractUsername(req); const { strategyName } = req.params; + if (strategyName === 'default') { res.status(403).end(); - } else { - try { - await this.strategyService.deprecateStrategy( - strategyName, - userName, - ); - res.status(200).end(); - } catch (error) { - handleErrors(res, this.logger, error); - } + return; } + + await this.strategyService.deprecateStrategy(strategyName, userName); + res.status(200).end(); } - async reactivateStrategy(req: IAuthRequest, res: Response): Promise { + async reactivateStrategy( + req: IAuthRequest, + res: Response, + ): Promise { const userName = extractUsername(req); const { strategyName } = req.params; - try { - await this.strategyService.reactivateStrategy( - strategyName, - userName, - ); - res.status(200).end(); - } catch (error) { - handleErrors(res, this.logger, error); - } + + await this.strategyService.reactivateStrategy(strategyName, userName); + res.status(200).end(); } } + export default StrategyController; diff --git a/src/lib/services/feature-toggle-service.ts b/src/lib/services/feature-toggle-service.ts index 14cb64e3c7..caaeabbfbd 100644 --- a/src/lib/services/feature-toggle-service.ts +++ b/src/lib/services/feature-toggle-service.ts @@ -390,7 +390,7 @@ class FeatureToggleService { value: string | number, context: IFeatureStrategyContext, userName: string, - ): Promise { + ): Promise> { const { projectId, environment, featureName } = context; const existingStrategy = await this.featureStrategiesStore.get(id); @@ -466,7 +466,7 @@ class FeatureToggleService { project: string, featureName: string, environment: string = DEFAULT_ENV, - ): Promise { + ): Promise[]> { const hasEnv = await this.featureEnvironmentStore.featureHasEnvironment( environment, featureName, @@ -701,7 +701,7 @@ class FeatureToggleService { ); } - async getStrategy(strategyId: string): Promise { + async getStrategy(strategyId: string): Promise> { const strategy = await this.featureStrategiesStore.getStrategyById( strategyId, ); diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index 448d19242c..c934dfbadd 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -188,7 +188,7 @@ export interface ITag { type: string; } -export interface IParameterDefinition { +export interface IAddonParameterDefinition { name: string; displayName: string; type: string; @@ -203,7 +203,7 @@ export interface IAddonDefinition { displayName: string; documentationUrl: string; description: string; - parameters?: IParameterDefinition[]; + parameters?: IAddonParameterDefinition[]; events?: string[]; tagTypes?: ITagType[]; } diff --git a/src/lib/types/stores/strategy-store.ts b/src/lib/types/stores/strategy-store.ts index d041c964a8..583a7244cc 100644 --- a/src/lib/types/stores/strategy-store.ts +++ b/src/lib/types/stores/strategy-store.ts @@ -4,7 +4,7 @@ export interface IStrategy { name: string; editable: boolean; description: string; - parameters: object; + parameters: object[]; deprecated: boolean; displayName: string; } diff --git a/src/test/e2e/api/admin/feature.e2e.test.ts b/src/test/e2e/api/admin/feature.e2e.test.ts index 7b5bf207d2..ce3a84c181 100644 --- a/src/test/e2e/api/admin/feature.e2e.test.ts +++ b/src/test/e2e/api/admin/feature.e2e.test.ts @@ -7,9 +7,9 @@ import { } from '../../helpers/test-helper'; import getLogger from '../../../fixtures/no-logger'; import { DEFAULT_ENV } from '../../../../lib/util/constants'; -import { StrategySchema } from '../../../../lib/openapi/spec/strategy-schema'; import { FeatureSchema } from '../../../../lib/openapi/spec/feature-schema'; import { VariantSchema } from '../../../../lib/openapi/spec/variant-schema'; +import { FeatureStrategySchema } from '../../../../lib/openapi/spec/feature-strategy-schema'; let app: IUnleashTest; let db: ITestDb; @@ -26,7 +26,7 @@ beforeAll(async () => { const createToggle = async ( toggle: Omit, - strategy: Omit = defaultStrategy, + strategy: Omit = defaultStrategy, projectId: string = 'default', username: string = 'test', ) => { @@ -576,7 +576,7 @@ test('tagging a feature with an already existing tag should be a noop', async () test('can untag feature', async () => { expect.assertions(1); - const feature1Name = faker.lorem.slug(3); + const feature1Name = faker.datatype.uuid(); await app.request.post('/api/admin/features').send({ name: feature1Name, type: 'killswitch', @@ -584,7 +584,7 @@ test('can untag feature', async () => { strategies: [{ name: 'default' }], }); const tag = { - value: faker.lorem.slug(1), + value: faker.lorem.word(), type: 'simple', }; await app.request @@ -607,8 +607,8 @@ test('can untag feature', async () => { test('Can get features tagged by tag', async () => { expect.assertions(2); - const feature1Name = faker.helpers.slugify(faker.lorem.words(3)); - const feature2Name = faker.helpers.slugify(faker.lorem.words(3)); + const feature1Name = faker.datatype.uuid(); + const feature2Name = faker.datatype.uuid(); await app.request.post('/api/admin/features').send({ name: feature1Name, type: 'killswitch', @@ -637,8 +637,8 @@ test('Can get features tagged by tag', async () => { }); test('Can query for multiple tags using OR', async () => { expect.assertions(3); - const feature1Name = faker.helpers.slugify(faker.lorem.words(3)); - const feature2Name = faker.helpers.slugify(faker.lorem.words(3)); + const feature1Name = faker.datatype.uuid(); + const feature2Name = faker.datatype.uuid(); await app.request.post('/api/admin/features').send({ name: feature1Name, type: 'killswitch', @@ -678,8 +678,8 @@ test('Can query for multiple tags using OR', async () => { }); }); test('Querying with multiple filters ANDs the filters', async () => { - const feature1Name = `test.${faker.helpers.slugify(faker.hacker.phrase())}`; - const feature2Name = faker.helpers.slugify(faker.lorem.words()); + const feature1Name = `test.${faker.datatype.uuid()}`; + const feature2Name = faker.datatype.uuid(); await app.request.post('/api/admin/features').send({ name: feature1Name, @@ -729,7 +729,7 @@ test('Querying with multiple filters ANDs the filters', async () => { }); test('Tagging a feature with a tag it already has should return 409', async () => { - const feature1Name = `test.${faker.helpers.slugify(faker.lorem.words(3))}`; + const feature1Name = `test.${faker.datatype.uuid()}`; await app.request.post('/api/admin/features').send({ name: feature1Name, type: 'killswitch', diff --git a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap index 9705a96a96..aa949d1fb4 100644 --- a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap +++ b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap @@ -465,7 +465,7 @@ Object { ], "type": "object", }, - "createStrategySchema": Object { + "createFeatureStrategySchema": Object { "properties": Object { "constraints": Object { "items": Object { @@ -612,7 +612,50 @@ Object { }, "strategies": Object { "items": Object { - "$ref": "#/components/schemas/featureStrategySchema", + "additionalProperties": false, + "properties": Object { + "constraints": Object { + "items": Object { + "$ref": "#/components/schemas/constraintSchema", + }, + "type": "array", + }, + "createdAt": Object { + "format": "date-time", + "type": "string", + }, + "environment": Object { + "type": "string", + }, + "featureName": Object { + "type": "string", + }, + "id": Object { + "type": "string", + }, + "parameters": Object { + "$ref": "#/components/schemas/parametersSchema", + }, + "projectId": Object { + "type": "string", + }, + "sortOrder": Object { + "type": "number", + }, + "strategyName": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "featureName", + "projectId", + "environment", + "strategyName", + "constraints", + "parameters", + ], + "type": "object", }, "type": "array", }, @@ -668,7 +711,7 @@ Object { }, "strategies": Object { "items": Object { - "$ref": "#/components/schemas/strategySchema", + "$ref": "#/components/schemas/featureStrategySchema", }, "type": "array", }, @@ -696,17 +739,6 @@ Object { }, "type": "array", }, - "createdAt": Object { - "format": "date-time", - "nullable": true, - "type": "string", - }, - "environment": Object { - "type": "string", - }, - "featureName": Object { - "type": "string", - }, "id": Object { "type": "string", }, @@ -716,23 +748,13 @@ Object { "parameters": Object { "$ref": "#/components/schemas/parametersSchema", }, - "projectId": Object { - "type": "string", - }, "sortOrder": Object { "type": "number", }, - "strategyName": Object { - "type": "string", - }, }, "required": Array [ + "name", "id", - "featureName", - "strategyName", - "constraints", - "parameters", - "environment", ], "type": "object", }, @@ -1383,30 +1405,73 @@ Object { ], "type": "object", }, - "strategySchema": Object { + "strategiesSchema": Object { "additionalProperties": false, "properties": Object { - "constraints": Object { + "strategies": Object { "items": Object { - "$ref": "#/components/schemas/constraintSchema", + "$ref": "#/components/schemas/strategySchema", }, "type": "array", }, - "id": Object { + "version": Object { + "type": "integer", + }, + }, + "required": Array [ + "version", + "strategies", + ], + "type": "object", + }, + "strategySchema": Object { + "additionalProperties": false, + "properties": Object { + "deprecated": Object { + "type": "boolean", + }, + "description": Object { "type": "string", }, + "displayName": Object { + "nullable": true, + "type": "string", + }, + "editable": Object { + "type": "boolean", + }, "name": Object { "type": "string", }, "parameters": Object { - "$ref": "#/components/schemas/parametersSchema", - }, - "sortOrder": Object { - "type": "number", + "items": Object { + "additionalProperties": false, + "properties": Object { + "description": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "required": Object { + "type": "boolean", + }, + "type": Object { + "type": "string", + }, + }, + "type": "object", + }, + "type": "array", }, }, "required": Array [ "name", + "displayName", + "description", + "editable", + "deprecated", + "parameters", ], "type": "object", }, @@ -1647,8 +1712,7 @@ Object { ], "type": "object", }, - "updateStrategySchema": Object { - "additionalProperties": false, + "updateFeatureStrategySchema": Object { "properties": Object { "constraints": Object { "items": Object { @@ -1656,9 +1720,6 @@ Object { }, "type": "array", }, - "id": Object { - "type": "string", - }, "name": Object { "type": "string", }, @@ -1669,7 +1730,6 @@ Object { "type": "number", }, }, - "required": Array [], "type": "object", }, "updateTagTypeSchema": Object { @@ -1724,6 +1784,43 @@ Object { ], "type": "object", }, + "upsertStrategySchema": Object { + "properties": Object { + "description": Object { + "type": "string", + }, + "editable": Object { + "type": "boolean", + }, + "name": Object { + "type": "string", + }, + "parameters": Object { + "items": Object { + "properties": Object { + "description": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "required": Object { + "type": "boolean", + }, + "type": Object { + "type": "string", + }, + }, + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "name", + ], + "type": "object", + }, "userSchema": Object { "additionalProperties": false, "properties": Object { @@ -3434,7 +3531,7 @@ Object { }, "/api/admin/projects/{projectId}/features/{featureName}/environments/{environment}/strategies": Object { "get": Object { - "operationId": "getStrategies", + "operationId": "getFeatureStrategies", "parameters": Array [ Object { "in": "path", @@ -3466,11 +3563,11 @@ Object { "content": Object { "application/json": Object { "schema": Object { - "$ref": "#/components/schemas/strategySchema", + "$ref": "#/components/schemas/featureStrategySchema", }, }, }, - "description": "strategySchema", + "description": "featureStrategySchema", }, }, "tags": Array [ @@ -3478,7 +3575,7 @@ Object { ], }, "post": Object { - "operationId": "addStrategy", + "operationId": "addFeatureStrategy", "parameters": Array [ Object { "in": "path", @@ -3509,11 +3606,11 @@ Object { "content": Object { "application/json": Object { "schema": Object { - "$ref": "#/components/schemas/createStrategySchema", + "$ref": "#/components/schemas/createFeatureStrategySchema", }, }, }, - "description": "createStrategySchema", + "description": "createFeatureStrategySchema", "required": true, }, "responses": Object { @@ -3535,7 +3632,7 @@ Object { }, "/api/admin/projects/{projectId}/features/{featureName}/environments/{environment}/strategies/{strategyId}": Object { "delete": Object { - "operationId": "deleteStrategy", + "operationId": "deleteFeatureStrategy", "parameters": Array [ Object { "in": "path", @@ -3580,7 +3677,7 @@ Object { ], }, "get": Object { - "operationId": "getStrategy", + "operationId": "getFeatureStrategy", "parameters": Array [ Object { "in": "path", @@ -3632,7 +3729,7 @@ Object { ], }, "patch": Object { - "operationId": "patchStrategy", + "operationId": "patchFeatureStrategy", "parameters": Array [ Object { "in": "path", @@ -3695,7 +3792,7 @@ Object { ], }, "put": Object { - "operationId": "updateStrategy", + "operationId": "updateFeatureStrategy", "parameters": Array [ Object { "in": "path", @@ -3734,11 +3831,11 @@ Object { "content": Object { "application/json": Object { "schema": Object { - "$ref": "#/components/schemas/updateStrategySchema", + "$ref": "#/components/schemas/updateFeatureStrategySchema", }, }, }, - "description": "updateStrategySchema", + "description": "updateFeatureStrategySchema", "required": true, }, "responses": Object { @@ -3999,6 +4096,179 @@ Object { ], }, }, + "/api/admin/strategies": Object { + "get": Object { + "operationId": "getAllStrategies", + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/strategiesSchema", + }, + }, + }, + "description": "strategiesSchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + "post": Object { + "operationId": "createStrategy", + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/upsertStrategySchema", + }, + }, + }, + "description": "upsertStrategySchema", + "required": true, + }, + "responses": Object { + "201": Object { + "description": "emptyResponse", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, + "/api/admin/strategies/{name}": Object { + "delete": Object { + "operationId": "removeStrategy", + "parameters": Array [ + Object { + "in": "path", + "name": "name", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "description": "emptyResponse", + }, + }, + "tags": Array [ + "admin", + ], + }, + "get": Object { + "operationId": "getStrategy", + "parameters": Array [ + Object { + "in": "path", + "name": "name", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/strategySchema", + }, + }, + }, + "description": "strategySchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, + "/api/admin/strategies/{strategyName}": Object { + "put": Object { + "operationId": "updateStrategy", + "parameters": Array [ + Object { + "in": "path", + "name": "strategyName", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/upsertStrategySchema", + }, + }, + }, + "description": "upsertStrategySchema", + "required": true, + }, + "responses": Object { + "200": Object { + "description": "emptyResponse", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, + "/api/admin/strategies/{strategyName}/deprecate": Object { + "post": Object { + "operationId": "deprecateStrategy", + "parameters": Array [ + Object { + "in": "path", + "name": "strategyName", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "description": "emptyResponse", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, + "/api/admin/strategies/{strategyName}/reactivate": Object { + "post": Object { + "operationId": "reactivateStrategy", + "parameters": Array [ + Object { + "in": "path", + "name": "strategyName", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "description": "emptyResponse", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, "/api/admin/tag-types": Object { "get": Object { "operationId": "getTagTypes", diff --git a/src/test/e2e/services/client-metrics-service.e2e.test.ts b/src/test/e2e/services/client-metrics-service.e2e.test.ts index fdb1962aef..8be77210cb 100644 --- a/src/test/e2e/services/client-metrics-service.e2e.test.ts +++ b/src/test/e2e/services/client-metrics-service.e2e.test.ts @@ -43,7 +43,7 @@ test('Apps registered should be announced', async () => { color: faker.internet.color(), }; const differentClient = { - appName: faker.lorem.slug(2), + appName: faker.datatype.uuid(), instanceId: faker.datatype.uuid(), strategies: ['default'], started: Date.now(), 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 9d384c962c..12074791b6 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 @@ -2,8 +2,8 @@ import FeatureToggleService from '../../../lib/services/feature-toggle-service'; import { createTestConfig } from '../../config/test-config'; import dbInit from '../helpers/database-init'; import { DEFAULT_ENV } from '../../../lib/util/constants'; -import { StrategySchema } from '../../../lib/openapi/spec/strategy-schema'; import { SegmentService } from '../../../lib/services/segment-service'; +import { FeatureStrategySchema } from '../../../lib/openapi/spec/feature-strategy-schema'; let stores; let db; @@ -30,7 +30,7 @@ afterAll(async () => { test('Should create feature toggle strategy configuration', async () => { const projectId = 'default'; const username = 'feature-toggle'; - const config: Omit = { + const config: Omit = { name: 'default', constraints: [], parameters: {}, @@ -58,7 +58,7 @@ test('Should be able to update existing strategy configuration', async () => { const projectId = 'default'; const username = 'existing-strategy'; const featureName = 'update-existing-strategy'; - const config: Omit = { + const config: Omit = { name: 'default', constraints: [], parameters: {}, @@ -93,7 +93,7 @@ test('Should be able to get strategy by id', async () => { const projectId = 'default'; const userName = 'strategy'; - const config: Omit = { + const config: Omit = { name: 'default', constraints: [], parameters: {}, diff --git a/src/test/fixtures/fake-strategies-store.ts b/src/test/fixtures/fake-strategies-store.ts index 11a225243f..7a5e416272 100644 --- a/src/test/fixtures/fake-strategies-store.ts +++ b/src/test/fixtures/fake-strategies-store.ts @@ -12,7 +12,7 @@ export default class FakeStrategiesStore implements IStrategyStore { description: 'default strategy', displayName: 'Default', editable: false, - parameters: {}, + parameters: [], deprecated: false, };