pdf-import.clamp.test.ts

2.5 KB · raw

1import { afterEach, describe, expect, it, vi } from "vitest";23// PCH-01-014: bound attacker-controlled page count and per-page render4// dimensions. Runs in the default node environment (no `document`), so5// renderPageToCanvas throws before any canvas is allocated — exactly the6// behaviour that lets us assert the clamps fire (or don't) by inspecting which7// per-page error surfaces.89function mockPdf(numPages: number, viewport: { width: number; height: number }) {10  const getPage = vi.fn(async () => ({ getViewport: () => viewport }));11  vi.doMock("pdfjs-dist", () => ({12    GlobalWorkerOptions: { workerSrc: "stub" },13    getDocument: () => ({ promise: Promise.resolve({ numPages, getPage }) }),14  }));15  return getPage;16}1718function fakeFile(size = 1000): File {19  return { size, arrayBuffer: async () => new ArrayBuffer(8) } as unknown as File;20}2122describe("PCH-01-014 — Codex PDF page/dimension clamps", () => {23  afterEach(() => {24    vi.resetModules();25    vi.doUnmock("pdfjs-dist");26  });2728  it("importCodexPDF truncates page count at MAX_PDF_PAGES", async () => {29    const getPage = mockPdf(1000, { width: 600, height: 800 });30    const { importCodexPDF } = await import("./pdf-import");31    const result = await importCodexPDF(fakeFile());32    // 3 probe pages (detectRenderScale) + at most 250 main-loop pages.33    expect(getPage.mock.calls.length).toBeLessThanOrEqual(253);34    expect(result.errors.some((e) => e.includes("only the first 250"))).toBe(true);35  });3637  it("renderPageToCanvas rejects an oversized viewport before allocating", async () => {38    mockPdf(1, { width: 14400 * 6, height: 14400 * 6 });39    const { importCodexPDF } = await import("./pdf-import");40    const result = await importCodexPDF(fakeFile());41    // The dimension guard throws before document.createElement, so the page42    // error is the render-limit message, not a "document is not defined".43    expect(result.errors.some((e) => e.includes("render limit"))).toBe(true);44  });4546  it("does not fire the dimension clamp on a normal-sized page", async () => {47    mockPdf(3, { width: 600, height: 800 });48    const { importCodexPDF } = await import("./pdf-import");49    const result = await importCodexPDF(fakeFile());50    // No render-limit rejection for a legitimate A4-ish page, and no51    // page-count truncation message for a small document.52    expect(result.errors.some((e) => e.includes("render limit"))).toBe(false);53    expect(result.errors.some((e) => e.includes("only the first"))).toBe(false);54  });55});