/** * DMP-18 / DMP-19 — `ChunkErrorEnvelope` shape and codes; * `GENERATION_STALE` includes responder generation. */ import { describe, expect, it } from "@suaveplan/testing/runner"; import { DMP_CODES, DmpError, decodeErrorPayload, encodeErrorPayload, } from "../src/index.js"; import type { ChunkErrorCode, ChunkErrorPayload, } from "../src/types/protocol.types.js"; const FILE_ID = "9".repeat(64); describe("DMP-18 — ChunkErrorEnvelope shape", () => { it("every ChunkErrorCode round-trips through envelope encoding", { timeout: 5000, }, () => { const codes: readonly ChunkErrorCode[] = [ "NOT_FOUND", "ACCESS_DENIED", "BANDWIDTH_EXCEEDED", "GENERATION_STALE", "INTERNAL", ]; for (const code of codes) { const payload: ChunkErrorPayload = { fileId: FILE_ID, correlationId: `req-${code}`, code, ...(code === "GENERATION_STALE" ? { responderGeneration: 7 } : {}), }; const wire = encodeErrorPayload(payload); const round = decodeErrorPayload(wire); expect(round.code).toBe(code); expect(round.correlationId).toBe(`req-${code}`); } }); it("rejects malformed wire bytes", { timeout: 5000 }, () => { try { decodeErrorPayload(new TextEncoder().encode("not json")); throw new Error("expected rejection"); } catch (err) { expect(err).toBeInstanceOf(DmpError); expect((err as DmpError).code).toBe(DMP_CODES.ENVELOPE_MALFORMED); } }); }); describe("DMP-19 — GENERATION_STALE includes responder generation", () => { it("GENERATION_STALE error includes responder's current generation", { timeout: 5000, }, () => { const wire = encodeErrorPayload({ fileId: FILE_ID, correlationId: "req-1", code: "GENERATION_STALE", responderGeneration: 42, }); const decoded = decodeErrorPayload(wire); expect(decoded.responderGeneration).toBe(42); }); it("GENERATION_STALE without responderGeneration rejected at encode", { timeout: 5000, }, () => { try { encodeErrorPayload({ fileId: FILE_ID, correlationId: "req-1", code: "GENERATION_STALE", }); throw new Error("expected rejection"); } catch (err) { expect(err).toBeInstanceOf(DmpError); expect((err as DmpError).code).toBe(DMP_CODES.ENVELOPE_MALFORMED); } }); it("GENERATION_STALE without responderGeneration rejected at decode", { timeout: 5000, }, () => { const wire = new TextEncoder().encode( JSON.stringify({ fileId: FILE_ID, correlationId: "req-1", code: "GENERATION_STALE", }) ); try { decodeErrorPayload(wire); throw new Error("expected rejection"); } catch (err) { expect(err).toBeInstanceOf(DmpError); expect((err as DmpError).code).toBe(DMP_CODES.ENVELOPE_MALFORMED); } }); it("optional retryAfterMs field round-trips", { timeout: 5000 }, () => { const wire = encodeErrorPayload({ fileId: FILE_ID, correlationId: "req-2", code: "BANDWIDTH_EXCEEDED", retryAfterMs: 5_000, }); const decoded = decodeErrorPayload(wire); expect(decoded.retryAfterMs).toBe(5_000); }); });