Files
sciagent/be0/tools/e2e_application_form_pdf_export.py
T
Thinh Lam 688fac73e9
CI/CD / backend (push) Failing after 2m8s
CI/CD / frontend (push) Failing after 1m40s
CI/CD / deploy (push) Has been skipped
sciagent code + Gitea Actions CI/CD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:38:30 +07:00

141 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""
Smoke / E2E test for application-form PDF export (docxtpl + LibreOffice).
Direct (no HTTP): exercises merge + conversion on this machine / container.
cd be0 && python tools/e2e_application_form_pdf_export.py --direct --out /tmp/e2e-mau.pdf
HTTP: needs FastAPI listening (--url is the API origin, no /api prefix in path).
python tools/e2e_application_form_pdf_export.py --http http://127.0.0.1:4402 --out /tmp/e2e-mau.pdf
Pure curl + jq (no Python on host; official JSON must be wrapped):
cd /path/to/repo && jq -n --slurpfile o fe0/public/assets/bieu_mau_sang_kien_template.json \
'{officialBieuMau: $o[0]}' | curl -sfS -X POST http://127.0.0.1:4402/api/v1/docx/preview-application-form-pdf \
-H 'Content-Type: application/json' -o /tmp/e2e-mau.pdf && file /tmp/e2e-mau.pdf
If the API returns HTTP 501, rebuild/run be0 with LibreOffice (see be0/Dockerfile: libreoffice-writer-nogui).
Docker (stack up):
docker compose exec be0 python tools/e2e_application_form_pdf_export.py --direct --out /tmp/e2e-mau.pdf
Refresh bundled sample after template changes:
cp ../fe0/public/assets/bieu_mau_sang_kien_template.json tools/e2e_sample_official_bieu_mau.json
Exit 0 on success; non-zero on failure.
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any, Dict
BE0_ROOT = Path(__file__).resolve().parent.parent
if str(BE0_ROOT) not in sys.path:
sys.path.insert(0, str(BE0_ROOT))
def _repo_root() -> Path:
return BE0_ROOT.parent
def load_sample_official() -> Dict[str, Any]:
"""officialBieuMau-shaped JSON (same as Review « Xem lại »)."""
candidates = [
Path(__file__).resolve().parent / "e2e_sample_official_bieu_mau.json",
_repo_root() / "fe0/public/assets/bieu_mau_sang_kien_template.json",
]
for p in candidates:
if p.is_file():
with open(p, encoding="utf-8") as f:
data = json.load(f)
break
else:
raise FileNotFoundError(
"No sample official JSON found. Expected tools/e2e_sample_official_bieu_mau.json "
"or fe0/public/assets/bieu_mau_sang_kien_template.json next to be0/."
)
# Visible markers in generated PDF/DOCX
if isinstance(data.get("TRANG BÌA"), dict):
data["TRANG BÌA"]["Tên sáng kiến (Tiếng Việt)"] = "E2E PDF export — tên sáng kiến kiểm thử"
data["TRANG BÌA"]["Năm"] = "2026"
return data
def run_direct(out_path: Path | None) -> bytes:
from src.be01.docx_to_pdf import convert_docx_bytes_to_pdf
from src.be01.fill_application_form import fill_application_form_docx
from src.be01.official_to_data_blank import official_to_data_blank
official = load_sample_official()
ctx = official_to_data_blank(official)
docx = fill_application_form_docx(ctx)
pdf = convert_docx_bytes_to_pdf(docx)
if not pdf.startswith(b"%PDF"):
raise RuntimeError("Output is not a PDF (missing %PDF header).")
if out_path:
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_bytes(pdf)
print(f"Wrote {len(pdf)} bytes → {out_path}")
else:
print(f"OK: generated PDF, {len(pdf)} bytes (no --out)")
return pdf
def run_http(base_url: str, out_path: Path | None) -> bytes:
import urllib.error
import urllib.request
official = load_sample_official()
url = base_url.rstrip("/") + "/api/v1/docx/preview-application-form-pdf"
body = json.dumps({"officialBieuMau": official}).encode("utf-8")
req = urllib.request.Request(
url,
data=body,
method="POST",
headers={"Content-Type": "application/json", "Accept": "application/pdf"},
)
try:
with urllib.request.urlopen(req, timeout=180) as resp:
raw = resp.read()
except urllib.error.HTTPError as e:
detail = e.read().decode("utf-8", errors="replace")
raise SystemExit(f"HTTP {e.code}: {detail}") from e
if not raw.startswith(b"%PDF"):
raise SystemExit(f"Expected PDF, got {len(raw)} bytes, head={raw[:64]!r}")
if out_path:
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_bytes(raw)
print(f"Wrote {len(raw)} bytes → {out_path}")
else:
print(f"OK: HTTP PDF, {len(raw)} bytes")
return raw
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
g = ap.add_mutually_exclusive_group(required=True)
g.add_argument("--direct", action="store_true", help="Run docxtpl + LibreOffice in-process")
g.add_argument("--http", metavar="BASE_URL", help="POST to FastAPI (e.g. http://127.0.0.1:4402)")
ap.add_argument("--out", type=Path, metavar="FILE.pdf", help="Write PDF to this path")
args = ap.parse_args()
try:
if args.direct:
run_direct(args.out)
else:
run_http(args.http, args.out)
except FileNotFoundError as e:
print(f"FAIL: {e}", file=sys.stderr)
sys.exit(2)
except Exception as e:
print(f"FAIL: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()