96 lines
3.0 KiB
TypeScript
96 lines
3.0 KiB
TypeScript
/**
|
|
* DMP-02 / DMP-07 — `kind:'whole'` returns capped chunk set; `maxChunks`
|
|
* default and enforcement.
|
|
*
|
|
* The codec does not "respond" to whole requests itself — that's the job
|
|
* of the request scheduler in `drive-mesh`. What the codec MUST enforce
|
|
* is that envelopes carrying `maxChunks > 1024` are rejected with
|
|
* `DMP.MAX_CHUNKS_EXCEEDED` so the scheduler never sees them. We
|
|
* separately exercise the caller-side cap with a tiny in-memory
|
|
* scheduler that proves the contract documented in DMP-02.
|
|
*/
|
|
|
|
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import {
|
|
DMP_CODES,
|
|
DmpError,
|
|
encodeRequestPayload,
|
|
MAX_CHUNKS_DEFAULT,
|
|
MAX_CHUNKS_HARD_CAP,
|
|
selectWhole,
|
|
} from "../src/index.js";
|
|
import type { ChunkRequestPayload } from "../src/types/protocol.types.js";
|
|
|
|
const FILE_ID = "f".repeat(32);
|
|
|
|
/** Caller-side cap helper. The actual scheduler lives in drive-mesh. */
|
|
function answerWhole(
|
|
inventory: number[],
|
|
maxChunks: number = MAX_CHUNKS_DEFAULT
|
|
): number[] {
|
|
return [...inventory].sort((a, b) => a - b).slice(0, maxChunks);
|
|
}
|
|
|
|
describe("DMP-02 — kind:'whole' returns capped chunk set", () => {
|
|
it("whole-form request returns at most maxChunks chunks", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const inventory = Array.from({ length: 1000 }, (_, i) => i);
|
|
const out = answerWhole(inventory, 64);
|
|
expect(out.length).toBe(64);
|
|
expect(out[0]).toBe(0);
|
|
expect(out[63]).toBe(63);
|
|
});
|
|
|
|
it("whole-form returns ALL chunks when inventory < maxChunks", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const out = answerWhole([0, 5, 12], 64);
|
|
expect(out).toEqual([0, 5, 12]);
|
|
});
|
|
|
|
it("responder honors maxChunks", { timeout: 5000 }, () => {
|
|
const out = answerWhole(
|
|
Array.from({ length: 200 }, (_, i) => i),
|
|
50
|
|
);
|
|
expect(out.length).toBe(50);
|
|
});
|
|
});
|
|
|
|
describe("DMP-07 — maxChunks enforcement", () => {
|
|
it("default is 64", { timeout: 5000 }, () => {
|
|
expect(MAX_CHUNKS_DEFAULT).toBe(64);
|
|
});
|
|
|
|
it("hard cap is 1024", { timeout: 5000 }, () => {
|
|
expect(MAX_CHUNKS_HARD_CAP).toBe(1024);
|
|
});
|
|
|
|
it("maxChunks > 1024 rejected", { timeout: 5000 }, () => {
|
|
const payload: ChunkRequestPayload = {
|
|
fileId: FILE_ID,
|
|
request: selectWhole(),
|
|
priority: "normal",
|
|
maxChunks: 4096,
|
|
};
|
|
try {
|
|
encodeRequestPayload(payload);
|
|
throw new Error("expected cap rejection");
|
|
} catch (err) {
|
|
expect(err).toBeInstanceOf(DmpError);
|
|
expect((err as DmpError).code).toBe(DMP_CODES.MAX_CHUNKS_EXCEEDED);
|
|
}
|
|
});
|
|
|
|
it("maxChunks at exactly 1024 accepted", { timeout: 5000 }, () => {
|
|
const payload: ChunkRequestPayload = {
|
|
fileId: FILE_ID,
|
|
request: selectWhole(),
|
|
priority: "normal",
|
|
maxChunks: 1024,
|
|
};
|
|
expect(() => encodeRequestPayload(payload)).not.toThrow();
|
|
});
|
|
});
|