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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/3300-test-openapi-test-ts-fails-when-the-open → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/3300-test-openapi-test-ts-fails-when-the-open was merged into main as b427850.

Review

Sign in to review this change.
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 };

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.

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 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs