Files
suaveplan/packages/foundation/data/packer-browser/src/range-response/range-response.test.ts
2026-05-27 23:47:53 +01:00

208 lines
7.5 KiB
TypeScript

import { describe, expect, it } from "@suaveplan/testing/runner";
import {
DEFAULT_RANGE_THRESHOLD_BYTES,
parseRange,
serveRangeResponse,
} from "./range-response.js";
function req(range: string | null, ifRange?: string): Request {
const headers = new Headers();
if (range !== null) headers.set("range", range);
if (ifRange !== undefined) headers.set("if-range", ifRange);
return new Request("https://example/asset", { headers });
}
const big = new Uint8Array(1024).map((_, i) => i % 256);
describe("parseRange", () => {
it("returns null when header is absent", { timeout: 5000 }, () => {
expect(parseRange(null, 100)).toBeNull();
});
it("returns null on a non-bytes unit", { timeout: 5000 }, () => {
expect(parseRange("items=0-9", 100)).toBeNull();
});
it("parses a single bytes range", { timeout: 5000 }, () => {
expect(parseRange("bytes=0-9", 100)).toEqual({ start: 0, end: 9 });
});
it("normalises an open-ended range", { timeout: 5000 }, () => {
expect(parseRange("bytes=100-", 200)).toEqual({ start: 100, end: 199 });
});
it("normalises a suffix range", { timeout: 5000 }, () => {
expect(parseRange("bytes=-10", 100)).toEqual({ start: 90, end: 99 });
});
it("clamps end to total-1 when over-shot", { timeout: 5000 }, () => {
expect(parseRange("bytes=0-9999", 100)).toEqual({ start: 0, end: 99 });
});
it("reports multi-range", { timeout: 5000 }, () => {
expect(parseRange("bytes=0-9,20-29", 100)).toBe("multi-range");
});
it("reports invalid on malformed header", { timeout: 5000 }, () => {
expect(parseRange("garbage", 100)).toBe("invalid");
expect(parseRange("bytes=abc-def", 100)).toBe("invalid");
expect(parseRange("bytes=-", 100)).toBe("invalid");
expect(parseRange("bytes=-0", 100)).toBe("invalid");
expect(parseRange("bytes=5-2", 100)).toBe("invalid");
expect(parseRange("bytes=200-300", 100)).toBe("invalid");
expect(parseRange("bytes=0-9-15", 100)).toBe("invalid");
expect(parseRange("bytes=-X", 100)).toBe("invalid");
// Open-ended range with non-numeric start (covers the rawEnd==="" branch validity check).
expect(parseRange("bytes=abc-", 100)).toBe("invalid");
expect(parseRange("bytes=-1-1", 100)).toBe("invalid");
});
});
describe("serveRangeResponse", () => {
it("yields 206 with Content-Range, Accept-Ranges, and Content-Length on a valid single range above threshold", {
timeout: 5000,
}, async () => {
const body = new Uint8Array(524288).map((_, i) => i % 256);
const res = serveRangeResponse(req("bytes=0-9"), body, {
rangeThreshold: 1024,
contentType: "audio/mpeg",
});
expect(res.status).toBe(206);
expect(res.headers.get("content-range")).toBe("bytes 0-9/524288");
expect(res.headers.get("accept-ranges")).toBe("bytes");
expect(res.headers.get("content-length")).toBe("10");
expect(res.headers.get("content-type")).toBe("audio/mpeg");
const ab = await res.arrayBuffer();
expect(ab.byteLength).toBe(10);
});
it("returns the full 200 when the body is below rangeThreshold", {
timeout: 5000,
}, async () => {
const res = serveRangeResponse(req("bytes=0-9"), big, {
rangeThreshold: 4096,
});
expect(res.status).toBe(200);
expect(res.headers.get("accept-ranges")).toBe("bytes");
expect(res.headers.get("content-length")).toBe(String(big.byteLength));
});
it("returns the full 200 when no Range header is supplied", {
timeout: 5000,
}, async () => {
const res = serveRangeResponse(req(null), big, { rangeThreshold: 1 });
expect(res.status).toBe(200);
expect(res.headers.get("accept-ranges")).toBe("bytes");
});
it("yields 416 on a multi-range request", { timeout: 5000 }, () => {
const res = serveRangeResponse(req("bytes=0-9,20-29"), big, {
rangeThreshold: 1,
});
expect(res.status).toBe(416);
expect(res.headers.get("content-range")).toBe(
`bytes */${big.byteLength}`
);
});
it("yields 416 on a malformed Range header", { timeout: 5000 }, () => {
const res = serveRangeResponse(req("bytes=abc-def"), big, {
rangeThreshold: 1,
});
expect(res.status).toBe(416);
});
it("yields 412 on If-Range etag mismatch", { timeout: 5000 }, () => {
const res = serveRangeResponse(req("bytes=0-9", '"v2"'), big, {
rangeThreshold: 1,
etag: '"v1"',
});
expect(res.status).toBe(412);
expect(res.headers.get("etag")).toBe('"v1"');
});
it("yields 206 on If-Range etag match", { timeout: 5000 }, () => {
const res = serveRangeResponse(req("bytes=0-9", '"v1"'), big, {
rangeThreshold: 1,
etag: '"v1"',
});
expect(res.status).toBe(206);
expect(res.headers.get("etag")).toBe('"v1"');
});
it("falls through to 200 on non-bytes range unit above threshold", {
timeout: 5000,
}, () => {
const res = serveRangeResponse(req("items=0-9"), big, {
rangeThreshold: 1,
});
expect(res.status).toBe(200);
});
it("uses defaults when opts is omitted entirely", { timeout: 5000 }, () => {
const res = serveRangeResponse(req(null), big);
expect(res.status).toBe(200);
expect(res.headers.get("accept-ranges")).toBe("bytes");
expect(res.headers.get("content-type")).toBe(
"application/octet-stream"
);
});
it("DEFAULT_RANGE_THRESHOLD_BYTES is 262144", { timeout: 5000 }, () => {
expect(DEFAULT_RANGE_THRESHOLD_BYTES).toBe(262144);
});
it("includes ETag on a below-threshold 200 when etag is supplied", {
timeout: 5000,
}, () => {
const res = serveRangeResponse(req(null), big, {
rangeThreshold: 1,
etag: '"v1"',
});
expect(res.headers.get("etag")).toBe('"v1"');
});
it("includes ETag on a 416 multi-range response when etag is supplied", {
timeout: 5000,
}, () => {
const res = serveRangeResponse(req("bytes=0-1,5-7"), big, {
rangeThreshold: 1,
etag: '"v9"',
});
expect(res.status).toBe(416);
expect(res.headers.get("etag")).toBe('"v9"');
});
it("includes ETag on a 206 partial-response when etag is supplied", {
timeout: 5000,
}, () => {
const res = serveRangeResponse(req("bytes=0-3"), big, {
rangeThreshold: 1,
etag: '"v7"',
});
expect(res.status).toBe(206);
expect(res.headers.get("etag")).toBe('"v7"');
});
it("falls back to 200 from the non-bytes-unit path when etag is supplied", {
timeout: 5000,
}, () => {
const res = serveRangeResponse(req("items=0-9"), big, {
rangeThreshold: 1,
etag: '"v3"',
});
expect(res.status).toBe(200);
expect(res.headers.get("etag")).toBe('"v3"');
});
it("handles suffix range above threshold", { timeout: 5000 }, async () => {
const body = new Uint8Array(8192);
for (let i = 0; i < body.byteLength; i++) body[i] = i & 0xff;
const res = serveRangeResponse(req("bytes=-100"), body, {
rangeThreshold: 1024,
});
expect(res.status).toBe(206);
expect(res.headers.get("content-range")).toBe("bytes 8092-8191/8192");
});
});