"""שומר INV-EX4 — מקור-תבנית-יחיד לכל מפיקי-מסמכי-ההחלטה. כל מפיק של מסמך-Word הקשור-להחלטה חייב להחיל עיצוב דרך הסגנונות שמוגדרים בטמפלט היחיד (skills/docx/decision_template.docx), ולא לקבוע פונט/גודל ידנית (``rFonts`` / ``sz`` / ``szCs``) — אלה נגזרים מ-styles.xml של הטמפלט. הטסט גורף את מודולי-המפיקים ונכשל אם נבנה אלמנט ``w:rFonts`` / ``w:sz`` / ``w:szCs`` בקוד, אלא אם השורה נושאת את הסמן המפורש ``# INV-EX4-ok`` (חריג מתועד — למשל חיזוק גופן-התבנית עצמו David כעקיפת באג-RTL של Word). ספ: docs/spec/06-export.md §INV-EX4. """ from __future__ import annotations import re from pathlib import Path import pytest from legal_mcp.services import ( analysis_docx_exporter, docx_exporter, docx_reviser, docx_retrofit, ) # Every module that builds/edits a decision-related Word document. _PRODUCER_MODULES = [ docx_exporter, analysis_docx_exporter, docx_reviser, docx_retrofit, ] # Construction (not lookup) of a hand-set font/size element: # OxmlElement("w:rFonts") (python-docx producers) # etree.SubElement(rPr, _w("sz")) (raw-lxml producer: docx_reviser) _HANDSET_RE = re.compile( r'OxmlElement\(\s*["\']w:(?:rFonts|sz|szCs)["\']' r'|SubElement\([^)]*_w\(\s*["\'](?:rFonts|sz|szCs)["\']' ) # Producers that build a document FROM SCRATCH must load the single template. # (docx_reviser/docx_retrofit operate on an already-templated existing doc.) _FROM_SCRATCH_MODULES = [docx_exporter, analysis_docx_exporter] def _module_path(module) -> Path: return Path(module.__file__) def test_no_handset_font_or_size_in_producers() -> None: """INV-EX4: no hand-set rFonts/sz/szCs in any producer (only via the template's named styles), unless explicitly marked ``# INV-EX4-ok``.""" violations: list[str] = [] for module in _PRODUCER_MODULES: path = _module_path(module) for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): if _HANDSET_RE.search(line) and "INV-EX4-ok" not in line: violations.append(f"{path.name}:{lineno}: {line.strip()}") assert not violations, ( "INV-EX4 violation — hand-set font/size outside the template styles " "(use a named template style, or mark with # INV-EX4-ok):\n " + "\n ".join(violations) ) @pytest.mark.parametrize("module", _FROM_SCRATCH_MODULES) def test_from_scratch_producers_load_the_single_template(module) -> None: """INV-EX4: from-scratch producers reference the one template file.""" source = _module_path(module).read_text(encoding="utf-8") assert "decision_template.docx" in source, ( f"{_module_path(module).name} must load the single template " "skills/docx/decision_template.docx (INV-EX4)." )