import { afterEach, describe, expect, it, vi } from "vitest"; // PCH-01-014: bound attacker-controlled page count and per-page render // dimensions. Runs in the default node environment (no `document`), so // renderPageToCanvas throws before any canvas is allocated — exactly the // behaviour that lets us assert the clamps fire (or don't) by inspecting which // per-page error surfaces. function mockPdf(numPages: number, viewport: { width: number; height: number }) { const getPage = vi.fn(async () => ({ getViewport: () => viewport })); vi.doMock("pdfjs-dist", () => ({ GlobalWorkerOptions: { workerSrc: "stub" }, getDocument: () => ({ promise: Promise.resolve({ numPages, getPage }) }), })); return getPage; } function fakeFile(size = 1000): File { return { size, arrayBuffer: async () => new ArrayBuffer(8) } as unknown as File; } describe("PCH-01-014 — Codex PDF page/dimension clamps", () => { afterEach(() => { vi.resetModules(); vi.doUnmock("pdfjs-dist"); }); it("importCodexPDF truncates page count at MAX_PDF_PAGES", async () => { const getPage = mockPdf(1000, { width: 600, height: 800 }); const { importCodexPDF } = await import("./pdf-import"); const result = await importCodexPDF(fakeFile()); // 3 probe pages (detectRenderScale) + at most 250 main-loop pages. expect(getPage.mock.calls.length).toBeLessThanOrEqual(253); expect(result.errors.some((e) => e.includes("only the first 250"))).toBe(true); }); it("renderPageToCanvas rejects an oversized viewport before allocating", async () => { mockPdf(1, { width: 14400 * 6, height: 14400 * 6 }); const { importCodexPDF } = await import("./pdf-import"); const result = await importCodexPDF(fakeFile()); // The dimension guard throws before document.createElement, so the page // error is the render-limit message, not a "document is not defined". expect(result.errors.some((e) => e.includes("render limit"))).toBe(true); }); it("does not fire the dimension clamp on a normal-sized page", async () => { mockPdf(3, { width: 600, height: 800 }); const { importCodexPDF } = await import("./pdf-import"); const result = await importCodexPDF(fakeFile()); // No render-limit rejection for a legitimate A4-ish page, and no // page-count truncation message for a small document. expect(result.errors.some((e) => e.includes("render limit"))).toBe(false); expect(result.errors.some((e) => e.includes("only the first"))).toBe(false); }); });