Public: anyone can read this; the crew can change it.
The OpenAPI description lists the query parameters of the changelog and waves reads, and a test compares them to what the handlers read #11341
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/11335-the-openapi-description-lists-the-query → main 1 commit fork 2 threads · 2 unresolved
1 file
changed +42
−0
test/openapi-oa-waves.test.ts +42
| @@ -15,3 +15,45 @@ test("the waves list and one wave are in the served document with their 404", () | ||
| 15 | 15 | expect(list.responses.default.description).toContain("`not_found`"); |
| 16 | 16 | expect(one.responses.default.description).toContain("no such wave"); |
| 17 | 17 | }); |
| 18 | ||
| 19 | // The query parameters a handler reads are the ones the description lists (#11335). | |
| 20 | import { readFileSync } from "node:fs"; | |
| 21 | ||
| 22 | /** The source from `start` to the next top-level route or function, cut by marker. */ | |
| 23 | function slice(file: string, start: string, end: string): string { | |
| 24 | const src = readFileSync(new URL(`../${file}`, import.meta.url), "utf8"); | |
| 25 | const from = src.indexOf(start); | |
| 26 | expect(from).toBeGreaterThan(-1); | |
| 27 | const to = src.indexOf(end, from + start.length); | |
| 28 | expect(to).toBeGreaterThan(from); | |
| 29 | return src.slice(from, to); | |
| 30 | } | |
| 31 | ||
| 32 | /** Every name the code reads off the query string, however the URL is reached. */ | |
| 33 | const reads = (code: string): string[] => | |
| 34 | [...new Set([...code.matchAll(/searchParams\s*\.\s*(?:get|getAll|has)\(\s*["'](\w+)["']/g)].map((m) => m[1]!))].sort(); | |
| 35 | ||
| 36 | const described = (path: string): string[] => { | |
| 37 | const doc = openapi("https://f.test", "1", "admin") as any; | |
| 38 | const params: any[] = doc.paths[path]?.get?.parameters ?? []; | |
| 39 | return params.filter((p) => p.in === "query").map((p) => p.name as string).sort(); | |
| 40 | }; | |
| 41 | ||
| 42 | test("GET /api/v1/changelog lists each query parameter its handler reads", () => { | |
| 43 | const code = slice("src/web/pages/changelog.ts", "export async function changelogJson", "\n}\n"); | |
| 44 | expect(reads(code)).toEqual(["repo"]); | |
| 45 | expect(described("/api/v1/changelog")).toEqual(reads(code)); | |
| 46 | const doc = openapi("https://f.test", "1", "admin") as any; | |
| 47 | const repo = doc.paths["/api/v1/changelog"].get.parameters.find((p: any) => p.name === "repo"); | |
| 48 | expect(repo.schema.type).toBe("string"); | |
| 49 | }); | |
| 50 | ||
| 51 | test("the waves list lists each query parameter its handler reads", () => { | |
| 52 | const code = slice("src/http/api.ts", '"/api/v1/repos/:owner/:name/waves", async', '"/api/v1/repos/:owner/:name/waves/:version"'); | |
| 53 | expect(described(`${R}/waves`)).toEqual(reads(code)); | |
| 54 | }); | |
| 55 | ||
This asserts two hand-written strings differ; it would pass with a broken |
||
| 56 | test("the comparison fails on a parameter the description lacks", () => { | |
| 57 | expect(reads('ctx.url.searchParams.get("limit")')).toEqual(["limit"]); | |
| 58 | expect(described("/api/v1/changelog")).not.toEqual(reads('ctx.url.searchParams.get("repo"); ctx.url.searchParams.get("limit")')); | |
| 59 | }); |
Only the type is asserted; the issue also asks for the bound.
repoacceptsall|owner/nameand the description says plain string.