From 49aabe029063a92eddfb4e99d0be321a7f1070bb Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Sun, 12 Nov 2023 00:27:01 +0800 Subject: [PATCH] refactor(core): fix tests --- .../core/src/routes/swagger/index.test.ts | 33 +++++++++---------- packages/core/src/routes/swagger/index.ts | 22 ++++++++++--- .../core/src/routes/swagger/utils/general.ts | 21 ++++++++++-- packages/core/src/utils/SchemaRouter.ts | 5 +-- 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/packages/core/src/routes/swagger/index.test.ts b/packages/core/src/routes/swagger/index.test.ts index 37387fb6b..dc8d7b510 100644 --- a/packages/core/src/routes/swagger/index.test.ts +++ b/packages/core/src/routes/swagger/index.test.ts @@ -8,7 +8,8 @@ import koaGuard from '#src/middleware/koa-guard.js'; import koaPagination from '#src/middleware/koa-pagination.js'; import type { AnonymousRouter } from '#src/routes/types.js'; -const { default: swaggerRoutes, paginationParameters } = await import('./index.js'); +const { default: swaggerRoutes } = await import('./index.js'); +const { paginationParameters } = await import('./utils/parameters.js'); const createSwaggerRequest = ( allRouters: Router[], @@ -79,7 +80,7 @@ describe('GET /swagger.json', () => { get: { tags: ['Mock'] }, }, '/api/.well-known': { - put: { tags: ['.well-known'] }, + put: { tags: ['Well known'] }, }, }); }); @@ -101,22 +102,18 @@ describe('GET /swagger.json', () => { const response = await swaggerRequest.get('/swagger.json'); expect(response.body.paths).toMatchObject({ '/api/mock/:id/:field': { - get: { - parameters: [ - { - name: 'id', - in: 'path', - required: true, - schema: { type: 'number' }, - }, - { - name: 'field', - in: 'path', - required: true, - schema: { type: 'string' }, - }, - ], - }, + parameters: [ + { + $ref: '#/components/parameters/mocId:root', + }, + { + name: 'field', + in: 'path', + required: true, + schema: { type: 'string' }, + }, + ], + get: {}, }, }); }); diff --git a/packages/core/src/routes/swagger/index.ts b/packages/core/src/routes/swagger/index.ts index 23c8571f0..ef4dde25e 100644 --- a/packages/core/src/routes/swagger/index.ts +++ b/packages/core/src/routes/swagger/index.ts @@ -17,7 +17,7 @@ import { translationSchemas, zodTypeToSwagger } from '#src/utils/zod.js'; import type { AnonymousRouter } from '../types.js'; -import { buildTag, findSupplementFiles } from './utils/general.js'; +import { buildTag, findSupplementFiles, normalizePath } from './utils/general.js'; import { type ParameterArray, buildParameters, @@ -103,6 +103,20 @@ const isManagementApiRouter = ({ stack }: Router) => .filter(({ path }) => !path.includes('.*')) .some(({ stack }) => stack.some((function_) => isKoaAuthMiddleware(function_))); +// Add more components here to cover more ID parameters in paths. For example, if there is a +// path `/foo/:barBazId`, then add `bar-baz` to the array. +const identifiableEntityNames = [ + 'application', + 'connector', + 'resource', + 'user', + 'log', + 'role', + 'scope', + 'hook', + 'domain', +]; + /** * Attach the `/swagger.json` route which returns the generated OpenAPI document for the * management APIs. @@ -123,14 +137,14 @@ export default function swaggerRoutes !path.includes('.*') && path.startsWith('/organization')) + .filter(({ path }) => !path.includes('.*')) .flatMap(({ path: routerPath, stack, methods }) => methods .map((method) => method.toLowerCase()) // There is no need to show the HEAD method. .filter((method): method is OpenAPIV3.HttpMethods => method !== 'head') .map((httpMethod) => { - const path = `/api${routerPath}`; + const path = normalizePath(routerPath); const { pathParameters, ...operation } = buildOperation( stack, routerPath, @@ -180,7 +194,7 @@ export default function swaggerRoutes ({ ...previous, ...buildPathIdParameters(entityName) }), {} ), diff --git a/packages/core/src/routes/swagger/utils/general.ts b/packages/core/src/routes/swagger/utils/general.ts index 4f969365c..a43fe2dc2 100644 --- a/packages/core/src/routes/swagger/utils/general.ts +++ b/packages/core/src/routes/swagger/utils/general.ts @@ -14,8 +14,12 @@ export const getRootComponent = (path?: string) => path?.split('/')[1]; * component name. * @example '/organization-roles' -> 'Organization roles' */ -export const buildTag = (path: string) => - capitalize((getRootComponent(path) ?? 'General').replaceAll('-', ' ')); +export const buildTag = (path: string) => { + const rootComponent = (getRootComponent(path) ?? 'General').replaceAll('-', ' '); + return rootComponent.startsWith('.') + ? capitalize(rootComponent.slice(1)) + : capitalize(rootComponent); +}; /** * Recursively find all supplement files (files end with `.openapi.json`) for the given @@ -37,3 +41,16 @@ export const findSupplementFiles = async (directory: string) => { return result; }; /* eslint-enable @silverhand/fp/no-mutating-methods, no-await-in-loop */ + +/** + * Normalize the path to the OpenAPI path by adding `/api` prefix and replacing the path parameters + * with OpenAPI path parameters. + * + * @example + * normalizePath('/organization/:id') -> '/api/organization/{id}' + */ +export const normalizePath = (path: string) => + `/api${path}` + .split('/') + .map((part) => (part.startsWith(':') ? `{${part.slice(1)}}` : part)) + .join('/'); diff --git a/packages/core/src/utils/SchemaRouter.ts b/packages/core/src/utils/SchemaRouter.ts index 65a13d04b..ebd947a4b 100644 --- a/packages/core/src/utils/SchemaRouter.ts +++ b/packages/core/src/utils/SchemaRouter.ts @@ -336,8 +336,7 @@ export default class SchemaRouter< `/:id/${pathname}/:${camelCaseSchemaId(relationSchema)}`, koaGuard({ params: z.object({ id: z.string().min(1), [relationSchemaId]: z.string().min(1) }), - // Should be 422 if the relation doesn't exist, update until we change the error handling - status: [204, 404], + status: [204, 422], }), async (ctx, next) => { const { @@ -345,7 +344,9 @@ export default class SchemaRouter< } = ctx.guard; await relationQueries.delete({ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- `koaGuard()` ensures the value is not `undefined` [columns.schemaId]: id!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- `koaGuard()` ensures the value is not `undefined` [columns.relationSchemaId]: relationId!, });