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
+126
View File
@@ -0,0 +1,126 @@
import { HttpResponse, http } from "msw";
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { createExternalAuthSessionAdapter } from "../../src/adapters/auth/external-session-adapter.ts";
import { createHttpClient } from "../../src/adapters/http/client.ts";
import { TEST_HTTP_CONTRACT } from "../helpers/http-contract-fixture.ts";
let responseStatuses: number[] = [];
const server = setupServer(
http.get("https://api.test/api/entities", () => {
const status = responseStatuses.shift() ?? 200;
if (status === 401) {
return HttpResponse.json(
{
success: false,
error: { code: "UNAUTHENTICATED" },
meta: { requestId: "request-1", traceId: "trace-1" },
},
{ status },
);
}
return HttpResponse.json({
success: true,
data: [{ id: "resource-1", name: "Example" }],
meta: { requestId: "request-2", traceId: "trace-1" },
});
}),
);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => {
responseStatuses = [];
server.resetHandlers();
});
afterAll(() => server.close());
const clock = { now: () => 0, sleep: async () => {} };
type HttpDependencies = Parameters<typeof createHttpClient>[0];
function testClient(options: HttpDependencies) {
return createHttpClient({ ...TEST_HTTP_CONTRACT, ...options });
}
type ExternalSessionOwner = Parameters<
typeof createExternalAuthSessionAdapter
>[0];
function createOwner(
overrides: Partial<ExternalSessionOwner> = {},
): ExternalSessionOwner {
return {
readState: () => "authenticated",
subscribe: () => () => {},
beginSignIn: async () => {},
signOut: async () => {},
attachCredential: async () => ({ headers: {} }),
recoverSession: async () => "restored",
notifyUnauthenticated: () => {},
...overrides,
};
}
describe("bounded 401 session recovery", () => {
it("calls recovery once and replays a safe request once", async () => {
responseStatuses = [401, 200];
const recoverSession = vi.fn(async () => "restored" as const);
const authSession = createExternalAuthSessionAdapter(createOwner({
attachCredential: async () => ({ headers: {} }),
recoverSession,
notifyUnauthenticated: vi.fn(),
}));
const client = testClient({
baseUrl: "https://api.test",
authSession,
clock,
});
await expect(client.execute("LIST_ENTITIES")).resolves.toMatchObject({
ok: true,
});
expect(recoverSession).toHaveBeenCalledTimes(1);
});
it("stops after a second 401 and notifies unauthenticated once", async () => {
responseStatuses = [401, 401];
const notifyUnauthenticated = vi.fn();
const authSession = createExternalAuthSessionAdapter(createOwner({
attachCredential: async () => ({ headers: {} }),
recoverSession: async () => "restored",
notifyUnauthenticated,
}));
const client = testClient({
baseUrl: "https://api.test",
authSession,
clock,
});
await expect(client.execute("LIST_ENTITIES")).resolves.toMatchObject({
ok: false,
error: { kind: "AUTH_REQUIRED" },
});
expect(notifyUnauthenticated).toHaveBeenCalledTimes(1);
});
it("normalizes attach and invalid recovery failures", async () => {
const attachFailure = createExternalAuthSessionAdapter(createOwner({
attachCredential: async () => {
throw new Error("credential detail");
},
recoverSession: async () => "restored",
notifyUnauthenticated: vi.fn(),
}));
const client = testClient({
baseUrl: "https://api.test",
authSession: attachFailure,
clock,
});
await expect(client.execute("LIST_ENTITIES")).resolves.toMatchObject({
ok: false,
error: { kind: "AUTH_INTEGRATION_FAILURE" },
});
});
});
+23
View File
@@ -0,0 +1,23 @@
import { HttpResponse, http } from "msw";
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
const server = setupServer(
http.get("https://example.test/health", () =>
HttpResponse.json({ success: true, data: { status: "ok" } }),
),
);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe("integration test level", () => {
it("uses MSW to isolate the HTTP boundary", async () => {
const response = await fetch("https://example.test/health");
await expect(response.json()).resolves.toEqual({
success: true,
data: { status: "ok" },
});
});
});
+174
View File
@@ -0,0 +1,174 @@
import { HttpResponse, http } from "msw";
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
import { createHttpClient } from "../../src/adapters/http/client.ts";
import { TEST_HTTP_CONTRACT } from "../helpers/http-contract-fixture.ts";
let attempts = 0;
const server = setupServer(
http.get("https://api.test/api/entities", () => {
attempts += 1;
if (attempts < 3) {
return HttpResponse.json(
{ success: false, error: { code: "TEMPORARY" } },
{ status: 503 },
);
}
return HttpResponse.json({
success: true,
data: [{ id: "resource-1", name: "Example" }],
meta: { requestId: "request-1", traceId: "trace-1" },
});
}),
);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => {
attempts = 0;
server.resetHandlers();
});
afterAll(() => server.close());
const clock = {
now: () => 0,
sleep: async () => {},
};
type HttpDependencies = Parameters<typeof createHttpClient>[0];
function testClient(options: HttpDependencies) {
return createHttpClient({ ...TEST_HTTP_CONTRACT, ...options });
}
describe("shared HTTP client", () => {
it("retries a safe request at most twice and returns validated data", async () => {
const client = testClient({
baseUrl: "https://api.test",
clock,
random: () => 0,
});
await expect(
client.execute("LIST_ENTITIES", { routeId: "TEST_ROUTE" }),
).resolves.toMatchObject({
ok: true,
value: [{ id: "resource-1", displayName: "Example" }],
meta: { requestId: "request-1" },
});
expect(attempts).toBe(3);
});
it("rejects a non-JSON response without exposing its body", async () => {
server.use(
http.get(
"https://api.test/api/entities",
() => new HttpResponse("<secret>raw body</secret>", { status: 502 }),
),
);
const client = testClient({ baseUrl: "https://api.test", clock });
const result = await client.execute("LIST_ENTITIES");
expect(result).toMatchObject({
ok: false,
error: { kind: "CONTENT_TYPE_MISMATCH" },
});
expect(JSON.stringify(result)).not.toContain("raw body");
});
it("classifies malformed JSON and invalid payloads at the boundary", async () => {
server.use(
http.get(
"https://api.test/api/entities",
() =>
new HttpResponse("{", {
headers: { "Content-Type": "application/json" },
}),
),
);
const client = testClient({ baseUrl: "https://api.test", clock });
await expect(client.execute("LIST_ENTITIES")).resolves.toMatchObject({
ok: false,
error: { kind: "MALFORMED_JSON" },
});
server.use(
http.get("https://api.test/api/entities", () =>
HttpResponse.json({
success: true,
data: [{ id: "resource-1", name: 42 }],
meta: { requestId: "request-1", traceId: "trace-1" },
}),
),
);
await expect(client.execute("LIST_ENTITIES")).resolves.toMatchObject({
ok: false,
error: { kind: "SCHEMA_MISMATCH" },
});
});
it("projects only approved 422 issue path and code metadata", async () => {
server.use(
http.post("https://api.test/api/entities", () =>
HttpResponse.json(
{
success: false,
error: {
code: "INVALID_INPUT",
message: "raw backend secret",
details: {
issues: [
{ path: "name", code: "REQUIRED", message: "raw field copy" },
{ path: 42, code: "INVALID" },
],
},
},
meta: { requestId: "request-422", traceId: "trace-422" },
},
{ status: 422 },
),
),
);
const client = testClient({ baseUrl: "https://api.test", clock });
const result = await client.execute("CREATE_ENTITY", {
routeId: "TEST_FORM",
body: { name: "Valid" },
});
expect(result).toMatchObject({
ok: false,
error: {
kind: "VALIDATION_REJECTED",
validationIssues: [{ path: "name", code: "REQUIRED" }],
},
});
expect(JSON.stringify(result)).not.toContain("raw backend secret");
expect(JSON.stringify(result)).not.toContain("raw field copy");
});
it("guards mapper exceptions as a mapping contract violation", async () => {
server.use(
http.get("https://api.test/api/entities", () =>
HttpResponse.json({
success: true,
data: [{ id: "resource-1", name: "Example" }],
meta: { requestId: "request-1", traceId: "trace-1" },
}),
),
);
const client = testClient({
baseUrl: "https://api.test",
clock,
mapPayload: () => {
throw new Error("raw mapper detail");
},
});
const result = await client.execute("LIST_ENTITIES");
expect(result).toMatchObject({
ok: false,
error: { kind: "MAPPING_CONTRACT_VIOLATION" },
});
expect(JSON.stringify(result)).not.toContain("raw mapper detail");
});
});
+193
View File
@@ -0,0 +1,193 @@
import { describe, expect, it, vi } from "vitest";
import { createHttpClient } from "../../src/adapters/http/client.ts";
import type { DiagnosticRecordInput } from "../../src/contracts/diagnostics.ts";
import { TEST_HTTP_CONTRACT } from "../helpers/http-contract-fixture.ts";
function successResponse(data: unknown) {
return Response.json({
success: true,
data,
meta: { requestId: "request-1", traceId: "trace-1" },
});
}
function failureResponse(status: number) {
return Response.json(
{
success: false,
error: { code: "TEMPORARY" },
meta: { requestId: "request-1", traceId: "trace-1" },
},
{ status },
);
}
type EmittedEvent = Readonly<{
eventName: string;
attributes: Readonly<Record<string, unknown>>;
}>;
function harness(fetcher: typeof fetch, maxRetryAttempts = 1) {
const records: DiagnosticRecordInput[] = [];
const emitted: EmittedEvent[] = [];
let currentTime = 0;
const client = createHttpClient({
...TEST_HTTP_CONTRACT,
baseUrl: "https://api.test",
fetcher,
maxRetryAttempts,
clock: {
now: () => currentTime,
sleep: async () => {
currentTime += 150;
},
},
scheduler: {
setTimeout: () => 1,
clearTimeout: () => {},
},
correlationIdFactory: () => "correlation-fixed",
diagnostics: {
record(input) {
records.push(structuredClone(input));
},
},
telemetry: {
emit(eventName, attributes) {
emitted.push({
eventName,
attributes: structuredClone(attributes),
});
},
},
});
return { client, records, emitted };
}
describe("HTTP diagnostics and terminal telemetry", () => {
it("records one successful reference route outcome and no failure event", async () => {
const { client, records, emitted } = harness(
vi.fn(async () => successResponse([])),
);
await expect(
client.execute({
operationId: "LIST_ENTITIES",
routeId: "TEST_ROUTE",
}),
).resolves.toMatchObject({ ok: true });
expect(records).toEqual([
{
level: "info",
eventId: "http.request.completed",
context: {
route_id: "TEST_ROUTE",
operation_id: "LIST_ENTITIES",
correlation_id: "correlation-fixed",
outcome: "success",
error_kind: "NONE",
http_status_group: "2xx",
attempt_count_bucket: "1",
duration_bucket: "lt100ms",
},
},
]);
expect(emitted).toEqual([]);
});
it("summarizes retry recovery once without a terminal failure event", async () => {
const fetcher = vi
.fn<typeof fetch>()
.mockResolvedValueOnce(failureResponse(503))
.mockResolvedValueOnce(successResponse([]));
const { client, records, emitted } = harness(fetcher);
await expect(
client.execute({
operationId: "LIST_ENTITIES",
routeId: "TEST_ROUTE",
}),
).resolves.toMatchObject({ ok: true });
expect(fetcher).toHaveBeenCalledTimes(2);
expect(records).toHaveLength(1);
expect(records[0]).toMatchObject({
eventId: "http.request.completed",
context: {
outcome: "recovered",
correlation_id: "correlation-fixed",
duration_bucket: "100-499ms",
},
});
expect(emitted).toEqual([]);
});
it("emits one bounded terminal failure after all attempts", async () => {
const fetcher = vi.fn(async () => failureResponse(503));
const { client, records, emitted } = harness(fetcher);
await expect(
client.execute({
operationId: "CREATE_ENTITY",
routeId: "TEST_ROUTE",
body: { name: "private user input" },
idempotencyKey: "private-idempotency-key",
}),
).resolves.toMatchObject({
ok: false,
error: { kind: "SERVER_FAILURE" },
});
expect(fetcher).toHaveBeenCalledTimes(2);
expect(records).toHaveLength(1);
expect(emitted).toEqual([
{
eventName: "api.request.failed",
attributes: {
error_kind: "SERVER_FAILURE",
http_status_group: "5xx",
attempt_count_bucket: "2",
route_id: "TEST_ROUTE",
operation_id: "CREATE_ENTITY",
duration_bucket: "100-499ms",
},
},
]);
expect(JSON.stringify({ records, emitted })).not.toMatch(
/private user input|private-idempotency-key/,
);
});
it("records navigation abort without failure telemetry", async () => {
const caller = new AbortController();
caller.abort("navigation");
const fetcher = vi.fn(async (request: RequestInfo | URL) => {
const signal = (request as Request).signal;
if (signal.aborted) throw new DOMException("aborted", "AbortError");
return successResponse([]);
});
const { client, records, emitted } = harness(
fetcher as unknown as typeof fetch,
);
await expect(
client.execute({
operationId: "LIST_ENTITIES",
routeId: "TEST_ROUTE",
signal: caller.signal,
}),
).resolves.toMatchObject({
ok: false,
error: { kind: "REQUEST_ABORTED" },
});
expect(records).toHaveLength(1);
expect(records[0]).toMatchObject({
eventId: "http.request.completed",
context: { outcome: "aborted", error_kind: "REQUEST_ABORTED" },
});
expect(emitted).toEqual([]);
});
});
@@ -0,0 +1,375 @@
import { describe, expect, it, vi } from "vitest";
import { createHttpClient } from "../../src/adapters/http/client.ts";
import { createContractHttpExecutor } from "../../src/adapters/http/http-execution-v3.ts";
import {
entityQueryKeys,
TEST_HTTP_CONTRACT,
} from "../helpers/http-contract-fixture.ts";
import { TEST_CREATE_HTTP_CONTRACT } from "../helpers/external-contract-fixture.ts";
function successResponse(data: unknown) {
return Response.json({
success: true,
data,
meta: { requestId: "request-1", traceId: "trace-1" },
});
}
function failureResponse(status: number) {
return Response.json(
{
success: false,
error: { code: "TEMPORARY" },
meta: { requestId: "request-1", traceId: "trace-1" },
},
{ status },
);
}
function immediateClock() {
return { now: () => 0, sleep: async () => {} };
}
function recordingScheduler() {
const callbacks: Array<() => void> = [];
return {
callbacks,
setTimeout: vi.fn((callback: () => void) => {
callbacks.push(callback);
return callbacks.length - 1;
}),
clearTimeout: vi.fn(),
};
}
type HttpDependencies = Parameters<typeof createHttpClient>[0];
function testClient(options: HttpDependencies) {
return createHttpClient({ ...TEST_HTTP_CONTRACT, ...options });
}
describe("HTTP operation execution contract", () => {
it("permits a keyless command intent but rejects an unexpected key before dispatch", async () => {
const attachCredentials = vi.fn(() => ({
kind: "READY" as const,
headers: {},
credentials: "omit" as const,
}));
const observedKeys: Array<string | null> = [];
const fetcher = vi.fn(
async (_input: RequestInfo | URL, init?: RequestInit) => {
observedKeys.push(new Headers(init?.headers).get("Idempotency-Key"));
return Response.json(
{ id: "created", name: "Created" },
{ status: 201 },
);
},
);
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
attachCredentials,
fetcher,
});
const nonKeyedCommand = {
...TEST_CREATE_HTTP_CONTRACT,
contract: {
...TEST_CREATE_HTTP_CONTRACT.contract,
retrySemantics: "NEVER" as const,
commandRecovery: null,
},
};
const scope = Object.freeze({
generation: 1,
fingerprint: "scope-1",
identities: Object.freeze({}) as never,
signal: new AbortController().signal,
isCurrent: () => true,
});
const intent = Object.freeze({
intentId: "intent-1",
operationId: "TEST_CREATE_ENTITY",
canonicalInputIdentity: "opaque-input-identity",
createdAtMonotonicMs: 1,
});
await expect(
executor.execute(
nonKeyedCommand,
{ name: "created" },
{ scope, intent },
),
).resolves.toMatchObject({ kind: "SUCCESS" });
expect(observedKeys).toEqual([null]);
attachCredentials.mockClear();
fetcher.mockClear();
await expect(
executor.execute(
nonKeyedCommand,
{ name: "created" },
{ scope, intent: { ...intent, idempotencyKey: "unexpected-key" } },
),
).resolves.toMatchObject({
kind: "CONTRACT_VIOLATION",
violation: {
kind: "UNEXPECTED_IDEMPOTENCY_KEY",
operation: "REQUEST",
},
effect: "NOT_STARTED",
});
expect(attachCredentials).not.toHaveBeenCalled();
expect(fetcher).not.toHaveBeenCalled();
});
it("fails auth-required execution before fetch when the session integration is unavailable", async () => {
const fetcher = vi.fn();
const client = testClient({
baseUrl: "https://api.test",
fetcher,
authSession: undefined,
});
await expect(
client.execute({ operationId: "LIST_ENTITIES", routeId: "TEST_ROUTE" }),
).resolves.toMatchObject({
ok: false,
error: { kind: "AUTH_INTEGRATION_FAILURE" },
});
expect(fetcher).not.toHaveBeenCalled();
});
it("rejects an oversized response without materializing its JSON", async () => {
const largeOperation = {
...TEST_HTTP_CONTRACT.getOperation("LIST_ENTITIES"),
maxResponseBytes: 32,
responseMediaTypes: ["application/json"],
successStatuses: [200],
};
const client = testClient({
baseUrl: "https://api.test",
getOperation: () => largeOperation,
fetcher: vi.fn(async () =>
Response.json({
success: true,
data: [{ id: "one", name: "a".repeat(100) }],
meta: { requestId: "request-1", traceId: "trace-1" },
}),
),
});
await expect(client.execute("LIST_ENTITIES")).resolves.toMatchObject({
ok: false,
error: { kind: "RESPONSE_BODY_LIMIT" },
});
});
it("sends parsed search/body values and aligns canonical query identity", async () => {
const requests: Request[] = [];
const scheduler = recordingScheduler();
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
const request = input as Request;
requests.push(request);
if (request.method === "POST") {
return successResponse({ id: "created", name: "Trimmed" });
}
return successResponse([]);
});
const client = testClient({
baseUrl: "https://api.test",
fetcher,
clock: immediateClock(),
scheduler,
});
const filters = { tags: ["open", "new"], cursor: "a/b", limit: 5 };
await client.execute({
operationId: "LIST_ENTITIES",
routeId: "TEST_ROUTE",
searchParams: filters,
});
await client.execute({
operationId: "CREATE_ENTITY",
routeId: "TEST_ROUTE",
body: { name: " Trimmed " },
idempotencyKey: "logical-command",
});
expect(requests[0].url).toBe(
"https://api.test/api/entities?cursor=a%2Fb&limit=5&tags=open&tags=new",
);
expect(requests[0].headers.get("Idempotency-Key")).toBeNull();
expect(entityQueryKeys.list(filters).at(-1)).toEqual(filters);
await expect(requests[1].json()).resolves.toEqual({ name: "Trimmed" });
expect(requests[1].headers.get("Idempotency-Key")).toBe("logical-command");
expect(requests[1].url).not.toContain("logical-command");
expect(requests[0].headers.get("X-Correlation-ID")).toBeTruthy();
expect(requests[0].credentials).toBe("same-origin");
expect(requests[0].cache).toBe("no-store");
expect(requests[0].redirect).toBe("error");
expect(scheduler.setTimeout).toHaveBeenCalledTimes(2);
expect(scheduler.clearTimeout).toHaveBeenCalledTimes(2);
});
it("performs no fetch or timer work for invalid request input", async () => {
const fetcher = vi.fn();
const scheduler = recordingScheduler();
const client = testClient({
baseUrl: "https://api.test",
fetcher,
scheduler,
});
await expect(
client.execute({
operationId: "CREATE_ENTITY",
routeId: "TEST_ROUTE",
body: { name: " " },
}),
).resolves.toMatchObject({
ok: false,
error: { kind: "VALIDATION_REJECTED" },
});
expect(fetcher).not.toHaveBeenCalled();
expect(scheduler.setTimeout).not.toHaveBeenCalled();
expect(scheduler.clearTimeout).not.toHaveBeenCalled();
});
it.each([
[0, 1],
[1, 2],
[2, 3],
])(
"applies runtime max retry count %i as %i total attempts",
async (maxRetryAttempts, totalAttempts) => {
const fetcher = vi.fn(async () => failureResponse(503));
const scheduler = recordingScheduler();
const client = testClient({
baseUrl: "https://api.test",
fetcher,
clock: immediateClock(),
scheduler,
maxRetryAttempts,
});
await expect(
client.execute({
operationId: "LIST_ENTITIES",
routeId: "TEST_ROUTE",
}),
).resolves.toMatchObject({
ok: false,
error: { kind: "SERVER_FAILURE" },
});
expect(fetcher).toHaveBeenCalledTimes(totalAttempts);
expect(scheduler.setTimeout).toHaveBeenCalledTimes(totalAttempts);
expect(scheduler.clearTimeout).toHaveBeenCalledTimes(totalAttempts);
},
);
it("distinguishes a runtime timeout from caller navigation abort and cleans listeners", async () => {
const scheduler = recordingScheduler();
const fetcher = vi.fn(
(input: RequestInfo | URL) =>
new Promise<Response>((_resolve, reject) => {
(input as Request).signal.addEventListener(
"abort",
() => reject(new DOMException("aborted", "AbortError")),
{ once: true },
);
}),
);
const client = testClient({
baseUrl: "https://api.test",
fetcher,
scheduler,
maxRetryAttempts: 0,
});
const timeoutResult = client.execute({
operationId: "LIST_ENTITIES",
routeId: "TEST_ROUTE",
});
await vi.waitFor(() => expect(scheduler.callbacks).toHaveLength(1));
scheduler.callbacks[0]();
await expect(timeoutResult).resolves.toMatchObject({
ok: false,
error: { kind: "REQUEST_TIMEOUT" },
});
const caller = new AbortController();
const add = vi.spyOn(caller.signal, "addEventListener");
const remove = vi.spyOn(caller.signal, "removeEventListener");
const abortResult = client.execute({
operationId: "LIST_ENTITIES",
routeId: "TEST_ROUTE",
signal: caller.signal,
});
await vi.waitFor(() => expect(fetcher).toHaveBeenCalledTimes(2));
caller.abort("navigation");
await expect(abortResult).resolves.toMatchObject({
ok: false,
error: { kind: "REQUEST_ABORTED" },
});
expect(add).toHaveBeenCalledOnce();
expect(remove).toHaveBeenCalledOnce();
expect(scheduler.clearTimeout).toHaveBeenCalledTimes(2);
});
it("never retries unsafe, non-retryable status, or schema failures", async () => {
const unsafeOperation = {
method: "POST",
path: "/api/unsafe",
operationId: "UNSAFE",
auth: "none",
timeoutMs: null,
idempotency: "none",
retry: "never",
requestSource: "none",
requestSchema: "unused",
responseSchema: "EntityPayload",
owner: "test",
} as const;
const unsafeFetch = vi.fn(async () => failureResponse(503));
const unsafeClient = testClient({
baseUrl: "https://api.test",
fetcher: unsafeFetch,
clock: immediateClock(),
getOperation: () => unsafeOperation,
});
await unsafeClient.execute({
operationId: "UNSAFE",
routeId: "TEST",
});
expect(unsafeFetch).toHaveBeenCalledOnce();
const statusFetch = vi.fn(async () => failureResponse(500));
const statusClient = testClient({
baseUrl: "https://api.test",
fetcher: statusFetch,
clock: immediateClock(),
});
await statusClient.execute({
operationId: "LIST_ENTITIES",
routeId: "TEST_ROUTE",
});
expect(statusFetch).toHaveBeenCalledOnce();
const schemaFetch = vi.fn(async () =>
successResponse([{ id: "one", name: 42 }]),
);
const schemaScheduler = recordingScheduler();
const schemaClient = testClient({
baseUrl: "https://api.test",
fetcher: schemaFetch,
clock: immediateClock(),
scheduler: schemaScheduler,
});
await schemaClient.execute({
operationId: "LIST_ENTITIES",
routeId: "TEST_ROUTE",
});
expect(schemaFetch).toHaveBeenCalledOnce();
expect(schemaScheduler.clearTimeout).toHaveBeenCalledOnce();
});
});
@@ -0,0 +1,411 @@
import { mkdir, rm } from "node:fs/promises";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { readBoundedBytes } from "../../src/adapters/http/bounded-body-reader.ts";
import { createContractHttpExecutor } from "../../src/adapters/http/http-execution-v3.ts";
import {
type InstalledHttpContract,
} from "../../src/contracts/external-contract-runtime.ts";
import type { CacheScopeSnapshot } from "../../src/contracts/server-state-scope.ts";
import { REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION } from "../../src/features/reference-feature/contracts/reference-feature-contract-contribution.ts";
import {
computeHttpScenarioCatalogDigest,
httpScenarioReceiptSchema,
type AttemptStatus,
type BodyDisposition,
type HttpScenarioAssertionGroups,
type RetryReason,
} from "../../scripts/lib/http-scenario-evidence.ts";
import { writeValidatedJsonArtifact } from "../../scripts/lib/validated-json-artifact.ts";
import { createReferenceScenarioHandlers } from "../mocks/handlers/reference-resources.ts";
import { createStrictMockServer } from "../mocks/server.ts";
import {
HTTP_SCENARIO_EXECUTION_IDS,
HTTP_SCENARIO_EXPECTATIONS,
HTTP_SCENARIO_RECEIPT_SCHEMA_VERSION,
OPERATION_SCENARIO_CATALOG,
type HttpScenarioExpectation,
type HttpScenarioOperationId,
} from "../mocks/scenarios/catalog.ts";
const RECEIPT_PATH = path.resolve(
"artifacts/tests/http-scenario-executions.json",
);
const mockApi = createStrictMockServer();
beforeAll(mockApi.listen);
afterAll(mockApi.close);
type AttemptTrace = {
status: AttemptStatus;
media: string | null;
pulledBytes: number;
appliedCeiling: number | null;
completed: boolean;
cancelled: boolean;
};
function operationFor(operationId: HttpScenarioOperationId) {
const operation = REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http.find(
(candidate) => candidate.contract.operationId === operationId,
);
if (!operation) throw new Error(`Missing operation fixture: ${operationId}`);
return operation as InstalledHttpContract<unknown, unknown, unknown>;
}
function inputFor(operationId: HttpScenarioOperationId): unknown {
if (operationId === "LIST_REFERENCE_RESOURCES") return { limit: 20 };
if (operationId === "GET_REFERENCE_RESOURCE") {
return { resourceId: "reference-1" };
}
return { name: "Created" };
}
function normalizeMedia(response: Response): string | null {
const value = response.headers.get("content-type");
return value?.split(";", 1)[0]?.trim().toLowerCase() || null;
}
function tracingFetcher(attempts: AttemptTrace[]): typeof fetch {
return async (input, init) => {
const trace: AttemptTrace = {
status: "PENDING_ABORT",
media: null,
pulledBytes: 0,
appliedCeiling: null,
completed: false,
cancelled: false,
};
attempts.push(trace);
let response: Response;
try {
response = await fetch(input, init);
} catch (error) {
trace.status = init?.signal?.aborted
? "PENDING_ABORT"
: "NETWORK_REJECTION";
throw error;
}
trace.status = response.status;
trace.media = normalizeMedia(response);
if (!response.body) return response;
const reader = response.body.getReader();
const proxy = new ReadableStream<Uint8Array>(
{
async pull(controller) {
try {
const chunk = await reader.read();
if (chunk.done) {
trace.completed = true;
controller.close();
return;
}
trace.pulledBytes += chunk.value.byteLength;
controller.enqueue(chunk.value);
} catch (error) {
controller.error(error);
}
},
cancel(reason) {
trace.cancelled = true;
// MSW-transferred oversized bodies leave the original cancel promise
// permanently pending. Record and request cancellation without making
// the proxy response less responsive than the real executor contract.
void reader.cancel(reason).catch(() => {});
},
},
{ highWaterMark: 0 },
);
return new Response(proxy, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
};
}
function outcomeDetail(outcome: Readonly<Record<string, unknown>>): string | null {
if (outcome.kind === "CONTRACT_VIOLATION") {
return String((outcome.violation as Readonly<{ kind: string }>).kind);
}
if (outcome.kind === "TRANSPORT_FAILURE") {
return String((outcome.failure as Readonly<{ kind: string }>).kind);
}
if (outcome.kind === "PROBLEM") {
return String((outcome.metadata as Readonly<{ status: number }>).status);
}
return null;
}
function retryReason(status: AttemptStatus): RetryReason {
if (status === "NETWORK_REJECTION") return "NETWORK_FAILURE";
if (status === 429) return "HTTP_429";
if (status === 503) return "HTTP_503";
throw new Error(`Sleep followed a non-retryable attempt: ${String(status)}`);
}
function bodyDisposition(
trace: AttemptTrace,
outcome: Readonly<Record<string, unknown>>,
): BodyDisposition {
if (trace.status === "NETWORK_REJECTION" || trace.status === "PENDING_ABORT") {
return "NO_RESPONSE";
}
if (
outcome.kind === "CONTRACT_VIOLATION" &&
outcomeDetail(outcome) === "RESPONSE_TOO_LARGE" &&
trace.cancelled
) {
return "REJECTED_LIMIT";
}
if (trace.completed) return "FULLY_READ_WITHIN_BOUND";
if (trace.cancelled) return "CANCELLED_WITHOUT_READ";
throw new Error(`Response body was neither consumed nor cancelled: ${trace.status}`);
}
async function waitForHandler(
ready: Promise<void>,
executionId: string,
): Promise<void> {
let watchdog: ReturnType<typeof setTimeout> | undefined;
try {
await Promise.race([
ready,
new Promise<never>((_resolve, reject) => {
watchdog = setTimeout(
() => reject(new Error(`Handler readiness timed out: ${executionId}`)),
2_000,
);
}),
]);
} finally {
if (watchdog !== undefined) clearTimeout(watchdog);
}
}
async function executeScenario(
entry: HttpScenarioExpectation,
): Promise<HttpScenarioAssertionGroups> {
const physicalAttempts: AttemptTrace[] = [];
const sleeps: RetryReason[] = [];
const observations: Array<Readonly<{
outcome: string;
attempts: number;
certainty: string;
}>> = [];
const caller = new AbortController();
const scopeLifetime = new AbortController();
let scopeCurrent = true;
let handlerReady!: () => void;
const ready = new Promise<void>((resolve) => {
handlerReady = resolve;
});
const scope: CacheScopeSnapshot = Object.freeze({
generation: 1,
fingerprint: "scenario-scope-1",
identities: Object.freeze({}) as CacheScopeSnapshot["identities"],
signal: scopeLifetime.signal,
isCurrent: () => scopeCurrent,
});
const baseOperation = operationFor(entry.operationId);
const operation =
entry.testDeadlineOverrideMs === null
? baseOperation
: Object.freeze({
...baseOperation,
frontend: Object.freeze({
...baseOperation.frontend,
totalDeadlineMs: entry.testDeadlineOverrideMs,
}),
});
const fetcher = tracingFetcher(physicalAttempts);
const executor = createContractHttpExecutor({
baseUrl: "https://api.test",
maxRetryAttempts: 2,
attachCredentials: () => ({
kind: "READY",
headers: {},
credentials: "omit",
}),
fetcher,
random: () => 0,
readBoundedResponseBytes: async (response, maximumBytes) => {
const trace = physicalAttempts.at(-1);
if (!trace) throw new Error("Body reader ran before a physical attempt");
if (trace.appliedCeiling !== null) {
throw new Error("Body reader ran more than once for one attempt");
}
trace.appliedCeiling = maximumBytes;
return readBoundedBytes(response, maximumBytes);
},
sleep: async () => {
const prior = physicalAttempts.at(-1);
if (!prior) throw new Error("Retry sleep occurred before an attempt");
sleeps.push(retryReason(prior.status));
},
observe: (observation) => observations.push(observation),
});
mockApi.server.use(
...createReferenceScenarioHandlers({
scenarios: { [entry.operationId]: entry.scenarioId },
onAttempt(attempt) {
if (
attempt.operationId === entry.operationId &&
attempt.scenarioId === entry.scenarioId
) {
handlerReady();
}
},
}),
);
try {
const execution = executor.execute(operation, inputFor(entry.operationId), {
scope,
signal: caller.signal,
...(entry.operationId === "CREATE_REFERENCE_RESOURCE"
? {
intent: Object.freeze({
intentId: `intent-${entry.scenarioId}`,
operationId: entry.operationId,
canonicalInputIdentity: "scenario-input",
idempotencyKey: `scenario-${entry.scenarioId}`,
createdAtMonotonicMs: 1,
}),
}
: {}),
});
if (entry.scenarioId === "timeout" || entry.scenarioId === "aborted") {
await waitForHandler(ready, entry.executionId);
if (entry.scenarioId === "aborted") {
if (entry.operationId === "LIST_REFERENCE_RESOURCES") {
caller.abort();
} else {
scopeCurrent = false;
scopeLifetime.abort();
}
}
}
const outcome = (await execution) as unknown as Readonly<Record<string, unknown>>;
expect(observations, `${entry.executionId} observer count`).toHaveLength(1);
const observation = observations[0]!;
const observedSignal = scopeLifetime.signal.aborted ? "ABORTED" : "ACTIVE";
const cancellationOwner =
observation.certainty === "TIMEOUT"
? "DEADLINE"
: caller.signal.aborted
? "CALLER"
: scopeLifetime.signal.aborted
? "SCOPE_FENCE"
: "NONE";
const observed: HttpScenarioAssertionGroups = Object.freeze({
status: Object.freeze({
attempts: Object.freeze(physicalAttempts.map((attempt) => attempt.status)),
final: physicalAttempts.at(-1)!.status,
}),
outcome: Object.freeze({
kind: String(outcome.kind),
detail: outcomeDetail(outcome),
}),
effect: Object.freeze({
outcome: String(outcome.effect),
observer: observation.certainty,
}),
retry: Object.freeze({ count: sleeps.length, reasons: Object.freeze(sleeps) }),
fetch: Object.freeze({
count: physicalAttempts.length,
observerAttempts: observation.attempts,
agrees: physicalAttempts.length === observation.attempts,
}),
media: Object.freeze({
attempts: Object.freeze(physicalAttempts.map((attempt) => attempt.media)),
final: physicalAttempts.at(-1)?.media ?? null,
}),
body: Object.freeze({
attempts: Object.freeze(
physicalAttempts.map((attempt) =>
Object.freeze({
disposition: bodyDisposition(attempt, outcome),
pulledBytes: attempt.pulledBytes,
ceiling: attempt.appliedCeiling ?? 0,
}),
),
),
}),
scope: Object.freeze({
start: "CURRENT",
end: scopeCurrent ? "CURRENT" : "STALE",
signal: observedSignal,
cancellationOwner,
}),
});
return observed;
} finally {
caller.abort();
scopeLifetime.abort();
mockApi.reset();
}
}
describe("HTTP scenario catalog execution evidence", () => {
it("declares exactly the 54 behaviorally distinct operation scenarios", () => {
const executionIds = Object.entries(OPERATION_SCENARIO_CATALOG).flatMap(
([operationId, scenarioIds]) =>
scenarioIds.map((scenarioId) => `${operationId}::${scenarioId}`),
);
expect(OPERATION_SCENARIO_CATALOG.LIST_REFERENCE_RESOURCES).toHaveLength(19);
expect(OPERATION_SCENARIO_CATALOG.GET_REFERENCE_RESOURCE).toHaveLength(18);
expect(OPERATION_SCENARIO_CATALOG.CREATE_REFERENCE_RESOURCE).toHaveLength(17);
expect(executionIds).toHaveLength(54);
expect(new Set(executionIds)).toHaveLength(54);
expect(executionIds).toEqual(HTTP_SCENARIO_EXECUTION_IDS);
});
it("executes every declared scenario and publishes one complete receipt", async () => {
await rm(RECEIPT_PATH, { force: true });
const rows: Array<Readonly<{
executionId: string;
expected: HttpScenarioAssertionGroups;
observed: HttpScenarioAssertionGroups;
testDeadlineOverrideMs: number | null;
}>> = [];
for (const entry of HTTP_SCENARIO_EXPECTATIONS) {
const observed = await executeScenario(entry);
expect(observed, entry.executionId).toEqual(entry.expected);
rows.push(
Object.freeze({
executionId: entry.executionId,
expected: entry.expected,
observed,
testDeadlineOverrideMs: entry.testDeadlineOverrideMs,
}),
);
}
const sortedRows = [...rows].sort((left, right) =>
left.executionId.localeCompare(right.executionId),
);
await mkdir(path.dirname(RECEIPT_PATH), { recursive: true });
await writeValidatedJsonArtifact({
path: RECEIPT_PATH,
schema: httpScenarioReceiptSchema,
value: {
schemaVersion: HTTP_SCENARIO_RECEIPT_SCHEMA_VERSION,
catalogDigest: computeHttpScenarioCatalogDigest(
HTTP_SCENARIO_RECEIPT_SCHEMA_VERSION,
HTTP_SCENARIO_EXPECTATIONS,
),
catalogTotal: 54,
executedIds: sortedRows.map((row) => row.executionId),
rows: sortedRows,
},
});
}, 30_000);
});
@@ -0,0 +1,161 @@
import { spawnSync } from "node:child_process";
import { cp, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { expect, it } from "vitest";
import {
captureCiCandidateArchive,
withVerifiedCapturedCandidate,
} from "../../scripts/lib/ci-candidate-archive.ts";
import { verifyArchivedLocalEvidence } from "../../scripts/lib/local-release-evidence.ts";
import {
RELEASE_CANDIDATE_EVIDENCE_PATHS,
RELEASE_CANDIDATE_MANIFEST_PATH,
releaseCandidateManifestSchema,
} from "../../scripts/lib/release-candidate.ts";
it(
"builds a real candidate assessment and passes the default archived verifier from the captured archive",
async () => {
const sourceRoot = process.cwd();
const fixtureRoot = await mkdtemp(path.join(tmpdir(), "security-followup-producer-"));
try {
await cp(sourceRoot, fixtureRoot, {
recursive: true,
filter: (source) => {
const relative = path.relative(sourceRoot, source);
if (!relative) return true;
const first = relative.split(path.sep)[0];
return ![
".release",
"artifacts",
"dist",
"node_modules",
].includes(first ?? "");
},
});
await cp(path.join(sourceRoot, "artifacts"), path.join(fixtureRoot, "artifacts"), {
recursive: true,
});
await rm(path.join(fixtureRoot, "artifacts/release"), {
recursive: true,
force: true,
});
await symlink(path.join(sourceRoot, "node_modules"), path.join(fixtureRoot, "node_modules"), "dir");
const git = spawnSync("git", ["show", "-s", "--format=%H%n%ct", "HEAD"], {
cwd: sourceRoot,
encoding: "utf8",
});
expect(git.status, git.stderr).toBe(0);
const [revision, sourceDateEpoch] = git.stdout.trim().split(/\r?\n/u);
const build = spawnSync(
"corepack",
["pnpm", "build:release-candidate"],
{
cwd: fixtureRoot,
encoding: "utf8",
timeout: 120_000,
maxBuffer: 32 * 1024 * 1024,
env: {
...process.env,
CI: "true",
VITE_BUILD_ID: "security-followup-integration",
VITE_COMMIT_SHA: revision,
RELEASE_ID: "security-followup-integration",
SOURCE_DATE_EPOCH: sourceDateEpoch,
CI_RUNNER_IMAGE: `fixture@sha256:${"a".repeat(64)}`,
},
},
);
expect(build.status, `${build.stdout}\n${build.stderr}`).toBe(0);
const manifest = releaseCandidateManifestSchema.parse(
JSON.parse(
await readFile(path.join(fixtureRoot, RELEASE_CANDIDATE_MANIFEST_PATH), "utf8"),
) as unknown,
);
const archivePath = path.join(fixtureRoot, "candidate.tar.gz");
const archived = spawnSync(
"/usr/bin/tar",
[
"--sort=name",
"--mtime=@0",
"--owner=0",
"--group=0",
"--numeric-owner",
"-czf",
archivePath,
"dist",
...RELEASE_CANDIDATE_EVIDENCE_PATHS,
RELEASE_CANDIDATE_MANIFEST_PATH,
],
{ cwd: fixtureRoot, encoding: "utf8" },
);
expect(archived.status, archived.stderr).toBe(0);
const archiveBytes = await readFile(archivePath);
const expectedSha256 = await import("node:crypto").then(({ createHash }) =>
createHash("sha256").update(archiveBytes).digest("hex"),
);
const captured = await captureCiCandidateArchive({ archivePath, expectedSha256 });
const verified = await withVerifiedCapturedCandidate({
captured,
verify: ({ extractionRoot, manifest: extractedManifest }) =>
verifyArchivedLocalEvidence({
extractionRoot,
expectedManifest: extractedManifest,
}),
});
expect(manifest.files).toContainEqual(
expect.objectContaining({
path: "artifacts/security/local-evidence-assessment.json",
}),
);
expect(verified).toEqual(
expect.objectContaining({
status: "PASS",
identity: expect.objectContaining({ sourceRevision: revision }),
failures: [],
}),
);
const outsideRoot = await mkdtemp(path.join(tmpdir(), "security-followup-outside-"));
try {
await mkdir(path.join(outsideRoot, "config/security"), { recursive: true });
await writeFile(
path.join(outsideRoot, "config/security/dependency-policy.json"),
'{"contradictoryCheckoutCanary":"FAIL"}\n',
);
const outsideVerification = spawnSync(
process.execPath,
[
path.join(sourceRoot, "scripts/verify-archived-local-evidence.ts"),
"--archive",
archivePath,
"--sha256",
expectedSha256,
],
{
cwd: outsideRoot,
encoding: "utf8",
timeout: 120_000,
maxBuffer: 32 * 1024 * 1024,
},
);
expect(
outsideVerification.status,
`${outsideVerification.stdout}\n${outsideVerification.stderr}`,
).toBe(0);
expect(outsideVerification.stdout).toContain(
"Archived local evidence verification: PASS",
);
} finally {
await rm(outsideRoot, { recursive: true, force: true });
}
} finally {
await rm(fixtureRoot, { recursive: true, force: true });
}
},
150_000,
);