110 lines
3.3 KiB
Python
110 lines
3.3 KiB
Python
"""pdf-toolkit skill — load, eligibility, and merge→split→extract round-trip."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from opensquilla.skills.eligibility import EligibilityContext, check_eligibility
|
|
from opensquilla.skills.loader import SkillLoader
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
BUNDLED = ROOT / "src" / "opensquilla" / "skills" / "bundled"
|
|
SCRIPTS = BUNDLED / "pdf-toolkit" / "scripts"
|
|
|
|
|
|
def _spec() -> object:
|
|
return SkillLoader(bundled_dir=BUNDLED).get_by_name("pdf-toolkit")
|
|
|
|
|
|
def test_skill_loads() -> None:
|
|
spec = _spec()
|
|
assert spec is not None
|
|
assert spec.name == "pdf-toolkit"
|
|
description = spec.description.lower()
|
|
assert "nano-pdf" in description, (
|
|
"description must explicitly distinguish from sibling nano-pdf skill"
|
|
)
|
|
|
|
|
|
def test_eligibility_with_python(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(
|
|
"opensquilla.skills.eligibility.shutil.which",
|
|
lambda name: "/usr/bin/python3" if name in {"python", "python3"} else None,
|
|
)
|
|
spec = _spec()
|
|
assert spec is not None
|
|
assert check_eligibility(spec, EligibilityContext.auto())
|
|
|
|
|
|
def _make_one_page_pdf(path: Path, label: str) -> None:
|
|
from reportlab.lib.pagesizes import LETTER
|
|
from reportlab.pdfgen import canvas
|
|
|
|
c = canvas.Canvas(str(path), pagesize=LETTER)
|
|
c.setFont("Helvetica", 14)
|
|
c.drawString(72, 720, label)
|
|
c.showPage()
|
|
c.save()
|
|
|
|
|
|
def test_merge_split_extract_round_trip(tmp_path: Path) -> None:
|
|
sys.path.insert(0, str(SCRIPTS))
|
|
try:
|
|
import extract # type: ignore[import-not-found]
|
|
import merge # type: ignore[import-not-found]
|
|
import split # type: ignore[import-not-found]
|
|
finally:
|
|
sys.path.pop(0)
|
|
|
|
a = tmp_path / "a.pdf"
|
|
b = tmp_path / "b.pdf"
|
|
_make_one_page_pdf(a, "ALPHA")
|
|
_make_one_page_pdf(b, "BRAVO")
|
|
|
|
combined = tmp_path / "combined.pdf"
|
|
written = merge.merge([{"file": str(a)}, {"file": str(b)}], combined)
|
|
assert written == 2
|
|
assert combined.exists()
|
|
|
|
out_dir = tmp_path / "split_out"
|
|
parts = split.split(combined, "1,2", out_dir)
|
|
assert len(parts) == 2
|
|
|
|
payload = extract.extract(combined, tables_strategy=None)
|
|
assert payload["pages"] == 2
|
|
page_texts = [item["content"] for item in payload["text"]]
|
|
full_text = "\n".join(page_texts)
|
|
assert "ALPHA" in full_text
|
|
assert "BRAVO" in full_text
|
|
|
|
|
|
def test_split_range_parsing() -> None:
|
|
sys.path.insert(0, str(SCRIPTS))
|
|
try:
|
|
import split # type: ignore[import-not-found]
|
|
finally:
|
|
sys.path.pop(0)
|
|
|
|
assert split.split_ranges("1-3") == [[1, 2, 3]]
|
|
assert split.split_ranges("1,3,5") == [[1], [3], [5]]
|
|
assert split.split_ranges("1-2,4-5") == [[1, 2], [4, 5]]
|
|
# Reverse range gets normalized.
|
|
assert split.split_ranges("5-3") == [[3, 4, 5]]
|
|
|
|
|
|
def test_merge_range_parsing() -> None:
|
|
sys.path.insert(0, str(SCRIPTS))
|
|
try:
|
|
import merge # type: ignore[import-not-found]
|
|
finally:
|
|
sys.path.pop(0)
|
|
|
|
assert merge.parse_ranges(None, 4) == [1, 2, 3, 4]
|
|
assert merge.parse_ranges("1,3", 4) == [1, 3]
|
|
assert merge.parse_ranges("1-3", 5) == [1, 2, 3]
|
|
# Out-of-range pages are filtered.
|
|
assert merge.parse_ranges("1,99", 4) == [1]
|