import { delay, http, HttpResponse, type JsonBodyType } from "msw"; import type { HttpScenarioId, HttpScenarioOperationId, } from "../scenarios/catalog.ts"; import { assertOperationScenario } from "../scenarios/catalog.ts"; export type ReferenceResourceFixture = Readonly<{ id: string; name: string; createdAt?: string; }>; type ScenarioOptions = Readonly<{ baseUrl?: string; scenarios?: Partial< Record< | "LIST_REFERENCE_RESOURCES" | "CREATE_REFERENCE_RESOURCE" | "GET_REFERENCE_RESOURCE", HttpScenarioId > >; resources?: ReferenceResourceFixture[]; onAttempt?(attempt: Readonly<{ operationId: HttpScenarioOperationId; scenarioId: HttpScenarioId; attempt: number; }>): void; onList?(search: string): void; onCreate?(body: Readonly>): void; }>; const DEFAULT_RESOURCE = Object.freeze({ id: "reference-1", name: "Reference", createdAt: "2026-07-26T00:00:00.000Z", }); async function scenarioResponse( operationId: HttpScenarioOperationId, scenario: HttpScenarioId, payload: JsonBodyType, attempt: number, ) { if (scenario === "slow") await delay(50); if (scenario === "timeout" || scenario === "aborted") { await delay("infinite"); } if (scenario === "network-error") return HttpResponse.error(); if (scenario === "content-type-mismatch") { return new HttpResponse("not json", { headers: { "Content-Type": "text/html" }, }); } if (scenario === "malformed-json") { return new HttpResponse("{invalid", { headers: { "Content-Type": "application/json" }, }); } if (scenario === "envelope-mismatch") { return HttpResponse.json({ data: payload }); } if (scenario === "schema-mismatch") { return HttpResponse.json({ unexpected: true }); } if ( scenario === "unauthenticated-401" ) { return HttpResponse.json(problem(401, "AUTH_REQUIRED"), { status: 401, }); } if (scenario === "forbidden-403") { return HttpResponse.json(problem(403, "FORBIDDEN"), { status: 403 }); } if (scenario === "not-found-404") { return HttpResponse.json(problem(404, "NOT_FOUND"), { status: 404 }); } if (scenario === "conflict-409") { return HttpResponse.json(problem(409, "CONFLICT"), { status: 409 }); } if (scenario === "validation-422") { return HttpResponse.json( problem(422, "VALIDATION_REJECTED"), { status: 422 }, ); } if (scenario === "rate-limited-429") { return HttpResponse.json(problem(429, "RATE_LIMITED"), { status: 429, headers: { "Retry-After": "1" }, }); } if ( scenario === "server-terminal-503" || (scenario === "server-retry-success" && attempt === 1) ) { return HttpResponse.json(problem(503, "SERVER_FAILURE"), { status: 503, }); } if (scenario === "response-too-large") { const ceiling = operationId === "LIST_REFERENCE_RESOURCES" ? 262_144 : 32_768; const bytes = new Uint8Array(ceiling + 1).fill(0x20); bytes[0] = 0x5b; bytes[bytes.length - 1] = 0x5d; return new HttpResponse( new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); }, }), { headers: { "Content-Type": "application/json", "Content-Length": String(bytes.byteLength), }, }, ); } return HttpResponse.json(payload); } function problem(status: number, code: string) { return Object.freeze({ type: `https://api.test/problems/${code.toLowerCase()}`, title: code, status, code, }); } export function createReferenceScenarioHandlers(options: ScenarioOptions = {}) { const baseUrl = options.baseUrl ?? "https://api.test"; const resources = options.resources ?? [{ ...DEFAULT_RESOURCE }]; const attempts = new Map(); const scenarioFor = ( operationId: keyof NonNullable, ) => assertOperationScenario( operationId, options.scenarios?.[operationId] ?? "success", ); const nextAttempt = (operationId: string) => { const next = (attempts.get(operationId) ?? 0) + 1; attempts.set(operationId, next); return next; }; return [ http.get(`${baseUrl}/api/reference-resources`, ({ request }) => { options.onList?.(new URL(request.url).search); const scenario = scenarioFor("LIST_REFERENCE_RESOURCES"); const payload = scenario === "empty" ? [] : resources; const attempt = nextAttempt("LIST_REFERENCE_RESOURCES"); options.onAttempt?.({ operationId: "LIST_REFERENCE_RESOURCES", scenarioId: scenario, attempt, }); return scenarioResponse( "LIST_REFERENCE_RESOURCES", scenario, payload, attempt, ); }), http.post( `${baseUrl}/api/reference-resources`, async ({ request }) => { const rawBody = await request.json(); const body = rawBody && typeof rawBody === "object" && !Array.isArray(rawBody) ? (rawBody as Readonly>) : {}; options.onCreate?.(body); const scenario = scenarioFor("CREATE_REFERENCE_RESOURCE"); const created = { id: "reference-created", name: String(body.name ?? "Created"), createdAt: "2026-07-26T00:00:00.000Z", }; if (scenario === "success") resources.push(created); const attempt = nextAttempt("CREATE_REFERENCE_RESOURCE"); options.onAttempt?.({ operationId: "CREATE_REFERENCE_RESOURCE", scenarioId: scenario, attempt, }); return scenarioResponse( "CREATE_REFERENCE_RESOURCE", scenario, created, attempt, ); }, ), http.get( `${baseUrl}/api/reference-resources/:resourceId`, ({ params }) => { const scenario = scenarioFor("GET_REFERENCE_RESOURCE"); const resource = resources.find((entry) => entry.id === params.resourceId) ?? DEFAULT_RESOURCE; const attempt = nextAttempt("GET_REFERENCE_RESOURCE"); options.onAttempt?.({ operationId: "GET_REFERENCE_RESOURCE", scenarioId: scenario, attempt, }); return scenarioResponse( "GET_REFERENCE_RESOURCE", scenario, resource, attempt, ); }, ), ] as const; }