/** * Tier-2 depth tests: real multipage PDF heroes through real document tool routes. * * Exercises split-pdf, merge-pdf, compress-pdf, and pdf-to-image with the typed * fixture registry, verifying page counts (qpdf), valid PDF headers, and image * output via Sharp. Each tool is gated on its required binary. */ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { gsAvailable, qpdfAvailable, qpdfPageCount } from "@snapotter/doc-engine"; import sharp from "sharp"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { fixtures, readFixture } from "../../fixtures/index.js"; import { buildTestApp, createMultipartPayload, loginAsAdmin, type TestApp, } from "../test-server.js"; const PDF_MULTI = readFixture(fixtures.document.pdfMulti); // 6-page const PDF_2 = readFixture(fixtures.document.pdf2); // 2-page let testApp: TestApp; let app: TestApp["app"]; let adminToken: string; beforeAll(async () => { testApp = await buildTestApp(); app = testApp.app; adminToken = await loginAsAdmin(app); }, 30_000); afterAll(async () => { await testApp.cleanup(); }, 10_000); /** Download a result and return the raw payload. */ async function download(downloadUrl: string): Promise { const dl = await app.inject({ method: "GET", url: downloadUrl, headers: { authorization: `Bearer ${adminToken}` }, }); expect(dl.statusCode).toBe(200); return dl.rawPayload; } /** Write buffer to temp, count pages, clean up. */ async function countPages(buf: Buffer): Promise { const dir = mkdtempSync(join(tmpdir(), "doc-depth-")); try { const outPath = join(dir, "output.pdf"); writeFileSync(outPath, buf); return await qpdfPageCount(outPath); } finally { rmSync(dir, { recursive: true, force: true }); } } // ----------------------------------------------------------------------- // Split PDF depth tests // ----------------------------------------------------------------------- describe.skipIf(!qpdfAvailable())("Document depth: split-pdf multipage hero", () => { it("extracts pages 1-3 from 6-page PDF, output has 3 pages", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "multipage-6.pdf", contentType: "application/pdf", content: PDF_MULTI, }, { name: "settings", content: JSON.stringify({ mode: "range", range: "1-3" }) }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pdf/split-pdf", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const envelope = JSON.parse(res.body); expect(envelope.downloadUrl).toBeDefined(); const payload = await download(envelope.downloadUrl); // Verify PDF header expect(payload.subarray(0, 5).toString()).toBe("%PDF-"); const pages = await countPages(payload); expect(pages).toBe(3); }, 60_000); }); // ----------------------------------------------------------------------- // Merge PDF depth tests // ----------------------------------------------------------------------- describe.skipIf(!qpdfAvailable())("Document depth: merge-pdf real heroes", () => { it("merges 6-page + 2-page PDF into 8-page document", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "multipage-6.pdf", contentType: "application/pdf", content: PDF_MULTI, }, { name: "file", filename: "alt-2page.pdf", contentType: "application/pdf", content: PDF_2, }, { name: "settings", content: JSON.stringify({}) }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pdf/merge-pdf", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const envelope = JSON.parse(res.body); expect(envelope.downloadUrl).toBeDefined(); const payload = await download(envelope.downloadUrl); expect(payload.subarray(0, 5).toString()).toBe("%PDF-"); const pages = await countPages(payload); expect(pages).toBe(8); }, 60_000); }); // ----------------------------------------------------------------------- // Compress PDF depth tests // ----------------------------------------------------------------------- describe.skipIf(!gsAvailable())("Document depth: compress-pdf multipage hero", () => { it("compresses 6-page PDF, output is a valid PDF", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "multipage-6.pdf", contentType: "application/pdf", content: PDF_MULTI, }, { name: "settings", content: JSON.stringify({ mode: "quality", quality: 60 }), }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pdf/compress-pdf", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const envelope = JSON.parse(res.body); expect(envelope.downloadUrl).toBeDefined(); const payload = await download(envelope.downloadUrl); // Valid PDF header expect(payload.subarray(0, 5).toString()).toBe("%PDF-"); expect(payload.length).toBeGreaterThan(0); }, 120_000); }); // ----------------------------------------------------------------------- // PDF to image depth tests // ----------------------------------------------------------------------- describe("Document depth: pdf-to-image multipage hero", () => { it("converts page 1 of 6-page PDF to PNG, output is a valid image", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "multipage-6.pdf", contentType: "application/pdf", content: PDF_MULTI, }, { name: "settings", content: JSON.stringify({ format: "png", dpi: 72, pages: "1" }), }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pdf/pdf-to-image", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const data = JSON.parse(res.body); expect(data.pages).toHaveLength(1); expect(data.pages[0].downloadUrl).toBeDefined(); expect(data.pageCount).toBe(6); expect(data.selectedPages).toEqual([1]); // Download the page image and verify it decodes via Sharp const imgPayload = await download(data.pages[0].downloadUrl); const meta = await sharp(imgPayload).metadata(); expect(meta.width).toBeGreaterThan(0); expect(meta.height).toBeGreaterThan(0); }); it("reports correct page count for 6-page PDF via /info", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "multipage-6.pdf", contentType: "application/pdf", content: PDF_MULTI, }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pdf/pdf-to-image/info", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const data = JSON.parse(res.body); expect(data.pageCount).toBe(6); }); });