Core operations: - Compress PDF (3 presets: screen, ebook, printer, prepress) - PDF/A detection and conversion to regular PDF - Compress while preserving PDF/A structure - Merge multiple PDFs - Split/extract pages by range - Rotate pages - Extract text and images Interface: - Streamlit web GUI (app.py) - FastAPI REST API with auto OpenAPI docs (api.py) Docs: - README.md, ARCHITECTURE.md, API.md - Unit tests (15/15 passing)
143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
"""
|
|
Testes unitários do core do pdf-toolkit.
|
|
"""
|
|
|
|
import io
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from PIL import Image
|
|
|
|
from core.validators import validate_pdf, is_pdf_stream, is_pdf_file
|
|
from core.engine import PDFEngine
|
|
|
|
|
|
# ── Helpers ─────────────────────────────────────────────────────────
|
|
|
|
def make_dummy_pdf() -> bytes:
|
|
"""Gera um PDF mínimo válido para testes."""
|
|
return (
|
|
b"%PDF-1.4\n"
|
|
b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"
|
|
b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n"
|
|
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]\n"
|
|
b" /Resources << /Font << /F1 4 0 R >> >>\n"
|
|
b" /Contents 5 0 R\n>>\nendobj\n"
|
|
b"4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n"
|
|
b"5 0 obj\n<< /Length 44 >>\nstream\nBT /F1 12 Tf 100 700 Td (Hello) Tj ET\nendstream\nendobj\n"
|
|
b"xref\n0 6\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \n0000000115 00000 n \n0000000266 00000 n \n0000000340 00000 n \n"
|
|
b"trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n462\n%%EOF\n"
|
|
)
|
|
|
|
|
|
engine = PDFEngine()
|
|
|
|
|
|
# ── Tests ───────────────────────────────────────────────────────────
|
|
|
|
class TestCompress:
|
|
def test_compress_ebook(self):
|
|
pdf = make_dummy_pdf()
|
|
result = engine.compress(pdf, preset="ebook")
|
|
assert isinstance(result, bytes)
|
|
assert result[:4] == b"%PDF"
|
|
# PDF mínimo não tem imagens, então tamanho fica similar
|
|
# mas a estrutura deve ser válida
|
|
info = engine.inspect(result)
|
|
assert info["valid"] is True
|
|
|
|
def test_compress_screen(self):
|
|
pdf = make_dummy_pdf()
|
|
result = engine.compress(pdf, preset="screen")
|
|
assert isinstance(result, bytes)
|
|
assert result[:4] == b"%PDF"
|
|
|
|
def test_compress_invalid_preset(self):
|
|
pdf = make_dummy_pdf()
|
|
# Preset inválido usa o padrão (ebook) sem levantar erro
|
|
# O comportamento é fallback seguro
|
|
result = engine.compress(pdf, preset="invalid")
|
|
assert isinstance(result, bytes)
|
|
assert result[:4] == b"%PDF"
|
|
|
|
|
|
class TestInspect:
|
|
def test_inspect_valid_pdf(self):
|
|
pdf = make_dummy_pdf()
|
|
info = engine.inspect(pdf, "test.pdf")
|
|
assert info["valid"] is True
|
|
assert info["pages"] == 1
|
|
assert info["file_size_mb"] >= 0
|
|
assert info["filename"] == "test.pdf"
|
|
|
|
def test_inspect_invalid_data(self):
|
|
info = engine.inspect(b"not a pdf", "fake.pdf")
|
|
assert info["valid"] is False
|
|
assert info["error"] != ""
|
|
|
|
|
|
class TestPdfa:
|
|
def test_detect_pdfa_returns_dict(self):
|
|
pdf = make_dummy_pdf()
|
|
result = engine.detect_pdfa(pdf)
|
|
assert isinstance(result, dict)
|
|
assert "is_pdfa" in result
|
|
assert "level" in result
|
|
assert "method" in result
|
|
|
|
def test_pdfa_to_regular_noop_on_common(self):
|
|
pdf = make_dummy_pdf()
|
|
result = engine.pdfa_to_regular(pdf)
|
|
assert isinstance(result, bytes)
|
|
assert result[:4] == b"%PDF"
|
|
|
|
def test_compress_pdfa_preserves_structure(self):
|
|
pdf = make_dummy_pdf()
|
|
result = engine.compress_pdfa(pdf, quality=50)
|
|
assert isinstance(result, bytes)
|
|
assert result[:4] == b"%PDF"
|
|
|
|
|
|
class TestOperations:
|
|
def test_merge_single(self):
|
|
pdf = make_dummy_pdf()
|
|
result = engine.merge([(pdf, "a.pdf")])
|
|
assert result[:4] == b"%PDF"
|
|
info = engine.inspect(result)
|
|
assert info["pages"] == 1
|
|
|
|
def test_merge_multiple(self):
|
|
pdf = make_dummy_pdf()
|
|
result = engine.merge([(pdf, "a.pdf"), (pdf, "b.pdf")])
|
|
info = engine.inspect(result)
|
|
assert info["pages"] == 2
|
|
|
|
def test_split(self):
|
|
pdf = make_dummy_pdf()
|
|
results = engine.split(pdf, [(0, 0)], "teste")
|
|
assert len(results) == 1
|
|
nome, dados = results[0]
|
|
assert "teste" in nome
|
|
assert dados[:4] == b"%PDF"
|
|
|
|
def test_extract_pages(self):
|
|
pdf = make_dummy_pdf()
|
|
result = engine.extract(pdf, [0])
|
|
assert result[:4] == b"%PDF"
|
|
info = engine.inspect(result)
|
|
assert info["pages"] == 1
|
|
|
|
def test_rotate(self):
|
|
pdf = make_dummy_pdf()
|
|
result = engine.rotate(pdf, [(0, 90)])
|
|
assert result[:4] == b"%PDF"
|
|
|
|
def test_extract_text(self):
|
|
pdf = make_dummy_pdf()
|
|
text = engine.text(pdf)
|
|
assert "Hello" in text
|
|
|
|
def test_extract_images_empty(self):
|
|
pdf = make_dummy_pdf()
|
|
imgs = engine.images(pdf)
|
|
assert imgs == [] |