import { afterEach, describe, expect, test } from "bun:test"; import { detail } from "../src/commands/detail"; // The portal contract requires backoff on 429/5xx, and `/scrape` calls // `detail` once per shortlisted posting - a burst that trips the rate limiter // is exactly when it matters. The handler used to call fetch() directly with // no retry loop: on a 429 it wrote API_ERROR and exited after ONE attempt, // while every other portal's detail command retried. These tests drive the // real command handler (not the wrapper in isolation) with a stubbed fetch, // instant timers, and process.exit turned into a throw so the exit path can // be asserted. On the pre-fix handler the first test sees 1 call and an exit. const originalFetch = globalThis.fetch; const originalSetTimeout = globalThis.setTimeout; const originalExit = process.exit; const originalLog = console.log; const originalStderrWrite = process.stderr.write; afterEach(() => { globalThis.fetch = originalFetch; globalThis.setTimeout = originalSetTimeout; process.exit = originalExit; console.log = originalLog; process.stderr.write = originalStderrWrite; }); const JSON_LD_PAGE = `
`; function instantTimers() { globalThis.setTimeout = ((fn: () => void) => originalSetTimeout(fn, 0)) as unknown as typeof setTimeout; } function stubFetch(responses: Array<() => Response>): { calls: number } { const state = { calls: 0 }; globalThis.fetch = (async () => { const i = Math.min(state.calls, responses.length - 1); state.calls++; return responses[i](); }) as unknown as typeof fetch; return state; } function captureOutput(): { stdout: string[]; stderr: string[] } { const out = { stdout: [] as string[], stderr: [] as string[] }; console.log = ((...args: unknown[]) => out.stdout.push(args.join(" "))) as typeof console.log; process.stderr.write = ((chunk: string | Uint8Array) => { out.stderr.push(String(chunk)); return true; }) as typeof process.stderr.write; return out; } function firstStderrJson(out: { stderr: string[] }): unknown { const firstLine = out.stderr.join("").trim().split("\n")[0]; return JSON.parse(firstLine); } class ExitCalled extends Error { constructor(public code: number | undefined) { super(`process.exit(${code})`); } } function throwingExit() { process.exit = ((code?: number) => { throw new ExitCalled(code); }) as unknown as typeof process.exit; } async function runDetail(slug: string): Promise<{ exit: number | null }> { const handler = (detail as unknown as { handler: (ctx: unknown) => Promise