import { API_PREFIX, listEndpoints } from "@umt/contracts"; import { describe, expect, it } from "vitest"; import { createApp } from "../src/http/app.ts"; import { EventBus } from "../src/http/sse.ts"; import { SERVER_MODULES } from "../src/modules/index.ts"; import type { ServerContext } from "../src/modules/types.ts"; import { DEV_KEY, json, memoryLogger } from "./helpers.ts"; // OpenAPI 里出现的每条 /api/v1 路由都必须登记在 @umt/contracts 的 Api 注册表(web 与测试都从那里取路径/schema)。 describe("Api 注册表覆盖", () => { it("OpenAPI 文档中的每条路由都在 listEndpoints() 里", async () => { const app = createApp({ config: { NODE_ENV: "development", DEV_ADMIN_KEY: DEV_KEY }, logger: memoryLogger().logger, bus: new EventBus(5), role: "all", version: "0.0.0-test", modules: SERVER_MODULES, ctx: { role: "all" } as unknown as ServerContext, }); const res = await app.request("/api/openapi.json"); expect(res.status).toBe(200); const doc = (await json(res)) as { paths: Record> }; const served: string[] = []; for (const [path, ops] of Object.entries(doc.paths)) { if (!path.startsWith(`${API_PREFIX}/`)) continue; // /api/openapi.json 等非业务路由 const rel = path.slice(API_PREFIX.length); for (const method of Object.keys(ops)) served.push(`${method.toUpperCase()} ${rel}`); } expect(served.length).toBeGreaterThan(0); const registered = new Set(listEndpoints().map((e) => `${e.method.toUpperCase()} ${e.path}`)); const missing = served.filter((k) => !registered.has(k)).sort(); expect(missing, `以下路由未登记到 packages/contracts/src/api.ts:\n${missing.join("\n")}`).toEqual([]); // 反向:api.ts 登记的每条端点都必须有实现(OpenAPI 里可见) const servedSet = new Set(served); const unimplemented = [...registered].filter((k) => !servedSet.has(k)).sort(); expect(unimplemented, `以下端点登记在 api.ts 但未实现:\n${unimplemented.join("\n")}`).toEqual([]); }); });