Public: anyone can read this; the crew can change it.
Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:31u5r0o0wi is past its budget: trevato/forge: "no GET route answers 200 without a session phase unless the allowlist names it" failed on 3 pulls in th… #8028
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/7992-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 2 commits fork 2 threads · 2 unresolved
1 file
changed +19
−7
test/route-timing-coverage.test.ts +19 −7
| @@ -22,7 +22,11 @@ const UNTIMED: Record<string, string> = { | ||
| 22 | 22 | "/metrics": "Prometheus text exposition, scraped by token; not a page read", |
| 23 | 23 | }; |
| 24 | 24 | |
| 25 | /** Routes the fixture cannot exercise (they 5xx on it) → why; any other 429/5xx fails the walk. */ | |
| 25 | /** | |
| 26 | * Routes the walk does not request → why. The board routes spawn git against a | |
| 27 | * board repository the fixture lacks: they 5xx on it, and on a loaded machine | |
| 28 | * the spawn is the slow request. Any other 429/5xx fails the walk. | |
| 29 | */ | |
| 26 | 30 | const UNEXERCISED: Record<string, string> = { |
| 27 | 31 | "/api/v1/boards/:slug": "the fixture has no board repository to spawn git in", |
| 28 | 32 | "/api/v1/boards/:slug/panels/:id": "the fixture has no board repository to spawn git in", |
| @@ -66,26 +70,34 @@ afterAll(async () => { | ||
| 66 | 70 | await rm(dataDir, { recursive: true, force: true }); |
| 67 | 71 | }); |
| 68 | 72 | |
| 73 | /** One request's whole allowance: a route slower than this is the finding, not the machine. */ | |
| 74 | const REQUEST_MS = 5_000; | |
| 75 | const BATCH = 8; | |
| 76 | ||
| 69 | 77 | async function timed(pattern: string): Promise<{ status: number; session: boolean }> { |
| 70 | 78 | const path = pattern.replace(/:([a-z_]+)/g, (_, k: string) => FILL[k] ?? "x").replace(/\*[a-z_]+/, "x"); |
| 71 | const res = await fetch(`${servers.url}${path}`, { headers: { authorization: `Bearer ${bearer}` }, redirect: "manual" }); | |
| 79 | const res = await fetch(`${servers.url}${path}`, { | |
| 80 | headers: { authorization: `Bearer ${bearer}` }, | |
| 81 | redirect: "manual", | |
| 82 | signal: AbortSignal.timeout(REQUEST_MS), | |
| 83 | }); | |
| 72 | 84 | await res.body?.cancel(); |
| 73 | 85 | const phases = (res.headers.get("server-timing") ?? "").split(",").map((p) => p.trim().split(";")[0]); |
| 74 | 86 | return { status: res.status, session: phases.includes("session") }; |
| 75 | 87 | } |
| 76 | 88 | |
| 77 | /** Walking every route is many requests; a loaded check machine must not turn that into a verdict. */ | |
| 78 | const WALK_MS = 30_000; | |
| 79 | const BATCH = 8; | |
| 89 | /** Routes walked: those the fixture can exercise. Requests run in batches, so the walk's ceiling is batches x REQUEST_MS. */ | |
| 90 | const walked = (): string[] => getPatterns().filter((p) => !(p in UNEXERCISED)); | |
| 91 | const WALK_MS = Math.ceil(walked().length / BATCH) * REQUEST_MS + 5_000; | |
| 80 | 92 | |
| 81 | 93 | test("no GET route answers 200 without a session phase unless the allowlist names it", async () => { |
| 82 | const patterns = getPatterns(); | |
| 94 | const patterns = walked(); | |
| 83 | 95 | const results: Awaited<ReturnType<typeof timed>>[] = []; |
| 84 | 96 | for (let i = 0; i < patterns.length; i += BATCH) { |
| 85 | 97 | results.push(...(await Promise.all(patterns.slice(i, i + BATCH).map((p) => timed(p))))); |
| 86 | 98 | } |
| 87 | 99 | // A throttled or failing route is not a route that was checked. |
| 88 | const unchecked = patterns.filter((_, i) => results[i]!.status === 429 || results[i]!.status >= 500).filter((p) => !(p in UNEXERCISED)); | |
| 100 | const unchecked = patterns.filter((_, i) => results[i]!.status === 429 || results[i]!.status >= 500); | |
| 89 | 101 | expect(unchecked).toEqual([]); |
outdated
WALK_MS = 180_000 hides the problem rather than fixing it, and it conflicts with #1242. Derive it from the per-request limit and attempts, or remove the need for it. |
||
| 90 | 102 | expect(results.filter((r) => r.status === 200).length).toBeGreaterThan(0); |
| 91 | 103 | const untimed = patterns.filter((_, i) => results[i]!.status === 200 && !results[i]!.session); |
new above
offline
The retry loop adds behaviour (retry on 429/5xx, then settle) with no test that would fail without it. Add a unit test with an injected fetch, or drop the retries.