chore: initialize from frontend template 4dc033c

This commit is contained in:
DongHyeonka
2026-08-13 18:23:26 +09:00
commit 40107eec84
897 changed files with 234824 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
export function successEnvelope<Value>(
data: Value,
requestId = "fixture-request",
) {
return Object.freeze({
success: true as const,
data: structuredClone(data),
meta: Object.freeze({
requestId,
traceId: "fixture-trace",
}),
});
}
export function failureEnvelope(
code: string,
details?: Readonly<Record<string, unknown>>,
) {
return Object.freeze({
success: false as const,
error: Object.freeze({
code,
...(details ? { details: structuredClone(details) } : {}),
}),
meta: Object.freeze({
requestId: "fixture-request",
traceId: "fixture-trace",
}),
});
}
+16
View File
@@ -0,0 +1,16 @@
import { http, HttpResponse } from "msw";
export function createBootstrapHandlers(
runtimeConfig: Readonly<Record<string, unknown>>,
releaseManifest: Readonly<Record<string, unknown>>,
baseUrl = "http://app.test",
) {
return [
http.get(`${baseUrl}/config.json`, () =>
HttpResponse.json(structuredClone(runtimeConfig)),
),
http.get(`${baseUrl}/release-manifest.json`, () =>
HttpResponse.json(structuredClone(releaseManifest)),
),
] as const;
}
+226
View File
@@ -0,0 +1,226 @@
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<Record<string, unknown>>): 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("<html>not json</html>", {
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<Uint8Array>({
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<string, number>();
const scenarioFor = (
operationId: keyof NonNullable<ScenarioOptions["scenarios"]>,
) =>
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<Record<string, unknown>>)
: {};
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;
}
+435
View File
@@ -0,0 +1,435 @@
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("<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) => scenario !== "empty" && scenario !== "aborted",
),
),
} 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: HttpScenarioOperationId,
scenario: HttpScenarioId,
) {
if (!OPERATION_SCENARIO_CATALOG[operationId].includes(scenario)) {
throw new Error(`Scenario ${scenario} is not declared for ${operationId}`);
}
return scenario;
}
+13
View File
@@ -0,0 +1,13 @@
import { setupServer } from "msw/node";
export function createStrictMockServer(
...handlers: Parameters<typeof setupServer>
) {
const server = setupServer(...handlers);
return Object.freeze({
server,
listen: () => server.listen({ onUnhandledRequest: "error" }),
reset: () => server.resetHandlers(),
close: () => server.close(),
});
}