import 'reflect-metadata'; import { METHOD_METADATA, PATH_METADATA } from '@nestjs/common/constants'; import { RequestMethod } from '@nestjs/common'; import { describe, expect, it } from 'vitest'; import { buildOpenApiDocument } from './openapi'; import { PublicApiController } from './public-api.controller'; /** * The OpenAPI document is maintained by hand (openapi.ts) — this test walks * the controller's real routes and asserts each one is described, so the * document cannot silently drift from the implementation (issue #104 * acceptance criterion), and nothing documented is stale. */ describe('public api OpenAPI document', () => { const verbs: Record = { [RequestMethod.GET]: 'get', [RequestMethod.POST]: 'post', [RequestMethod.PUT]: 'put', [RequestMethod.PATCH]: 'patch', [RequestMethod.DELETE]: 'delete', }; function controllerRoutes(): { path: string; verb: string }[] { const prototype = PublicApiController.prototype as unknown as Record; const routes: { path: string; verb: string }[] = []; for (const name of Object.getOwnPropertyNames(prototype)) { if (name === 'constructor') continue; const handler = prototype[name]; if (typeof handler !== 'function') continue; const method = Reflect.getMetadata(METHOD_METADATA, handler) as number | undefined; if (method === undefined) continue; const raw = Reflect.getMetadata(PATH_METADATA, handler) as string; // Nest `:param` → OpenAPI `{param}`; the controller base is the server url. const path = `/${raw}`.replace(/\/+/g, '/').replace(/:([A-Za-z0-9_]+)/g, '{$1}'); routes.push({ path, verb: verbs[method]! }); } return routes; } it('describes every controller route and nothing else', () => { const document = buildOpenApiDocument() as { paths: Record>; }; const documented = new Set( Object.entries(document.paths).flatMap(([path, methods]) => Object.keys(methods).map((verb) => `${verb} ${path}`), ), ); const implemented = new Set(controllerRoutes().map(({ verb, path }) => `${verb} ${path}`)); expect([...implemented].filter((route) => !documented.has(route))).toEqual([]); expect([...documented].filter((route) => !implemented.has(route))).toEqual([]); }); it('declares bearer security and the versioned server url', () => { const document = buildOpenApiDocument() as { servers: { url: string }[]; components: { securitySchemes: Record }; }; expect(document.servers[0]!.url).toBe('/api/public/v1'); expect(document.components.securitySchemes.pat).toBeDefined(); }); });