4d1daab35a
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m34s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 18s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 10s
build / build (push) Successful in 26s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m37s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m33s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m27s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 17s
Testy / Kontrola składni wszystkich warstw (push) Successful in 10s
Ostatnie trzy pozycje z treści LOG-05, których wcześniej nie było: - whole_sign_aries — znaki jako domy, ale dom I ZAWSZE na 0° Barana, niezależnie od Ascendentu (tradycja indyjska i część szkół hellenistycznych), - equal_mc — równe domy zakotwiczone na MC: dom X zaczyna się dokładnie na południku, a nie ma go gdzieś w środku, - obrót kosmogramu: Ascendent albo 0° Barana po lewej stronie koła. Zmienia WYŁĄCZNIE rysunek, żadna liczba nie jest przeliczana. Wariant „od Barana" unieruchamia koło względem zodiaku, więc dwa horoskopy da się porównać na oko. Oba nowe systemy zgodne z wyrocznią co do zera od pierwszego uruchomienia. Razem 13 systemów: 2 833 424 porównania w przemiale, zero przekroczeń. PRZY OKAZJI — DWA BŁĘDY, KTÓRE SAM WPROWADZIŁEM I KTÓRYCH TESTY NIE WIDZIAŁY: - linia zbierająca ostrzeżenia o fallbacku trafiła do handlera strony głównej zamiast do compile_pdf: odwoływała się do nieistniejącej zmiennej, czyli 500 na stronie głównej, a do PDF-a ostrzeżenia nie docierały wcale, - compile_build używał parametru formularza, którego nie miał w sygnaturze. Oba przeszły przez komplet zielonych testów, bo żaden nie wywoływał POST-a — testy prezentacji sprawdzały teksty w szablonach i w main.py. Doszły więc testy uderzające w prawdziwe trasy (POST / i POST /compile ze stubowaną logiką), które łapią tę klasę błędu. Z tego samego powodu przepisane dwa testy PDF-a: greppowały z main.py dokładny kształt wywołania render(chart, theme="print") i pękały przy dopisaniu argumentu, mimo poprawnego zachowania. Teraz wołają trasę i sprawdzają, że KAŻDY z czterech rysunków dostaje motyw druku. LOG-05 i PRE-05 → Zrobione w docs/astrololo_wymagania.xlsx. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
154 lines
6.3 KiB
Python
154 lines
6.3 KiB
Python
"""Łącze do usługi render i wariant kosmogramu do druku (PRE-24).
|
|
|
|
Przez to łącze leci CAŁY raport: dane urodzeniowe i opisy z baz. Musi być
|
|
szyfrowane tak samo jak pozostałe, WŁASNYM kluczem — i musi obowiązywać ten sam
|
|
niezmiennik co dla klienta logiki: żadnego wyjścia w dół bez tokenu i klucza.
|
|
"""
|
|
import pathlib
|
|
|
|
import pytest
|
|
|
|
APP = pathlib.Path(__file__).resolve().parents[1] / "app"
|
|
CLIENT = APP / "clients" / "render_client.py"
|
|
SRC = CLIENT.read_text(encoding="utf-8")
|
|
WHEEL = (APP / "chartwheel.py").read_text(encoding="utf-8")
|
|
MAIN = (APP / "main.py").read_text(encoding="utf-8")
|
|
COMPILE_JS = (APP / "static" / "compile.js").read_text(encoding="utf-8")
|
|
|
|
|
|
# ─────────────────────────── szyfrowanie łącza ───────────────────────────
|
|
|
|
def test_uses_its_own_third_key():
|
|
"""Osobny klucz na parę rozmówców. Przejęcie klucza renderu nie może otwierać
|
|
łącza do logiki ani do danych."""
|
|
from app import link_crypto
|
|
|
|
assert link_crypto.ENV_PRESENTATION_RENDER == "LINK_KEY_PRESENTATION_RENDER"
|
|
assert link_crypto.ENV_PRESENTATION_RENDER != link_crypto.ENV_PRESENTATION_LOGIC
|
|
assert link_crypto.ENV_PRESENTATION_RENDER != link_crypto.ENV_LOGIC_DATA
|
|
assert "ENV_PRESENTATION_RENDER" in SRC
|
|
|
|
|
|
def test_report_goes_through_the_encrypted_link():
|
|
assert "link_crypto.call(" in SRC
|
|
assert "link=_link()" in SRC
|
|
|
|
|
|
def test_every_outbound_call_carries_token_and_key():
|
|
"""Ten sam niezmiennik co przy kliencie logiki — tam brak nagłówka wyszedł
|
|
dopiero na produkcji (401)."""
|
|
import ast
|
|
|
|
tree = ast.parse(SRC)
|
|
calls = [n for n in ast.walk(tree)
|
|
if isinstance(n, ast.Call) and isinstance(n.func, ast.Attribute)
|
|
and n.func.attr in ("call", "call_json")]
|
|
assert calls, "nie znaleziono wywołania przez link_crypto"
|
|
for c in calls:
|
|
kwargs = {k.arg for k in c.keywords}
|
|
assert "headers" in kwargs and "link" in kwargs
|
|
|
|
|
|
def test_health_is_the_only_unencrypted_path():
|
|
"""/health musi zostać otwarty dla sond k8s — ale nic poza nim."""
|
|
from app import link_crypto
|
|
|
|
assert "/health" in link_crypto.PUBLIC_PATHS
|
|
raw = [line for line in SRC.splitlines() if "client.get(" in line or "client.post(" in line]
|
|
assert all("/health" in line for line in raw), f"surowe wywołania poza /health: {raw}"
|
|
|
|
|
|
def test_key_is_read_lazily():
|
|
"""Konfiguracja może się zmienić bez restartu procesu (podmiana sekretu)."""
|
|
assert "def _link()" in SRC and "key_from_env" in SRC
|
|
|
|
|
|
# ──────────────────────── kosmogram w wariancie do druku ─────────────────
|
|
|
|
def test_pdf_route_uses_print_theme():
|
|
"""Samodzielny konwerter SVG→PDF nie zna naszego arkusza stylów.
|
|
|
|
Sprawdzamy, że trasa PDF-a w ogóle podaje motyw druku — bez wiązania się
|
|
z dokładnym kształtem wywołania (dochodzą do niego kolejne argumenty).
|
|
Pełny sprawdzian, że KAŻDY rysunek dostaje 'print', jest w test_compile.py."""
|
|
assert 'theme="print"' in MAIN
|
|
|
|
|
|
def test_print_theme_has_no_css_variables():
|
|
"""`var(--…)` w SVG poza stroną nie rozwiąże się — wyszłyby czarne albo
|
|
żadne kreski."""
|
|
from app import chartwheel
|
|
|
|
chart = _sample_chart()
|
|
printed = chartwheel.render(chart, theme="print")
|
|
assert printed and "var(--" not in printed
|
|
|
|
|
|
def test_screen_theme_still_uses_css_variables():
|
|
"""Na stronie koło ma się dostrajać do motywu aplikacji."""
|
|
from app import chartwheel
|
|
|
|
assert "var(--" in chartwheel.render(_sample_chart(), theme="screen")
|
|
|
|
|
|
def test_print_theme_embeds_the_glyph_font():
|
|
"""Font glifów podaje CSS klasy `.glyph` — poza stroną trzeba go wpisać
|
|
W SAM rysunek, inaczej kosmogram w PDF wyszedłby BEZ symboli."""
|
|
from app import chartwheel
|
|
|
|
printed = chartwheel.render(_sample_chart(), theme="print")
|
|
assert 'font-family=' in printed
|
|
assert "DejaVu Sans" in printed # font obecny w obrazie usługi render
|
|
|
|
|
|
def test_screen_theme_leaves_font_to_css():
|
|
from app import chartwheel
|
|
|
|
assert "font-family=" not in chartwheel.render(_sample_chart(), theme="screen")
|
|
|
|
|
|
def test_unknown_theme_falls_back_to_screen():
|
|
from app import chartwheel
|
|
|
|
assert chartwheel.render(_sample_chart(), theme="cokolwiek") == \
|
|
chartwheel.render(_sample_chart(), theme="screen")
|
|
|
|
|
|
# ─────────────────────────── składanie po stronie klienta ────────────────
|
|
|
|
def test_browser_sends_all_three_parts():
|
|
for field in ("person", "natal", "predictions"):
|
|
assert field in COMPILE_JS, f"raport wysyłany bez części: {field}"
|
|
|
|
|
|
def test_pdf_button_exists():
|
|
tpl = (APP / "templates" / "compile.html").read_text(encoding="utf-8")
|
|
assert 'id="pdfBtn"' in tpl
|
|
|
|
|
|
def test_wheel_failure_does_not_block_the_report():
|
|
"""Brak rysunków ma dać raport bez nich, a nie brak raportu — tekst kosztował
|
|
wywołanie modelu, rysunki policzymy zawsze."""
|
|
assert "figures: list[dict] = []" in MAIN
|
|
assert "Brak rysunków nie może zablokować raportu" in MAIN
|
|
|
|
|
|
# ─────────────────────────────────── pomocnicze ──────────────────────────
|
|
|
|
def _sample_chart():
|
|
signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
|
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"]
|
|
glyphs = ["♈", "♉", "♊", "♋", "♌", "♍", "♎", "♏", "♐", "♑", "♒", "♓"]
|
|
asc = 128.9
|
|
asc_sign = int(asc // 30)
|
|
return {
|
|
"angles": {"Asc": {"name": "Asc", "decimal": asc, "sign": signs[asc_sign]},
|
|
"MC": {"name": "MC", "decimal": 5.0},
|
|
"Dsc": {"name": "Dsc", "decimal": (asc + 180) % 360},
|
|
"IC": {"name": "IC", "decimal": 185.0}},
|
|
"cusps": [{"house": i + 1, "sign": signs[(asc_sign + i) % 12],
|
|
"decimal": (asc_sign * 30 + i * 30) % 360} for i in range(12)],
|
|
"sign_glyphs": [{"sign": s, "glyph": g} for s, g in zip(signs, glyphs)],
|
|
"positions": [{"name": "Sun", "decimal": 40.2, "glyph": "☉", "direction": "D"}],
|
|
}
|