/** * DMP-03 / DMP-22 — `kind:'specific'` with empty `chunkIds` is rejected. * * Empty-array semantics MUST NOT be a synonym for "everything". Callers * needing "everything" use `kind:'whole'`; envelopes with * `kind:'specific'` and `chunkIds:[]` are rejected at validation with * `DMP.EMPTY_REQUEST`. */ import { describe, expect, it } from "@suaveplan/testing/runner"; import { DMP_CODES, DmpError, decodeRequestPayload, encodeRequestPayload, selectForm, } from "../src/index.js"; const FILE_ID = "1".repeat(32); describe("DMP-03 — kind:'specific' rejects empty array", () => { it("kind:specific with empty chunkIds is rejected", { timeout: 5000, }, () => { try { encodeRequestPayload({ fileId: FILE_ID, request: { kind: "specific", chunkIds: [] }, priority: "normal", }); throw new Error("expected EMPTY_REQUEST"); } catch (err) { expect(err).toBeInstanceOf(DmpError); expect([ DMP_CODES.EMPTY_REQUEST, DMP_CODES.ENVELOPE_MALFORMED, ]).toContain((err as DmpError).code); } }); it("selectForm([]) throws EMPTY_REQUEST without producing an envelope", { timeout: 5000, }, () => { try { selectForm([]); throw new Error("expected EMPTY_REQUEST"); } catch (err) { expect(err).toBeInstanceOf(DmpError); expect((err as DmpError).code).toBe(DMP_CODES.EMPTY_REQUEST); } }); }); describe("DMP-22 — empty kind:'specific' rejection at decode", () => { it("specific with empty chunkIds rejected at validation", { timeout: 5000, }, () => { // Synthesise a wire envelope manually so we can carry an empty // chunkIds array past the encoder's gate. const wire = new TextEncoder().encode( JSON.stringify({ fileId: FILE_ID, request: { kind: "specific", chunkIds: [] }, priority: "normal", }) ); try { decodeRequestPayload(wire); throw new Error("expected rejection"); } catch (err) { expect(err).toBeInstanceOf(DmpError); // Either schema rejects (chunkIds must be non-empty) or the // explicit EMPTY_REQUEST gate fires — both satisfy DMP-22. expect([ DMP_CODES.EMPTY_REQUEST, DMP_CODES.ENVELOPE_MALFORMED, ]).toContain((err as DmpError).code); } }); });