docling-studio/e2e/generate-test-data.py
Pier-Jean Malandrino cfc5bb5c35 feat: add E2E API tests with Karate V2
Set up a full E2E test suite (39 scenarios) using Karate against
the real API stack. Hybrid architecture: domain-based features +
cross-domain workflows, with data-driven testing and callable helpers.

Structure:
- e2e/pom.xml: Maven + karate-core 1.5
- 3 helpers (upload, analyze+poll, cleanup)
- 3 JSON schemas (health, document, analysis)
- 12 feature files across health, documents, analyses, workflows
- Tags: @smoke (2), @regression (35), @e2e (2)
- generate-test-data.py: fpdf2-based PDF generation (no binaries)

Also adds:
- RATE_LIMIT_RPM env var to make rate limiter configurable (0=disabled)
- CI job e2e with needs: [backend, frontend]
- e2e/ in .dockerignore

Closes #119
2026-04-08 13:47:03 +02:00

78 lines
2.8 KiB
Python

"""Generate deterministic test PDFs for E2E tests.
Uses fpdf2 to create valid PDFs with real text content so Docling
can extract and chunk them. No binary files committed to the repo.
Usage:
python e2e/generate-test-data.py
Dependencies:
pip install fpdf2
"""
from __future__ import annotations
import os
from fpdf import FPDF
OUTPUT_DIR = os.path.join(
os.path.dirname(__file__), "src", "test", "resources", "common", "data", "generated"
)
_PARAGRAPHS = [
"Document processing is a critical step in building retrieval-augmented generation systems.",
"Docling Studio provides tools for analyzing PDF documents and extracting structured content.",
"The conversion pipeline supports OCR, table detection, and formula enrichment features.",
"Chunking splits document content into semantically meaningful segments for vector indexing.",
"Each chunk preserves metadata such as page number, bounding boxes, and heading hierarchy.",
"The hybrid chunker combines hierarchical document structure with token-based splitting.",
"Vector stores like OpenSearch enable fast similarity search over embedded chunk vectors.",
"Quality control requires visual inspection of chunk boundaries and extracted text accuracy.",
"Batch processing of large documents uses page ranges to prevent memory exhaustion.",
"Progress reporting allows users to monitor long-running document conversion tasks.",
]
def _make_pdf(page_count: int, path: str) -> None:
"""Create a valid PDF with N pages containing text paragraphs."""
pdf = FPDF()
pdf.set_auto_page_break(auto=True, margin=25)
for page_num in range(page_count):
pdf.add_page()
pdf.set_font("Helvetica", "B", 16)
pdf.cell(0, 10, f"Page {page_num + 1} of {page_count}", new_x="LMARGIN", new_y="NEXT")
pdf.ln(5)
pdf.set_font("Helvetica", "", 11)
for i in range(5):
para = _PARAGRAPHS[(page_num * 5 + i) % len(_PARAGRAPHS)]
pdf.multi_cell(0, 6, para)
pdf.ln(3)
pdf.output(path)
size_kb = os.path.getsize(path) / 1024
print(f" {os.path.basename(path)}: {page_count} pages, {size_kb:.1f} KB")
def _make_non_pdf(path: str) -> None:
"""Create a non-PDF file for negative testing."""
with open(path, "wb") as f:
f.write(b"This is not a PDF file.\n")
print(f" {os.path.basename(path)}: not-a-pdf")
def main() -> None:
os.makedirs(OUTPUT_DIR, exist_ok=True)
print(f"Generating test data in {OUTPUT_DIR}")
_make_pdf(1, os.path.join(OUTPUT_DIR, "small.pdf"))
_make_pdf(5, os.path.join(OUTPUT_DIR, "medium.pdf"))
_make_pdf(25, os.path.join(OUTPUT_DIR, "large.pdf"))
_make_non_pdf(os.path.join(OUTPUT_DIR, "not-a-pdf.txt"))
print("Done.")
if __name__ == "__main__":
main()