"""Generowanie źródła LaTeX raportu (PRE-24). Najważniejsza część to UCIECZKA znaków specjalnych. Kompilacji tu nie uruchomimy (brak TeX Live w tym środowisku), ale to i tak nie ona jest tu najbardziej krucha: błędna ucieczka nie wywala kompilacji, tylko po cichu psuje treść — najgorszy możliwy wynik, bo PDF powstaje i wygląda poprawnie. """ import pytest from app.latex import build, esc # ─────────────────────────── ucieczka znaków specjalnych ──────────────── def test_percent_is_escaped(): """NAJGROŹNIEJSZY przypadek: `%` bez ucieczki komentuje resztę linii, więc zdanie urywa się w środku, a PDF powstaje normalnie — tylko krótszy.""" out = esc("wzrost o 50% w tym okresie") assert r"50\%" in out assert "w tym okresie" in out def test_backslash_is_escaped_first(): """Odwrotny ukośnik musi iść pierwszy, inaczej zepsulibyśmy ucieczki wstawione później (np. `\\%` zamieniłoby się w coś innego).""" assert esc("a\\b") == r"a\textbackslash{}b" def test_all_specials_are_escaped(): for ch in ("$", "&", "#", "_", "%", "{", "}"): assert ch not in esc(ch).replace("\\" + ch, ""), f"{ch} nie zostało uciec­zone" def test_tilde_and_caret_use_commands(): assert esc("~") == r"\textasciitilde{}" assert esc("^") == r"\textasciicircum{}" def test_polish_and_glyphs_pass_through(): """XeLaTeX bierze unicode wprost — polskich znaków ani glifów nie ruszamy.""" text = "Zażółć gęślą jaźń ♄ ♓ ☉" assert esc(text) == text def test_escape_handles_none_and_numbers(): assert esc(None) == "" assert esc(42) == "42" # ─────────────────────────────── układ dokumentu ───────────────────────── def _report(**over): base = { "person": "Jan Kowalski", "data": {"date": "1984-04-30", "time": "11:20", "tz_offset": 2, "lat": 50.0647, "lon": 19.945, "place": "Kraków", "house_system": "whole_sign", "zodiac": "tropical"}, "natal": {"text": "Interpretacja natalna.\n\nDrugi akapit."}, "predictions": [ {"from_date": "2026-01-01", "to_date": "2026-03-31", "text": "Pierwszy kwartał."}, {"from_date": "2026-07-01", "to_date": "2026-09-30", "text": "Trzeci kwartał."}, ], } base.update(over) return base def test_document_is_complete(): tex = build(_report()) assert tex.startswith(r"\documentclass") assert tex.rstrip().endswith(r"\end{document}") def test_uses_unicode_engine_setup(): """XeLaTeX + fontspec — bez tego polskie znaki i glify nie wyjdą.""" tex = build(_report()) assert r"\usepackage{fontspec}" in tex assert r"\setmainfont" in tex def test_section_order_matches_the_brief(): """Prośba partnerów: imię → dane → rysunek → natalna → predykcje.""" tex = build(_report(), wheel_pdf="wheel.pdf") person = tex.index("Jan Kowalski") data = tex.index("Data urodzenia") wheel = tex.index("includegraphics") natal = tex.index("Interpretacja natalna") preds = tex.index("Predykcje okresowe") assert person < data < wheel < natal < preds def test_person_name_opens_the_document(): tex = build(_report()) body = tex.split(r"\begin{document}")[1] assert body.index("Jan Kowalski") < 80, "imię ma być na samym początku" def test_all_predictions_are_included(): tex = build(_report()) assert "2026-01-01" in tex and "2026-07-01" in tex assert "Pierwszy kwartał" in tex and "Trzeci kwartał" in tex def test_paragraphs_are_preserved(): """Model oddziela akapity pustą linią — mają zostać akapitami, nie zlepkiem.""" tex = build(_report()) assert "Interpretacja natalna." in tex and "Drugi akapit." in tex assert "Interpretacja natalna.\n\nDrugi akapit." in tex def test_hostile_text_cannot_break_the_document(): """Tekst od modelu jest wejściem z ZEWNĄTRZ — nie może wstrzyknąć polecenia ani urwać dokumentu.""" nasty = r"100% \end{document} \input{/etc/passwd} $x_1$ #& {}" tex = build(_report(natal={"text": nasty})) body = tex.split(r"\begin{document}")[1] assert body.count(r"\end{document}") == 1, "tekst urwał dokument" assert r"\input{" not in body def test_missing_parts_are_simply_absent(): """Niekompletny materiał ma dać krótszy raport, nie wyjątek.""" tex = build({"person": "", "data": {}}) assert r"\begin{document}" in tex and r"\end{document}" in tex assert "Raport astrologiczny" in tex # zapas, gdy brak imienia def test_wheel_is_optional(): assert "includegraphics" not in build(_report()) assert "includegraphics" in build(_report(), wheel_pdf="wheel.pdf")