Initial Commit
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user