Public: anyone can read this; the crew can change it.
test/openapi.test.ts fails when the OpenAPI document describes a route the router does not have #3318
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3300-test-openapi-test-ts-fails-when-the-open → main 1 commit fork 1 thread · 1 unresolved
1 file
changed +42
−0
test/openapi-served.test.ts added +42
| @@ -0,0 +1,42 @@ | ||
| 1 | /** Every route the OpenAPI document describes is one the router serves. */ | |
| 2 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 3 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 4 | import { tmpdir } from "node:os"; | |
| 5 | import { join } from "node:path"; | |
| 6 | import { createApp, type App } from "../src/app.ts"; | |
| 7 | import { ROUTES } from "../src/contract/openapi.ts"; | |
| 8 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 9 | ||
| 10 | let dataDir: string; | |
| 11 | let app: App; | |
| 12 | let servers: Servers; | |
| 13 | ||
| 14 | beforeAll(async () => { | |
| 15 | dataDir = await mkdtemp(join(tmpdir(), "forge-openapi-served-")); | |
| 16 | app = createApp({ dataDir, port: 0 }); | |
| 17 | servers = await serve(app); | |
| 18 | }); | |
| 19 | ||
| 20 | afterAll(async () => { | |
| 21 | servers.stop(); | |
| 22 | app.close(); | |
| 23 | await rm(dataDir, { recursive: true, force: true }); | |
| 24 | }); | |
| 25 | ||
| 26 | const sample = (name: string) => (name === "number" ? "1" : "sample"); | |
| 27 | ||
| 28 | test("every described method and path resolves to a handler, not no_route", async () => { | |
| 29 | expect(ROUTES.length).toBeGreaterThan(5); | |
| 30 | const missing: string[] = []; | |
| 31 | for (const r of ROUTES) { | |
| 32 | const path = r.path.replace(/\{(\w+)\}/g, (_, n: string) => sample(n)); | |
| 33 | const res = await fetch(`${servers.url}${path}`, { | |
| 34 | method: r.method.toUpperCase(), | |
| 35 | headers: { "content-type": "application/json", accept: "application/json" }, | |
| 36 | ...(r.method === "get" ? {} : { body: "{}" }), | |
| 37 | }); | |
| 38 | const body = (await res.json().catch(() => ({}))) as { error?: string }; | |
| 39 | if (res.status === 404 && body.error === "no_route") missing.push(`${r.method.toUpperCase()} ${r.path}`); | |
| 40 | } | |
| 41 | expect(missing, `described but not served: ${missing.join(", ")}`).toEqual([]); | |
| 42 | }); |
new above
offline
Only no_route counts as missing. If a handler ever answers 404 no_route for an unknown owner/name sample, this will flag a served route. Keep an eye on that; if it happens, use a sample that exists.