From 452b5a6748bf623c304de8dd133fbbcd359ec5d0 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 1 Aug 2023 15:58:15 +0200 Subject: [PATCH] OpenAPI: add operation tests: require summaries and descriptions (#4377) This PR adds an e2e test to the OpenAPI tests that checks that all openapi operations have both summaries and descriptions. It also fixes the few schemas that were missing one or the other. --- src/lib/routes/admin-api/archive.ts | 6 ++++ src/lib/routes/admin-api/client-metrics.ts | 5 ++-- .../routes/admin-api/project/environments.ts | 1 + .../admin-api/project/project-features.ts | 4 ++- src/lib/routes/admin-api/public-signup.ts | 3 +- src/lib/routes/public-invite.ts | 3 +- src/test/e2e/api/openapi/openapi.e2e.test.ts | 30 +++++++++++++++++++ 7 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/lib/routes/admin-api/archive.ts b/src/lib/routes/admin-api/archive.ts index b4ed181ec3..f0048d7ae5 100644 --- a/src/lib/routes/admin-api/archive.ts +++ b/src/lib/routes/admin-api/archive.ts @@ -42,6 +42,9 @@ export default class ArchiveController extends Controller { middleware: [ openApiService.validPath({ tags: ['Archive'], + summary: 'Get archived features', + description: + 'Retrieve a list of all [archived feature toggles](https://docs.getunleash.io/reference/archived-toggles).', operationId: 'getArchivedFeatures', responses: { 200: createResponseSchema('featuresSchema'), @@ -62,6 +65,9 @@ export default class ArchiveController extends Controller { openApiService.validPath({ tags: ['Archive'], operationId: 'getArchivedFeaturesByProjectId', + summary: 'Get archived features in project', + description: + 'Retrieves a list of archived features that belong to the provided project.', responses: { 200: createResponseSchema('featuresSchema'), ...getStandardResponses(401, 403), diff --git a/src/lib/routes/admin-api/client-metrics.ts b/src/lib/routes/admin-api/client-metrics.ts index 223b7ac304..e459c20b64 100644 --- a/src/lib/routes/admin-api/client-metrics.ts +++ b/src/lib/routes/admin-api/client-metrics.ts @@ -59,8 +59,9 @@ class ClientMetricsController extends Controller { openApiService.validPath({ operationId: 'getRawFeatureMetrics', tags: ['Metrics'], - summary: - 'Feature usage metrics for the last 48 hours, grouped by hour', + summary: 'Get feature metrics', + description: + 'Get usage metrics for a specific feature for the last 48 hours, grouped by hour', responses: { 200: createResponseSchema('featureMetricsSchema'), ...getStandardResponses(401, 403, 404), diff --git a/src/lib/routes/admin-api/project/environments.ts b/src/lib/routes/admin-api/project/environments.ts index 9231609afc..0e9bcd5cd9 100644 --- a/src/lib/routes/admin-api/project/environments.ts +++ b/src/lib/routes/admin-api/project/environments.ts @@ -99,6 +99,7 @@ export default class EnvironmentsController extends Controller { openApiService.validPath({ tags: ['Projects'], operationId: 'addDefaultStrategyToProjectEnvironment', + summary: 'Set environment-default strategy', description: 'Adds a default strategy for this environment. Unleash will use this strategy by default when enabling a toggle. Use the wild card "*" for `:environment` to add to all environments. ', requestBody: createRequestSchema( diff --git a/src/lib/routes/admin-api/project/project-features.ts b/src/lib/routes/admin-api/project/project-features.ts index d3b9327d15..7a8aa97a7e 100644 --- a/src/lib/routes/admin-api/project/project-features.ts +++ b/src/lib/routes/admin-api/project/project-features.ts @@ -325,8 +325,10 @@ export default class ProjectFeaturesController extends Controller { middleware: [ openApiService.validPath({ tags: ['Features'], - summary: 'Set the order of strategies on the list', operationId: 'setStrategySortOrder', + summary: 'Set strategy sort order', + description: + 'Set the sort order of the provided list of strategies.', requestBody: createRequestSchema( 'setStrategySortOrderSchema', ), diff --git a/src/lib/routes/admin-api/public-signup.ts b/src/lib/routes/admin-api/public-signup.ts index a7c2381e9c..3ae135e9da 100644 --- a/src/lib/routes/admin-api/public-signup.ts +++ b/src/lib/routes/admin-api/public-signup.ts @@ -74,7 +74,8 @@ export class PublicSignupController extends Controller { middleware: [ openApiService.validPath({ tags: ['Public signup tokens'], - summary: 'Retrieve all existing public signup tokens', + summary: 'Get public signup tokens', + description: 'Retrieves all existing public signup tokens.', operationId: 'getAllPublicSignupTokens', responses: { 200: createResponseSchema('publicSignupTokensSchema'), diff --git a/src/lib/routes/public-invite.ts b/src/lib/routes/public-invite.ts index 0cc91f3c4d..8f95507420 100644 --- a/src/lib/routes/public-invite.ts +++ b/src/lib/routes/public-invite.ts @@ -52,7 +52,8 @@ export class PublicInviteController extends Controller { openApiService.validPath({ tags: ['Public signup tokens'], operationId: 'validatePublicSignupToken', - summary: `Check whether a public sign-up token exists, has not expired and is enabled`, + summary: `Validate signup token`, + description: `Check whether the provided public sign-up token exists, has not expired and is enabled`, responses: { 200: emptyResponse, ...getStandardResponses(400), diff --git a/src/test/e2e/api/openapi/openapi.e2e.test.ts b/src/test/e2e/api/openapi/openapi.e2e.test.ts index a54a196f19..9477e7fffb 100644 --- a/src/test/e2e/api/openapi/openapi.e2e.test.ts +++ b/src/test/e2e/api/openapi/openapi.e2e.test.ts @@ -192,3 +192,33 @@ test('all tags are listed in the root "tags" list', async () => { } expect(invalidTags).toStrictEqual({}); }); + +test('all API operations have summaries and descriptions', async () => { + const { body: spec } = await app.request + .get('/docs/openapi.json') + .expect('Content-Type', /json/) + .expect(200); + + const anomalies = Object.entries(spec.paths).flatMap(([path, data]) => { + return Object.entries(data) + .map(([verb, operationDescription]) => { + if ( + 'summary' in operationDescription && + 'description' in operationDescription + ) { + return undefined; + } else { + return [verb, operationDescription.operationId]; + } + }) + .filter(Boolean) + .map( + ([verb, operationId]) => + `${verb.toUpperCase()} ${path} (operation ID: ${operationId})`, + ); + }); + + // any items left in the anomalies list is missing either a summary, or a + // description, or both. + expect(anomalies).toStrictEqual([]); +});