From 80fbe15792de3f06915d41bbb34b3ae5cc3a2d7e Mon Sep 17 00:00:00 2001 From: DEls Date: Fri, 18 Sep 2026 19:10:35 +0200 Subject: [PATCH] fix(dn): detect PDF content for page range extraction --- .../task-2-report.md | 37 +++++++++++++++++++ src/lib/dn-pipeline/split.test.ts | 5 +++ src/lib/dn-pipeline/split.ts | 13 +++++-- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/.superpowers/sdd/2026-09-18-hybrid-delivery-note-ingestion/task-2-report.md b/.superpowers/sdd/2026-09-18-hybrid-delivery-note-ingestion/task-2-report.md index 394abbd..80327ec 100644 --- a/.superpowers/sdd/2026-09-18-hybrid-delivery-note-ingestion/task-2-report.md +++ b/.superpowers/sdd/2026-09-18-hybrid-delivery-note-ingestion/task-2-report.md @@ -68,3 +68,40 @@ Exit status: 0. - `npm install sharp` reports existing audit vulnerabilities and an unrelated `isolated-vm` engine warning on Node 22; neither blocks this task's tests or typecheck. + +## Fix Round 1 + +Addressed review findings: + +- `extractPageRange()` now detects PDF content using `%PDF-` magic bytes, + independent of declared MIME, matching the normalizer and split adapter. +- Updated `splitPages()` documentation to describe decoding and canonical PNG + output instead of pass-through behavior. +- Added a regression test proving a multi-page PDF declared as `image/jpeg` + extracts only the requested range. + +RED evidence: + +```text +npx vitest run src/lib/dn-pipeline/split.test.ts +... +× split (requires poppler + the fixture) > extracts a page range from a PDF despite an incorrect declared MIME + → expected 4 to be 2 +Test Files 1 failed (1) +Tests 1 failed | 5 passed (6) +``` + +GREEN and verification evidence: + +```text +npx vitest run src/lib/dn-pipeline/normalise-source.test.ts src/lib/dn-pipeline/split.test.ts +Test Files 2 passed (2) +Tests 12 passed (12) + +npm run typecheck +> mega-infrastructure-dashboard@1.0.0 typecheck +> tsc --noEmit + +git diff --check +exit status: 0 +``` diff --git a/src/lib/dn-pipeline/split.test.ts b/src/lib/dn-pipeline/split.test.ts index aeb2c39..0f04dbf 100644 --- a/src/lib/dn-pipeline/split.test.ts +++ b/src/lib/dn-pipeline/split.test.ts @@ -31,6 +31,11 @@ describe.skipIf(!existsSync(FIXTURE))("split (requires poppler + the fixture)", expect(await pageCount(out, PDF)).toBe(2); }); + it("extracts a page range from a PDF despite an incorrect declared MIME", async () => { + const out = await extractPageRange(buf, "image/jpeg", 2, 3); + expect(await pageCount(out, PDF)).toBe(2); + }); + it("rejects a range outside the document", async () => { await expect(extractPageRange(buf, PDF, 3, 9)).rejects.toThrow("out of range"); }); diff --git a/src/lib/dn-pipeline/split.ts b/src/lib/dn-pipeline/split.ts index ba17cb5..d88a27c 100644 --- a/src/lib/dn-pipeline/split.ts +++ b/src/lib/dn-pipeline/split.ts @@ -15,6 +15,11 @@ import { promisify } from "node:util"; import { normaliseSource } from "./normalise-source"; const run = promisify(execFile); +const PDF_MAGIC = Buffer.from("%PDF-"); + +function isPdf(buffer: Buffer): boolean { + return buffer.subarray(0, PDF_MAGIC.length).equals(PDF_MAGIC); +} export interface PageImage { page: number; @@ -23,7 +28,7 @@ export interface PageImage { /** Page count of a PDF. Images are always one page. */ export async function pageCount(buffer: Buffer, mime: string): Promise { - if (!buffer.subarray(0, 5).equals(Buffer.from("%PDF-"))) return 1; + if (!isPdf(buffer)) return 1; const dir = await mkdtemp(join(tmpdir(), "dn-count-")); try { const path = join(dir, "in.pdf"); @@ -38,8 +43,8 @@ export async function pageCount(buffer: Buffer, mime: string): Promise { } /** - * Render every page to PNG. An image upload passes through untouched as - * a single page. + * Decode and render every page to canonical PNG. An image upload becomes one + * decoded, orientation-corrected PNG page. */ export async function splitPages(buffer: Buffer, mime: string): Promise { const pages = await normaliseSource(buffer, mime); @@ -57,7 +62,7 @@ export async function extractPageRange( pageFrom: number, pageTo: number, ): Promise { - if (mime !== "application/pdf") return buffer; + if (!isPdf(buffer)) return buffer; const src = await PDFDocument.load(buffer); const out = await PDFDocument.create();