import { describe, expect, it } from "bun:test"; import { mkdtempSync, rmSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { createApp } from "../src/app"; import { reseedDatabase } from "../src/database"; import { openApiDocument } from "../src/openapi"; const swaggerAliasPaths = new Set(["/openapi.json", "/swagger.json"]); function normalizeOpenApiPath(path: string) { return path.replace(/:([^/]+)/g, "{$1}"); } function getDocumentedOperations() { return Object.entries(openApiDocument.paths).flatMap(([path, pathItem]) => Object.keys(pathItem as Record).map( (method) => `${method.toUpperCase()} ${normalizeOpenApiPath(path)}`, ), ); } function getRuntimeOperations() { const tempDir = mkdtempSync(join(tmpdir(), "mock-task-api-routes-")); try { const db = reseedDatabase(join(tempDir, "test.sqlite")); const app = createApp(db, { parseJson: false }); const stack = (app as { router: { stack: Array<{ route?: unknown }> } }).router.stack; return stack.flatMap((layer) => { if (!layer.route) { return []; } const route = layer.route as { path: string; methods: Record }; if (swaggerAliasPaths.has(route.path)) { return []; } return Object.entries(route.methods) .filter(([, enabled]) => enabled) .map(([method]) => `${method.toUpperCase()} ${normalizeOpenApiPath(route.path)}`); }); } finally { rmSync(tempDir, { recursive: true, force: true }); } } describe("tsoa swagger document", () => { it("matches all runtime business routes registered in express", () => { const documentedOperations = getDocumentedOperations(); const runtimeOperations = getRuntimeOperations(); expect(runtimeOperations.sort()).toEqual(documentedOperations.sort()); }); it("documents the expected business endpoints generated from controllers", () => { const operations = getDocumentedOperations(); expect(operations.sort()).toEqual([ "GET /health", "GET /orgs", "GET /orgs/{orgId}", "GET /orgs/{orgId}/projects", "GET /orgs/{orgId}/tasks", "GET /users", "GET /projects", "GET /tasks", "POST /tasks", "GET /tasks/{taskId}", "PATCH /tasks/{taskId}", ].sort()); }); it("does not include swagger hosting aliases as business operations", () => { expect(openApiDocument.paths["/openapi.json"]).toBeUndefined(); expect(openApiDocument.paths["/swagger.json"]).toBeUndefined(); expect(openApiDocument.paths["/api-docs"]).toBeUndefined(); }); it("uses tsoa-generated schemas for the task mutation payloads", () => { const schemas = openApiDocument.components.schemas as Record>; expect(schemas.CreateTaskRequest).toBeDefined(); expect(schemas.UpdateTaskRequest).toBeDefined(); }); });