#!/usr/bin/env python3 """Deterministic regression for Phase 08-01: Reliable PDF download (R16). Proves: * src.pdf_writer.docx_to_pdf renders a non-empty PDF for a real .docx (reportlab fallback on Linux/HF; docx2pdf on Windows+Word) -> the API can always create a .pdf sidecar so pdf_b64 is populated on the DOCX path. * api_server.py creates the sidecar on BOTH DOCX paths (generate + repair) and surfaces a pdf_error reason ('engine_missing' / 'latex_error') on the LaTeX path while still returning tex_b64. * Dockerfile installs Tectonic AND has a hard `tectonic --version` build gate. No network, no MS Word dependency. Runs from the repo root in < 15 seconds. Exit code: 0 when every assertion passes, 1 otherwise. """ import os import sys import tempfile REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if REPO_ROOT not in sys.path: sys.path.insert(0, REPO_ROOT) API_PATH = os.path.join(REPO_ROOT, "api_server.py") DOCKERFILE = os.path.join(REPO_ROOT, "Dockerfile") _failures = [] def _read(path): with open(path, encoding="utf-8") as fh: return fh.read() def check(condition, message): if condition: print(f" [PASS] {message}") else: print(f" [FAIL] {message}") _failures.append(message) def test_reportlab_sidecar(): print("[1] docx_to_pdf renders a non-empty PDF (reportlab fallback off-Windows)") from docx import Document from src.pdf_writer import docx_to_pdf tmp = tempfile.mkdtemp(prefix="pdf_verify_") docx_path = os.path.join(tmp, "sample.docx") try: doc = Document() doc.add_paragraph("Jane Candidate") doc.add_paragraph("PROFESSIONAL EXPERIENCE") doc.add_paragraph("Led product roadmap and discovery for a B2B SaaS platform.") doc.add_paragraph("Drove A/B testing and stakeholder management across teams.") doc.save(docx_path) pdf_path = docx_to_pdf(docx_path) check(bool(pdf_path) and os.path.exists(pdf_path), "docx_to_pdf returned a PDF path that exists") size = os.path.getsize(pdf_path) if pdf_path and os.path.exists(pdf_path) else 0 check(size > 0, f"rendered PDF is non-empty ({size} bytes)") finally: import shutil shutil.rmtree(tmp, ignore_errors=True) def test_api_wiring(): print("[2] api_server.py creates PDF sidecars + surfaces latex pdf_error") src = _read(API_PATH) check(src.count("docx_to_pdf") >= 2, "docx_to_pdf called on both DOCX paths (generate + repair)") check("pdf_error" in src and src.count("pdf_error") >= 4, "pdf_error added to both LaTeX payloads") check("engine_missing" in src and "latex_error" in src, "pdf_error distinguishes engine_missing vs latex_error") check(src.count("tex_b64") >= 2, "tex_b64 still returned on the LaTeX path (.tex always offered)") def test_dockerfile(): print("[3] Dockerfile installs Tectonic with a hard build gate") df = _read(DOCKERFILE) # Tectonic is now pinned to a musl-static GitHub release (the rolling # drop-sh build crashed at compile time with a glibc "free(): invalid # pointer"). Accept either install method so the check is forward-robust. has_tectonic_install = ( "tectonic-typesetting/tectonic/releases" in df or "drop-sh.fullyjustified.net" in df ) check(has_tectonic_install, "Tectonic install layer present") check("musl" in df, "uses the musl-static build (avoids the glibc crash)") check("tectonic --version" in df, "tectonic --version build assertion present") def main(): print("=" * 64) print("Phase 08-01 verification: Reliable PDF download (R16)") print("=" * 64) test_reportlab_sidecar() test_api_wiring() test_dockerfile() print("-" * 64) if _failures: print(f"FAIL - {len(_failures)} check(s) failed:") for f in _failures: print(f" - {f}") return 1 print("PASS - PDF download wiring verified (sidecar + latex reason + docker gate)") return 0 if __name__ == "__main__": sys.exit(main())