mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
refactor: add schemas to strategy controller (#1744)
* refactor: avoid duplicate feature strategy operationIds * refactor: fix flaky feature tests * refactor: remove duplicate controller error handling * refactor: unify feature strategy schemas * refactor: add schemas to strategy controller
This commit is contained in:
parent
e013a72ddd
commit
ac3f076a31
@ -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,
|
||||
|
@ -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",
|
||||
}
|
||||
`;
|
@ -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",
|
||||
|
@ -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",
|
||||
}
|
||||
`;
|
@ -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<typeof createStrategySchema>;
|
||||
export type CreateFeatureStrategySchema = FromSchema<
|
||||
typeof createFeatureStrategySchema
|
||||
>;
|
30
src/lib/openapi/spec/feature-environment-schema.test.ts
Normal file
30
src/lib/openapi/spec/feature-environment-schema.test.ts
Normal file
@ -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();
|
||||
});
|
@ -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,
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ test('featureSchema', () => {
|
||||
name: 'a',
|
||||
strategies: [
|
||||
{
|
||||
id: 'a',
|
||||
name: 'a',
|
||||
constraints: [
|
||||
{
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
@ -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',
|
||||
},
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
@ -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;
|
||||
|
27
src/lib/openapi/spec/strategies-schema.ts
Normal file
27
src/lib/openapi/spec/strategies-schema.ts
Normal file
@ -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<typeof strategiesSchema>;
|
28
src/lib/openapi/spec/strategy-schema.test.ts
Normal file
28
src/lib/openapi/spec/strategy-schema.test.ts
Normal file
@ -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();
|
||||
});
|
@ -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<typeof strategySchema>;
|
||||
|
35
src/lib/openapi/spec/update-feature-strategy-schema.ts
Normal file
35
src/lib/openapi/spec/update-feature-strategy-schema.ts
Normal file
@ -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
|
||||
>;
|
@ -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<typeof updateStrategySchema>;
|
41
src/lib/openapi/spec/upsert-strategy-schema.ts
Normal file
41
src/lib/openapi/spec/upsert-strategy-schema.ts
Normal file
@ -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<typeof upsertStrategySchema>;
|
@ -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<FeatureStrategyParams, any, CreateStrategySchema>,
|
||||
res: Response<StrategySchema>,
|
||||
async addFeatureStrategy(
|
||||
req: IAuthRequest<
|
||||
FeatureStrategyParams,
|
||||
any,
|
||||
CreateFeatureStrategySchema
|
||||
>,
|
||||
res: Response<FeatureStrategySchema>,
|
||||
): Promise<void> {
|
||||
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<FeatureStrategyParams, any, any, any>,
|
||||
res: Response<StrategySchema[]>,
|
||||
res: Response<FeatureStrategySchema[]>,
|
||||
): Promise<void> {
|
||||
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<StrategyIdParams, any, UpdateStrategySchema>,
|
||||
res: Response<StrategySchema>,
|
||||
async updateFeatureStrategy(
|
||||
req: IAuthRequest<StrategyIdParams, any, UpdateFeatureStrategySchema>,
|
||||
res: Response<FeatureStrategySchema>,
|
||||
): Promise<void> {
|
||||
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<StrategyIdParams, any, Operation[], any>,
|
||||
res: Response<StrategySchema>,
|
||||
res: Response<FeatureStrategySchema>,
|
||||
): Promise<void> {
|
||||
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<StrategyIdParams, any, any, any>,
|
||||
res: Response<StrategySchema>,
|
||||
res: Response<FeatureStrategySchema>,
|
||||
): Promise<void> {
|
||||
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<StrategyIdParams, any, any, any>,
|
||||
res: Response<void>,
|
||||
): Promise<void> {
|
||||
@ -612,7 +623,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
{ name: string; value: string | number },
|
||||
any
|
||||
>,
|
||||
res: Response<StrategySchema>,
|
||||
res: Response<FeatureStrategySchema>,
|
||||
): Promise<void> {
|
||||
const { strategyId, environment, projectId, featureName } = req.params;
|
||||
const userName = extractUsername(req);
|
||||
|
@ -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'",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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<IUnleashServices, 'strategyService'>,
|
||||
{
|
||||
strategyService,
|
||||
openApiService,
|
||||
}: Pick<IUnleashServices, 'strategyService' | 'openApiService'>,
|
||||
) {
|
||||
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<StrategiesSchema>,
|
||||
): Promise<void> {
|
||||
const strategies = await this.strategyService.getStrategies();
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
strategiesSchema.$id,
|
||||
{ version, strategies },
|
||||
);
|
||||
}
|
||||
|
||||
async getAllStrategies(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const strategies = await this.strategyService.getStrategies();
|
||||
res.json({ version, strategies });
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
}
|
||||
}
|
||||
async getStrategy(
|
||||
req: Request,
|
||||
res: Response<StrategySchema>,
|
||||
): Promise<void> {
|
||||
const strategy = await this.strategyService.getStrategy(
|
||||
req.params.name,
|
||||
);
|
||||
|
||||
async getStrategy(req: Request, res: Response): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
async createStrategy(
|
||||
req: IAuthRequest<unknown, UpsertStrategySchema>,
|
||||
res: Response<void>,
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
async updateStrategy(
|
||||
req: IAuthRequest<unknown, UpsertStrategySchema>,
|
||||
res: Response<void>,
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
async deprecateStrategy(
|
||||
req: IAuthRequest,
|
||||
res: Response<void>,
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
async reactivateStrategy(
|
||||
req: IAuthRequest,
|
||||
res: Response<void>,
|
||||
): Promise<void> {
|
||||
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;
|
||||
|
@ -390,7 +390,7 @@ class FeatureToggleService {
|
||||
value: string | number,
|
||||
context: IFeatureStrategyContext,
|
||||
userName: string,
|
||||
): Promise<IStrategyConfig> {
|
||||
): Promise<Saved<IStrategyConfig>> {
|
||||
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<IStrategyConfig[]> {
|
||||
): Promise<Saved<IStrategyConfig>[]> {
|
||||
const hasEnv = await this.featureEnvironmentStore.featureHasEnvironment(
|
||||
environment,
|
||||
featureName,
|
||||
@ -701,7 +701,7 @@ class FeatureToggleService {
|
||||
);
|
||||
}
|
||||
|
||||
async getStrategy(strategyId: string): Promise<IStrategyConfig> {
|
||||
async getStrategy(strategyId: string): Promise<Saved<IStrategyConfig>> {
|
||||
const strategy = await this.featureStrategiesStore.getStrategyById(
|
||||
strategyId,
|
||||
);
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ export interface IStrategy {
|
||||
name: string;
|
||||
editable: boolean;
|
||||
description: string;
|
||||
parameters: object;
|
||||
parameters: object[];
|
||||
deprecated: boolean;
|
||||
displayName: string;
|
||||
}
|
||||
|
@ -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<FeatureSchema, 'createdAt'>,
|
||||
strategy: Omit<StrategySchema, 'id'> = defaultStrategy,
|
||||
strategy: Omit<FeatureStrategySchema, 'id'> = 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',
|
||||
|
@ -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",
|
||||
|
@ -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(),
|
||||
|
@ -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<StrategySchema, 'id'> = {
|
||||
const config: Omit<FeatureStrategySchema, 'id'> = {
|
||||
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<StrategySchema, 'id'> = {
|
||||
const config: Omit<FeatureStrategySchema, 'id'> = {
|
||||
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<StrategySchema, 'id'> = {
|
||||
const config: Omit<FeatureStrategySchema, 'id'> = {
|
||||
name: 'default',
|
||||
constraints: [],
|
||||
parameters: {},
|
||||
|
2
src/test/fixtures/fake-strategies-store.ts
vendored
2
src/test/fixtures/fake-strategies-store.ts
vendored
@ -12,7 +12,7 @@ export default class FakeStrategiesStore implements IStrategyStore {
|
||||
description: 'default strategy',
|
||||
displayName: 'Default',
|
||||
editable: false,
|
||||
parameters: {},
|
||||
parameters: [],
|
||||
deprecated: false,
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user