@@ -6,12 +6,16 @@ import {
|
||||
assertBoundedItems,
|
||||
assertBoundedText,
|
||||
createObjectUrlLease,
|
||||
createObjectUrlLeasePool,
|
||||
createSeededRandom,
|
||||
formatBytes,
|
||||
planBlobDownloads,
|
||||
sanitizeDownloadFilename,
|
||||
secureRandomBytes,
|
||||
secureRandomInt,
|
||||
shuffleSeeded,
|
||||
triggerBlobDownload,
|
||||
triggerBlobDownloads,
|
||||
} from "../../src/helpers";
|
||||
|
||||
describe("shared ceilings and download safety", () => {
|
||||
@@ -85,6 +89,113 @@ describe("shared ceilings and download safety", () => {
|
||||
vi.stubGlobal("URL", originalUrl);
|
||||
}
|
||||
});
|
||||
|
||||
it("formats IEC and SI byte quantities deterministically", () => {
|
||||
expect(formatBytes(0)).toBe("0 B");
|
||||
expect(formatBytes(1536)).toBe("1.50 KiB");
|
||||
expect(formatBytes(1500, { system: "si", fractionDigits: 1 })).toBe(
|
||||
"1.5 kB",
|
||||
);
|
||||
expect(formatBytes(-1024)).toBe("-1.00 KiB");
|
||||
expect(formatBytes(Number.NaN)).toBe("unknown");
|
||||
});
|
||||
|
||||
it("replaces and revokes named object URL leases", () => {
|
||||
let sequence = 0;
|
||||
const revokeObjectURL = vi.fn();
|
||||
const pool = createObjectUrlLeasePool({
|
||||
createObjectURL: vi.fn(() => `blob:${++sequence}`),
|
||||
revokeObjectURL,
|
||||
});
|
||||
const first = pool.create("preview", new Blob(["one"]));
|
||||
const second = pool.create("preview", new Blob(["two"]));
|
||||
expect(first.revoked).toBe(true);
|
||||
expect(second.url).toBe("blob:2");
|
||||
expect(pool.size).toBe(1);
|
||||
expect(pool.revoke("missing")).toBe(false);
|
||||
pool.revokeAll();
|
||||
expect(second.revoked).toBe(true);
|
||||
expect(pool.size).toBe(0);
|
||||
expect(revokeObjectURL).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("plans collision-free batch names and revokes every URL", () => {
|
||||
const items = [
|
||||
{ blob: new Blob(["a"]), filename: "Report.txt" },
|
||||
{ blob: new Blob(["b"]), filename: "report.txt" },
|
||||
{ blob: new Blob(["c"]), filename: "../unsafe?.txt" },
|
||||
];
|
||||
expect(planBlobDownloads(items).map((item) => item.filename)).toEqual([
|
||||
"Report.txt",
|
||||
"report (2).txt",
|
||||
"_unsafe_.txt",
|
||||
]);
|
||||
|
||||
const revokeObjectURL = vi.fn();
|
||||
let index = 0;
|
||||
const click = vi
|
||||
.spyOn(HTMLAnchorElement.prototype, "click")
|
||||
.mockImplementation(() => undefined);
|
||||
let scheduled: (() => void) | undefined;
|
||||
const batch = triggerBlobDownloads(items, {
|
||||
ownerDocument: document,
|
||||
urlApi: {
|
||||
createObjectURL: () => `blob:batch-${++index}`,
|
||||
revokeObjectURL,
|
||||
},
|
||||
schedule: (callback) => {
|
||||
scheduled = callback;
|
||||
},
|
||||
});
|
||||
expect(click).toHaveBeenCalledTimes(3);
|
||||
expect(batch.leases.every((lease) => !lease.revoked)).toBe(true);
|
||||
scheduled?.();
|
||||
expect(batch.leases.every((lease) => lease.revoked)).toBe(true);
|
||||
expect(revokeObjectURL).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("bounds lazy download plans before consuming the whole iterable", () => {
|
||||
let yielded = 0;
|
||||
function* many() {
|
||||
while (true) {
|
||||
yielded += 1;
|
||||
yield { blob: new Blob(["x"]), filename: "same.txt" };
|
||||
}
|
||||
}
|
||||
expect(() => planBlobDownloads(many(), { maximumFiles: 2 })).toThrow(
|
||||
HelperLimitError,
|
||||
);
|
||||
expect(yielded).toBe(3);
|
||||
expect(() => planBlobDownloads([], { maximumFilenameLength: 7 })).toThrow(
|
||||
/at least 8/u,
|
||||
);
|
||||
});
|
||||
|
||||
it("revokes already-created batch URLs when a click fails", () => {
|
||||
const revokeObjectURL = vi.fn();
|
||||
const click = vi
|
||||
.spyOn(HTMLAnchorElement.prototype, "click")
|
||||
.mockImplementationOnce(() => undefined)
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error("blocked");
|
||||
});
|
||||
expect(() =>
|
||||
triggerBlobDownloads(
|
||||
[
|
||||
{ blob: new Blob(["a"]), filename: "a.txt" },
|
||||
{ blob: new Blob(["b"]), filename: "b.txt" },
|
||||
],
|
||||
{
|
||||
ownerDocument: document,
|
||||
urlApi: {
|
||||
createObjectURL: () => `blob:test:${click.mock.calls.length}`,
|
||||
revokeObjectURL,
|
||||
},
|
||||
},
|
||||
),
|
||||
).toThrow("blocked");
|
||||
expect(revokeObjectURL).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("secure and seeded randomness", () => {
|
||||
|
||||
Reference in New Issue
Block a user