118 lines
3.6 KiB
TypeScript
118 lines
3.6 KiB
TypeScript
/**
|
|
* 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);
|
|
});
|
|
});
|