fix(pdf): stop page tools failing on short and encrypted PDFs (#594)

Empty the hardcoded page-range default in remove/split/extract PDF tools (remove-pages defaulted to "2,4-6", out of range for any PDF under 6 pages) and disable submit until a range is entered. Reject password-protected PDFs up front for PDF-only tools with guidance to unlock first, instead of failing cryptically in the qpdf worker. Adds integration + e2e coverage.
This commit is contained in:
SnapOtter
2026-07-21 06:14:43 +00:00
committed by GitHub
parent 4ba7503f15
commit 73df107758
11 changed files with 173 additions and 9 deletions
@@ -13,6 +13,7 @@ import {
const PDF = readFixture(fixtures.document.pdf3);
const PDF_PATH = fixtures.document.pdf3;
const ENCRYPTED_PDF = readFixture(fixtures.document.encrypted);
let testApp: TestApp;
let adminToken: string;
@@ -107,6 +108,30 @@ describe.skipIf(!qpdfAvailable())("remove-pages (requires qpdf)", () => {
expect(body.details || body.error || body.message).toMatch(/out of range/i);
}, 60_000);
it("rejects a password-protected PDF up front with a clear message", async () => {
// A page tool cannot operate on an encrypted PDF without the password.
// The factory must reject it at input validation (400) with guidance to
// unlock first, instead of letting qpdf fail cryptically in the worker.
const { body, contentType } = createMultipartPayload([
{
name: "file",
filename: "encrypted.pdf",
contentType: "application/pdf",
content: ENCRYPTED_PDF,
},
{ name: "settings", content: JSON.stringify({ pages: "1" }) },
]);
const res = await testApp.app.inject({
method: "POST",
url: "/api/v1/tools/pdf/remove-pages",
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
body,
});
expect(res.statusCode).toBe(400);
const parsed = JSON.parse(res.body);
expect(parsed.error).toMatch(/password-protected|unlock/i);
}, 60_000);
it("removes page 1 from a 96-page pdf (large keepSpec path)", async () => {
// The keep-spec for 95 pages as a raw comma list would be ~280 chars,
// exceeding the 200-char assertValidRange cap. compressPageRuns compresses