feat: initial release — pdf-toolkit v1.0.0
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)
This commit is contained in:
143
tests/test_core.py
Normal file
143
tests/test_core.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
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 == []
|
||||
Reference in New Issue
Block a user