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>; }>; 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() .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([]); }); });