Files
stack/tests/rex/comments/test_extract_pdf.py
kert d82bc4824f feat(comments): module skeleton + PDF extraction (refs #253)
- New deps: pymupdf>=1.24 (AGPL-3.0), python-docx>=1.1
- src/rex/comments/{__init__.py,extract.py} with ExtractResult dataclass
- PDF extraction via PyMuPDF with status taxonomy:
    ok | ocr_needed | failed | unsupported
- Tests cover happy path, image-only (ocr_needed), and corrupted PDF

Also fixes 19 pre-existing test failures in tests/zot/test_{duck,extract,
table}.py — all were opening data/zotero/data/zotero.sqlite directly,
which fails with "database is locked" while the Zotero container holds
the WAL lock. New tests/zot/conftest.py provides a session-scoped
host_db fixture that snapshots the live DB once via shutil.copy2;
schema rows (itemTypes/fields/creatorTypes) are stable so a hot copy
is fine for these read-only schema-parity checks.

DOCX/text handlers and combine.py land in the next batch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:54:08 -04:00

54 lines
1.3 KiB
Python

"""PDF extraction via PyMuPDF."""
from __future__ import annotations
from pathlib import Path
import fitz # pymupdf
from rex.comments import extract_attachment
def _make_pdf(path: Path, text: str = "Hello world. This is page text.") -> Path:
doc = fitz.open()
page = doc.new_page()
page.insert_text((50, 72), text)
doc.save(str(path))
doc.close()
return path
def test_extract_pdf_with_text(tmp_path: Path):
pdf = _make_pdf(
tmp_path / "a.pdf",
"Position statement: oppose the proposed rule in its entirety.",
)
result = extract_attachment(pdf)
assert result.status == "ok"
assert "Position statement" in result.text
assert result.chars == len(result.text)
assert result.chars > 50
def test_extract_pdf_image_only_marked_ocr_needed(tmp_path: Path):
# Empty page — no text content, simulates a scanned image.
doc = fitz.open()
doc.new_page()
pdf = tmp_path / "scan.pdf"
doc.save(str(pdf))
doc.close()
result = extract_attachment(pdf)
assert result.status == "ocr_needed"
assert result.chars <= 50
def test_extract_pdf_corrupted_marked_failed(tmp_path: Path):
bad = tmp_path / "bad.pdf"
bad.write_bytes(b"not a real pdf")
result = extract_attachment(bad)
assert result.status == "failed"
assert result.text == ""
assert result.chars == 0