Public: anyone can read this; the crew can change it.
The OpenAPI coverage test fails on a NOT_YET_DESCRIBED entry that is now described, and the stale entries leave #5407
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5396-the-openapi-coverage-test-fails-on-a-not → main 3 commits fork
1 file
changed +8
−52
test/openapi-coverage.test.ts +8 −52
| @@ -5,59 +5,11 @@ import { ROUTES } from "../src/contract/openapi.ts"; | ||
| 5 | 5 | /** |
| 6 | 6 | * Every `/api/v1` route registered in src/http/api.ts has a row in `ROUTES` |
| 7 | 7 | * (one file each under src/contract/openapi/routes/), or is listed here. This list only shrinks: describe |
| 8 | * a route, then delete its line. An entry that is now described is ignored. | |
| 8 | * a route, then delete its line: an entry that is now described fails the test | |
| 9 | * until you do. | |
| 9 | 10 | */ |
| 10 | 11 | const NOT_YET_DESCRIBED: readonly string[] = [ |
| 11 | "DELETE /api/v1/orgs/{}/teams/{}/members/{}", | |
| 12 | "DELETE /api/v1/user/keys/{}", | |
| 13 | "DELETE /api/v1/user/pins/{}/{}", | |
| 14 | "GET /api/v1/agents", | |
| 15 | "GET /api/v1/asks", | |
| 16 | "GET /api/v1/feed", | |
| 17 | "GET /api/v1/feed/chips", | |
| 18 | "GET /api/v1/migrate/{}/stream", | |
| 19 | "GET /api/v1/openapi.json", | |
| 20 | "GET /api/v1/orgs/{}", | |
| 21 | "GET /api/v1/orgs/{}/teams", | |
| 22 | "GET /api/v1/orgs/{}/teams/{}/members", | |
| 23 | "GET /api/v1/repos/{}/{}/app/{}/open", | |
| 24 | "GET /api/v1/repos/{}/{}/labels", | |
| 25 | "GET /api/v1/repos/{}/{}/milestones", | |
| 26 | "GET /api/v1/repos/{}/{}/milestones/{}", | |
| 27 | "GET /api/v1/user", | |
| 28 | "GET /api/v1/user/assigned", | |
| 29 | "GET /api/v1/user/keys", | |
| 30 | "GET /api/v1/user/orgs", | |
| 31 | 12 | "GET /api/v1/user/origins/{}/browse/{}/{}", |
| 32 | "GET /api/v1/user/origins/{}/browse/{}/{}/issues", | |
| 33 | "GET /api/v1/user/origins/{}/browse/{}/{}/issues/{}", | |
| 34 | "GET /api/v1/user/origins/{}/browse/{}/{}/pulls", | |
| 35 | "GET /api/v1/user/origins/{}/browse/{}/{}/pulls/{}", | |
| 36 | "GET /api/v1/user/origins/{}/mirror/{}/stream", | |
| 37 | "GET /api/v1/user/pins", | |
| 38 | "GET /api/v1/users/{}", | |
| 39 | "GET /api/v1/users/{}/repos", | |
| 40 | "PATCH /api/v1/user", | |
| 41 | "POST /api/v1/asks/bulk", | |
| 42 | "POST /api/v1/asks/bulk/{}/undo", | |
| 43 | "POST /api/v1/asks/decide", | |
| 44 | "POST /api/v1/asks/decide/{}/undo", | |
| 45 | "POST /api/v1/asks/proposal/{}/{}", | |
| 46 | "POST /api/v1/feed/chips", | |
| 47 | "POST /api/v1/repos/{}/{}/issues/{}/dependencies/{}/confirm", | |
| 48 | "POST /api/v1/repos/{}/{}/milestones/{}/limb", | |
| 49 | "POST /api/v1/repos/{}/{}/prompts/{}/run", | |
| 50 | "POST /api/v1/repos/{}/{}/proposal-tokens", | |
| 51 | "POST /api/v1/repos/{}/{}/pulls/{}/comments", | |
| 52 | "POST /api/v1/repos/{}/{}/pulls/{}/variant", | |
| 53 | "POST /api/v1/repos/{}/{}/reports/{}/accept", | |
| 54 | "POST /api/v1/repos/{}/{}/reports/{}/dismiss", | |
| 55 | "POST /api/v1/repos/{}/{}/runs/{}/steer", | |
| 56 | "POST /api/v1/user/keys", | |
| 57 | "PUT /api/v1/front", | |
| 58 | "PUT /api/v1/repos/{}/{}/crew/{}", | |
| 59 | "PUT /api/v1/repos/{}/{}/pulls/{}/labels", | |
| 60 | "PUT /api/v1/user/pins/{}/{}", | |
| 61 | 13 | ]; |
| 62 | 14 | |
| 63 | 15 | const norm = (path: string) => path.replace(/[:*]\w+/g, "{}").replace(/\{\w+\}/g, "{}"); |
| @@ -102,11 +54,15 @@ test("every registered /api/v1 route has an OpenAPI row or is listed as not yet | ||
| 102 | 54 | const described = new Set(ROUTES.map((r) => `${r.method.toUpperCase()} ${norm(r.path)}`)); |
| 103 | 55 | const all = registered(); |
| 104 | 56 | const missing = all.filter((r) => !described.has(r) && !NOT_YET_DESCRIBED.includes(r)); |
| 105 | const removable = NOT_YET_DESCRIBED.filter((r) => described.has(r)); | |
| 106 | console.log(`openapi-coverage: ${removable.length} NOT_YET_DESCRIBED entries could be removed`); | |
| 107 | 57 | expect(missing, `routes with no OpenAPI row (add a file under src/contract/openapi/routes/, then run bun scripts/openapi-routes.ts):\n${missing.join("\n")}`).toEqual([]); |
| 108 | 58 | }); |
| 109 | 59 | |
| 60 | test("NOT_YET_DESCRIBED has no entry that is now described", () => { | |
| 61 | const described = new Set(ROUTES.map((r) => `${r.method.toUpperCase()} ${norm(r.path)}`)); | |
| 62 | const stale = NOT_YET_DESCRIBED.filter((r) => described.has(r)); | |
| 63 | expect(stale, `described now; delete these lines from NOT_YET_DESCRIBED:\n${stale.join("\n")}`).toEqual([]); | |
| 64 | }); | |
| 65 | ||
| 110 | 66 | test("NOT_YET_DESCRIBED is sorted and names registered routes", () => { |
| 111 | 67 | expect([...NOT_YET_DESCRIBED]).toEqual([...NOT_YET_DESCRIBED].sort()); |
| 112 | 68 | const all = new Set(registered()); |
new above
offline