import { delay, http, HttpResponse } from "msw"; import { failureEnvelope, successEnvelope, } from "../contracts/envelopes.js"; import type { HttpScenarioId } from "../scenarios/catalog.js"; import { assertOperationScenario } from "../scenarios/catalog.js"; 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[]; 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( scenario: HttpScenarioId, payload: unknown, attempt: number, ) { if (scenario === "slow") await delay(50); if (scenario === "timeout") await delay(30_000); 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(successEnvelope({ unexpected: true })); } if ( scenario === "auth-persistent-401" || (scenario === "auth-recover-once" && attempt === 1) ) { return HttpResponse.json(failureEnvelope("AUTH_REQUIRED"), { status: 401, }); } if (scenario === "forbidden-403") { return HttpResponse.json(failureEnvelope("FORBIDDEN"), { status: 403 }); } if (scenario === "not-found-404") { return HttpResponse.json(failureEnvelope("NOT_FOUND"), { status: 404 }); } if (scenario === "conflict-409") { return HttpResponse.json(failureEnvelope("CONFLICT"), { status: 409 }); } if (scenario === "validation-422") { return HttpResponse.json( failureEnvelope("VALIDATION_REJECTED", { issues: [{ path: "name", code: "too_small" }], }), { status: 422 }, ); } if (scenario === "rate-limited-429") { return HttpResponse.json(failureEnvelope("RATE_LIMITED"), { status: 429, headers: { "Retry-After": "1" }, }); } if ( scenario === "server-terminal-500" || (scenario === "server-retry-success" && attempt === 1) ) { return HttpResponse.json(failureEnvelope("SERVER_FAILURE"), { status: 503, }); } return HttpResponse.json(successEnvelope(payload)); } 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; return scenarioResponse( scenario, payload, nextAttempt("LIST_REFERENCE_RESOURCES"), ); }), 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); return scenarioResponse( scenario, created, nextAttempt("CREATE_REFERENCE_RESOURCE"), ); }, ), 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; return scenarioResponse( scenario, resource, nextAttempt("GET_REFERENCE_RESOURCE"), ); }, ), ] as const; }