import type { AttemptStatus, BodyDisposition, HttpScenarioAssertionGroups, RetryReason, } from "../../../scripts/lib/http-scenario-evidence.ts"; export type { AttemptStatus, BodyDisposition, HttpScenarioAssertionGroups, RetryReason, } from "../../../scripts/lib/http-scenario-evidence.ts"; export const HTTP_SCENARIO_RECEIPT_SCHEMA_VERSION = 1 as const; export const HTTP_SCENARIO_IDS = Object.freeze([ "success", "empty", "slow", "network-error", "timeout", "aborted", "content-type-mismatch", "malformed-json", "envelope-mismatch", "schema-mismatch", "response-too-large", "unauthenticated-401", "forbidden-403", "not-found-404", "conflict-409", "validation-422", "rate-limited-429", "server-retry-success", "server-terminal-503", ] as const); export type HttpScenarioId = (typeof HTTP_SCENARIO_IDS)[number]; export const HTTP_SCENARIO_OPERATION_IDS = Object.freeze([ "LIST_REFERENCE_RESOURCES", "GET_REFERENCE_RESOURCE", "CREATE_REFERENCE_RESOURCE", ] as const); export type HttpScenarioOperationId = (typeof HTTP_SCENARIO_OPERATION_IDS)[number]; export type HttpScenarioExecutionId = `${HttpScenarioOperationId}::${HttpScenarioId}`; export type HttpScenarioExpectation = Readonly<{ executionId: HttpScenarioExecutionId; operationId: HttpScenarioOperationId; scenarioId: HttpScenarioId; expected: HttpScenarioAssertionGroups; testDeadlineOverrideMs: number | null; }>; const encoder = new TextEncoder(); const jsonBytes = (value: unknown) => encoder.encode(JSON.stringify(value)).byteLength; const RESOURCE = Object.freeze({ id: "reference-1", name: "Reference", createdAt: "2026-07-26T00:00:00.000Z", }); const CREATED = Object.freeze({ id: "reference-created", name: "Created", createdAt: "2026-07-26T00:00:00.000Z", }); function operationFacts(operationId: HttpScenarioOperationId) { const command = operationId === "CREATE_REFERENCE_RESOURCE"; return Object.freeze({ command, ceiling: operationId === "LIST_REFERENCE_RESOURCES" ? 262_144 : 32_768, payload: operationId === "LIST_REFERENCE_RESOURCES" ? [RESOURCE] : operationId === "GET_REFERENCE_RESOURCE" ? RESOURCE : CREATED, }); } function problem(status: number, code: string) { return { type: `https://api.test/problems/${code.toLowerCase()}`, title: code, status, code, }; } function attemptBody( disposition: BodyDisposition, pulledBytes: number, ceiling: number, ) { return Object.freeze({ disposition, pulledBytes, ceiling }); } function expectationFor( operationId: HttpScenarioOperationId, scenarioId: HttpScenarioId, ): HttpScenarioExpectation { const { command, ceiling, payload } = operationFacts(operationId); let statuses: AttemptStatus[] = [200]; let kind = "SUCCESS"; let detail: string | null = null; let outcomeEffect = command ? "APPLIED_CONFIRMED" : "NOT_APPLICABLE"; let observer = outcomeEffect; let retryReasons: RetryReason[] = []; let media: (string | null)[] = ["application/json"]; let bodies = [attemptBody("FULLY_READ_WITHIN_BOUND", jsonBytes(payload), ceiling)]; let scopeEnd: "CURRENT" | "STALE" = "CURRENT"; let scopeSignal: "ACTIVE" | "ABORTED" = "ACTIVE"; let cancellationOwner: "NONE" | "CALLER" | "SCOPE_FENCE" | "DEADLINE" = "NONE"; let testDeadlineOverrideMs: number | null = null; switch (scenarioId) { case "success": case "slow": break; case "empty": bodies = [attemptBody("FULLY_READ_WITHIN_BOUND", jsonBytes([]), ceiling)]; break; case "network-error": { const attemptCount = command ? 1 : 3; statuses = Array.from({ length: attemptCount }, () => "NETWORK_REJECTION"); kind = "TRANSPORT_FAILURE"; detail = "NETWORK_FAILURE"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_STARTED"; observer = "NETWORK_FAILURE"; retryReasons = command ? [] : ["NETWORK_FAILURE", "NETWORK_FAILURE"]; media = Array.from({ length: attemptCount }, () => null); bodies = Array.from({ length: attemptCount }, () => attemptBody("NO_RESPONSE", 0, 0), ); break; } case "timeout": statuses = ["PENDING_ABORT"]; kind = "TRANSPORT_FAILURE"; detail = "TIMEOUT"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_STARTED"; observer = "TIMEOUT"; media = [null]; bodies = [attemptBody("NO_RESPONSE", 0, 0)]; cancellationOwner = "DEADLINE"; testDeadlineOverrideMs = 500; break; case "aborted": statuses = ["PENDING_ABORT"]; media = [null]; bodies = [attemptBody("NO_RESPONSE", 0, 0)]; if (operationId === "LIST_REFERENCE_RESOURCES") { kind = "CANCELLED"; outcomeEffect = "NOT_STARTED"; observer = "CANCELLED"; cancellationOwner = "CALLER"; } else { scopeSignal = "ABORTED"; kind = "TRANSPORT_FAILURE"; detail = "ABORTED_BY_SCOPE"; outcomeEffect = "NOT_STARTED"; observer = "SCOPE_FENCED"; scopeEnd = "STALE"; cancellationOwner = "SCOPE_FENCE"; } break; case "content-type-mismatch": kind = "CONTRACT_VIOLATION"; detail = "CONTENT_TYPE_MISMATCH"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLICABLE"; observer = "CONTRACT_VIOLATION"; media = ["text/html"]; bodies = [ attemptBody( "FULLY_READ_WITHIN_BOUND", encoder.encode("not json").byteLength, ceiling, ), ]; break; case "malformed-json": kind = "CONTRACT_VIOLATION"; detail = "JSON_INVALID"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLICABLE"; observer = "CONTRACT_VIOLATION"; bodies = [ attemptBody( "FULLY_READ_WITHIN_BOUND", encoder.encode("{invalid").byteLength, ceiling, ), ]; break; case "envelope-mismatch": kind = "CONTRACT_VIOLATION"; detail = "SUCCESS_SCHEMA_INVALID"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLICABLE"; observer = "CONTRACT_VIOLATION"; bodies = [ attemptBody("FULLY_READ_WITHIN_BOUND", jsonBytes({ data: payload }), ceiling), ]; break; case "schema-mismatch": kind = "CONTRACT_VIOLATION"; detail = "SUCCESS_SCHEMA_INVALID"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLICABLE"; observer = "CONTRACT_VIOLATION"; bodies = [ attemptBody( "FULLY_READ_WITHIN_BOUND", jsonBytes({ unexpected: true }), ceiling, ), ]; break; case "response-too-large": kind = "CONTRACT_VIOLATION"; detail = "RESPONSE_TOO_LARGE"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLICABLE"; observer = "RESPONSE_TOO_LARGE"; bodies = [attemptBody("REJECTED_LIMIT", 0, ceiling)]; break; case "unauthenticated-401": statuses = [401]; kind = "UNAUTHENTICATED"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLICABLE"; observer = "UNAUTHENTICATED"; bodies = [attemptBody("CANCELLED_WITHOUT_READ", 0, 0)]; break; case "forbidden-403": statuses = [403]; kind = "FORBIDDEN"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLICABLE"; observer = "FORBIDDEN"; bodies = [attemptBody("CANCELLED_WITHOUT_READ", 0, 0)]; break; case "not-found-404": statuses = [404]; kind = "PROBLEM"; detail = "404"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLIED"; observer = command ? "MAYBE_APPLIED" : "NOT_STARTED"; bodies = [ attemptBody( "FULLY_READ_WITHIN_BOUND", jsonBytes(problem(404, "NOT_FOUND")), 65_536, ), ]; break; case "conflict-409": statuses = [409]; kind = "PROBLEM"; detail = "409"; outcomeEffect = "NOT_APPLIED"; observer = command ? "NOT_APPLIED" : "NOT_STARTED"; bodies = [ attemptBody( "FULLY_READ_WITHIN_BOUND", jsonBytes(problem(409, "CONFLICT")), 65_536, ), ]; break; case "validation-422": statuses = [422]; kind = "PROBLEM"; detail = "422"; outcomeEffect = "NOT_APPLIED"; observer = command ? "NOT_APPLIED" : "NOT_STARTED"; bodies = [ attemptBody( "FULLY_READ_WITHIN_BOUND", jsonBytes(problem(422, "VALIDATION_REJECTED")), 65_536, ), ]; break; case "rate-limited-429": { const attemptCount = command ? 1 : 3; statuses = Array.from({ length: attemptCount }, () => 429); kind = "RATE_LIMITED"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLICABLE"; observer = "RATE_LIMITED"; retryReasons = command ? [] : ["HTTP_429", "HTTP_429"]; media = Array.from({ length: attemptCount }, () => "application/json"); bodies = Array.from({ length: attemptCount }, () => attemptBody("CANCELLED_WITHOUT_READ", 0, 0), ); break; } case "server-retry-success": if (command) { statuses = [503]; kind = "PROBLEM"; detail = "503"; outcomeEffect = "MAYBE_APPLIED"; observer = "MAYBE_APPLIED"; bodies = [ attemptBody( "FULLY_READ_WITHIN_BOUND", jsonBytes(problem(503, "SERVER_FAILURE")), 65_536, ), ]; } else { statuses = [503, 200]; retryReasons = ["HTTP_503"]; media = ["application/json", "application/json"]; bodies = [ attemptBody( "FULLY_READ_WITHIN_BOUND", jsonBytes(problem(503, "SERVER_FAILURE")), 65_536, ), attemptBody("FULLY_READ_WITHIN_BOUND", jsonBytes(payload), ceiling), ]; } break; case "server-terminal-503": { const attemptCount = command ? 1 : 3; statuses = Array.from({ length: attemptCount }, () => 503); kind = "PROBLEM"; detail = "503"; outcomeEffect = command ? "MAYBE_APPLIED" : "NOT_APPLIED"; observer = command ? "MAYBE_APPLIED" : "NOT_STARTED"; retryReasons = command ? [] : ["HTTP_503", "HTTP_503"]; media = Array.from({ length: attemptCount }, () => "application/json"); bodies = Array.from({ length: attemptCount }, () => attemptBody( "FULLY_READ_WITHIN_BOUND", jsonBytes(problem(503, "SERVER_FAILURE")), 65_536, ), ); break; } } const executionId = `${operationId}::${scenarioId}` as const; return Object.freeze({ executionId, operationId, scenarioId, expected: Object.freeze({ status: Object.freeze({ attempts: Object.freeze(statuses), final: statuses.at(-1)!, }), outcome: Object.freeze({ kind, detail }), effect: Object.freeze({ outcome: outcomeEffect, observer }), retry: Object.freeze({ count: retryReasons.length, reasons: Object.freeze(retryReasons), }), fetch: Object.freeze({ count: statuses.length, observerAttempts: statuses.length, agrees: true, }), media: Object.freeze({ attempts: Object.freeze(media), final: media.at(-1) ?? null, }), body: Object.freeze({ attempts: Object.freeze(bodies) }), scope: Object.freeze({ start: "CURRENT" as const, end: scopeEnd, signal: scopeSignal, cancellationOwner, }), }), testDeadlineOverrideMs, }); } const OPERATION_SCENARIOS = Object.freeze({ LIST_REFERENCE_RESOURCES: HTTP_SCENARIO_IDS, GET_REFERENCE_RESOURCE: Object.freeze( HTTP_SCENARIO_IDS.filter((scenario) => scenario !== "empty"), ), CREATE_REFERENCE_RESOURCE: Object.freeze( HTTP_SCENARIO_IDS.filter( (scenario) => scenario !== "empty" && scenario !== "aborted", ), ), } satisfies Readonly< Record >); export const HTTP_SCENARIO_EXPECTATIONS = Object.freeze( HTTP_SCENARIO_OPERATION_IDS.flatMap((operationId) => OPERATION_SCENARIOS[operationId].map((scenarioId) => expectationFor(operationId, scenarioId), ), ), ); export const HTTP_SCENARIO_EXECUTION_IDS = Object.freeze( HTTP_SCENARIO_EXPECTATIONS.map((expectation) => expectation.executionId), ); export const OPERATION_SCENARIO_CATALOG = Object.freeze( Object.fromEntries( HTTP_SCENARIO_OPERATION_IDS.map((operationId) => [ operationId, Object.freeze( HTTP_SCENARIO_EXPECTATIONS.filter( (expectation) => expectation.operationId === operationId, ).map((expectation) => expectation.scenarioId), ), ]), ) as Readonly< Record >, ); export function assertOperationScenario( operationId: HttpScenarioOperationId, scenario: HttpScenarioId, ) { if (!OPERATION_SCENARIO_CATALOG[operationId].includes(scenario)) { throw new Error(`Scenario ${scenario} is not declared for ${operationId}`); } return scenario; }