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"); }); });