test: execute the HTTP scenario catalog
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { delay, http, HttpResponse, type JsonBodyType } from "msw";
|
||||
|
||||
import type { HttpScenarioId } from "../scenarios/catalog.ts";
|
||||
import type {
|
||||
HttpScenarioId,
|
||||
HttpScenarioOperationId,
|
||||
} from "../scenarios/catalog.ts";
|
||||
import { assertOperationScenario } from "../scenarios/catalog.ts";
|
||||
|
||||
export type ReferenceResourceFixture = Readonly<{
|
||||
@@ -20,6 +23,11 @@ type ScenarioOptions = Readonly<{
|
||||
>
|
||||
>;
|
||||
resources?: ReferenceResourceFixture[];
|
||||
onAttempt?(attempt: Readonly<{
|
||||
operationId: HttpScenarioOperationId;
|
||||
scenarioId: HttpScenarioId;
|
||||
attempt: number;
|
||||
}>): void;
|
||||
onList?(search: string): void;
|
||||
onCreate?(body: Readonly<Record<string, unknown>>): void;
|
||||
}>;
|
||||
@@ -31,12 +39,15 @@ const DEFAULT_RESOURCE = Object.freeze({
|
||||
});
|
||||
|
||||
async function scenarioResponse(
|
||||
operationId: HttpScenarioOperationId,
|
||||
scenario: HttpScenarioId,
|
||||
payload: JsonBodyType,
|
||||
attempt: number,
|
||||
) {
|
||||
if (scenario === "slow") await delay(50);
|
||||
if (scenario === "timeout") await delay(30_000);
|
||||
if (scenario === "timeout" || scenario === "aborted") {
|
||||
await delay("infinite");
|
||||
}
|
||||
if (scenario === "network-error") return HttpResponse.error();
|
||||
if (scenario === "content-type-mismatch") {
|
||||
return new HttpResponse("<html>not json</html>", {
|
||||
@@ -55,8 +66,7 @@ async function scenarioResponse(
|
||||
return HttpResponse.json({ unexpected: true });
|
||||
}
|
||||
if (
|
||||
scenario === "auth-persistent-401" ||
|
||||
(scenario === "auth-recover-once" && attempt === 1)
|
||||
scenario === "unauthenticated-401"
|
||||
) {
|
||||
return HttpResponse.json(problem(401, "AUTH_REQUIRED"), {
|
||||
status: 401,
|
||||
@@ -84,13 +94,34 @@ async function scenarioResponse(
|
||||
});
|
||||
}
|
||||
if (
|
||||
scenario === "server-terminal-500" ||
|
||||
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<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(bytes);
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": String(bytes.byteLength),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
return HttpResponse.json(payload);
|
||||
}
|
||||
|
||||
@@ -125,10 +156,17 @@ export function createReferenceScenarioHandlers(options: ScenarioOptions = {}) {
|
||||
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,
|
||||
nextAttempt("LIST_REFERENCE_RESOURCES"),
|
||||
attempt,
|
||||
);
|
||||
}),
|
||||
http.post(
|
||||
@@ -149,10 +187,17 @@ export function createReferenceScenarioHandlers(options: ScenarioOptions = {}) {
|
||||
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,
|
||||
nextAttempt("CREATE_REFERENCE_RESOURCE"),
|
||||
attempt,
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -163,10 +208,17 @@ export function createReferenceScenarioHandlers(options: ScenarioOptions = {}) {
|
||||
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,
|
||||
nextAttempt("GET_REFERENCE_RESOURCE"),
|
||||
attempt,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
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",
|
||||
@@ -9,37 +25,411 @@ export const HTTP_SCENARIO_IDS = Object.freeze([
|
||||
"malformed-json",
|
||||
"envelope-mismatch",
|
||||
"schema-mismatch",
|
||||
"auth-recover-once",
|
||||
"auth-persistent-401",
|
||||
"response-too-large",
|
||||
"unauthenticated-401",
|
||||
"forbidden-403",
|
||||
"not-found-404",
|
||||
"conflict-409",
|
||||
"validation-422",
|
||||
"rate-limited-429",
|
||||
"server-retry-success",
|
||||
"server-terminal-500",
|
||||
"server-terminal-503",
|
||||
] as const);
|
||||
|
||||
export type HttpScenarioId = (typeof HTTP_SCENARIO_IDS)[number];
|
||||
|
||||
export const OPERATION_SCENARIO_CATALOG = Object.freeze({
|
||||
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("<html>not json</html>").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) => !["empty", "aborted"].includes(scenario),
|
||||
(scenario) => scenario !== "empty" && scenario !== "aborted",
|
||||
),
|
||||
),
|
||||
GET_REFERENCE_RESOURCE: HTTP_SCENARIO_IDS,
|
||||
} satisfies Readonly<Record<string, readonly HttpScenarioId[]>>);
|
||||
} satisfies Readonly<
|
||||
Record<HttpScenarioOperationId, readonly HttpScenarioId[]>
|
||||
>);
|
||||
|
||||
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<HttpScenarioOperationId, readonly HttpScenarioId[]>
|
||||
>,
|
||||
);
|
||||
|
||||
export function assertOperationScenario(
|
||||
operationId: keyof typeof OPERATION_SCENARIO_CATALOG,
|
||||
operationId: HttpScenarioOperationId,
|
||||
scenario: HttpScenarioId,
|
||||
) {
|
||||
if (!OPERATION_SCENARIO_CATALOG[operationId].includes(scenario)) {
|
||||
throw new Error(
|
||||
`Scenario ${scenario} is not declared for ${operationId}`,
|
||||
);
|
||||
throw new Error(`Scenario ${scenario} is not declared for ${operationId}`);
|
||||
}
|
||||
return scenario;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user