test(qa): cover PDF extraction dispatch (#118860)

This commit is contained in:
Vincent Koc
2026-08-04 07:02:00 +08:00
committed by GitHub
parent a2ccd6ea61
commit 1cdfe9ed7d
2 changed files with 137 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
title: PDF document extraction dispatch
scenario:
id: pdf-document-extraction-dispatch
surface: media
category: media.media-intake-and-access
coverage:
primary:
- media.pdf-document-extraction-dispatch
objective: Verify PDF intake dispatches through the bundled document extractor to real clawpdf and reports disabled extraction explicitly.
successCriteria:
- The bundled document-extract plugin supplies the PDF extractor.
- A parseable inline PDF yields its sentinel text through real clawpdf.
- The document extractor runtime tags the result with extractor pdf.
- Disabling document extraction produces the explicit unavailable error.
docsRefs:
- docs/plugins/reference/document-extract.md
- docs/tools/pdf.md
codeRefs:
- src/media/pdf-extract.ts
- src/media/document-extractors.runtime.ts
- src/plugins/document-extractors.runtime.ts
- extensions/document-extract/document-extractor.ts
- test/e2e/qa-lab/media/pdf-document-extraction-dispatch.product.test.ts
execution:
kind: vitest
path: test/e2e/qa-lab/media/pdf-document-extraction-dispatch.product.test.ts
summary: Run a parseable inline PDF through bundled extractor discovery, real clawpdf extraction, and the disabled-plugin failure path.

View File

@@ -0,0 +1,109 @@
import { describe, expect, it } from "vitest";
import { extractDocumentContent } from "../../../../src/media/document-extractors.runtime.js";
import { extractPdfContent } from "../../../../src/media/pdf-extract.js";
import { resolvePluginDocumentExtractors } from "../../../../src/plugins/document-extractors.runtime.js";
const SENTINEL = "OPENCLAW_PDF_DISPATCH_SENTINEL";
function createInlinePdf(text: string): Buffer {
const escapedText = text.replace(/[\\()]/gu, "\\$&");
const content = `BT
/F1 18 Tf
72 720 Td
(${escapedText}) Tj
ET
`;
const objects = [
"<< /Type /Catalog /Pages 2 0 R >>",
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>",
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
`<< /Length ${Buffer.byteLength(content)} >>
stream
${content}endstream`,
];
let pdf = "%PDF-1.4\n% OpenClaw QA fixture\n";
const offsets: number[] = [];
for (const [index, object] of objects.entries()) {
offsets.push(Buffer.byteLength(pdf));
pdf += `${index + 1} 0 obj
${object}
endobj
`;
}
const xrefOffset = Buffer.byteLength(pdf);
pdf += `xref
0 ${objects.length + 1}
`;
pdf += "0000000000 65535 f \n";
for (const offset of offsets) {
pdf += `${String(offset).padStart(10, "0")} 00000 n \n`;
}
pdf += `trailer
<< /Size ${objects.length + 1} /Root 1 0 R >>
startxref
${xrefOffset}
%%EOF
`;
return Buffer.from(pdf, "ascii");
}
describe("PDF document extraction dispatch", () => {
it("uses the bundled PDF extractor and reports disabled extraction explicitly", async () => {
const buffer = createInlinePdf(SENTINEL);
const bundledPdfExtractor = resolvePluginDocumentExtractors().find(
(extractor) => extractor.id === "pdf",
);
expect(bundledPdfExtractor).toMatchObject({
id: "pdf",
pluginId: "document-extract",
mimeTypes: ["application/pdf"],
});
const extracted = await extractDocumentContent({
buffer,
mimeType: "application/pdf",
maxPages: 1,
maxPixels: 1_000_000,
minTextChars: 1,
});
expect(extracted).toMatchObject({
extractor: "pdf",
text: SENTINEL,
images: [],
});
await expect(
extractPdfContent({
buffer,
maxPages: 1,
maxPixels: 1_000_000,
minTextChars: 1,
}),
).resolves.toEqual({
text: SENTINEL,
images: [],
});
await expect(
extractPdfContent({
buffer,
maxPages: 1,
maxPixels: 1_000_000,
minTextChars: 1,
config: {
plugins: {
entries: {
"document-extract": { enabled: false },
},
},
},
}),
).rejects.toThrow(
"PDF extraction disabled or unavailable: enable the document-extract plugin to process application/pdf files.",
);
});
});