89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
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<string, unknown>).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<string, boolean> };
|
|
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<string, Record<string, unknown>>;
|
|
|
|
expect(schemas.CreateTaskRequest).toBeDefined();
|
|
expect(schemas.UpdateTaskRequest).toBeDefined();
|
|
});
|
|
});
|