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>
157 lines
6.8 KiB
Python
157 lines
6.8 KiB
Python
"""Porównanie systemów domów naraz (PRE-05).
|
|
|
|
JS-a tu nie ma — sprawdzamy strukturalnie: formularz pozwala zaznaczyć kilka
|
|
systemów, klient przekazuje je do logiki, a szablon pokazuje kuspy obok siebie.
|
|
"""
|
|
import pathlib
|
|
|
|
import pytest
|
|
|
|
APP = pathlib.Path(__file__).resolve().parents[1] / "app"
|
|
CHART = (APP / "templates" / "chart.html").read_text(encoding="utf-8")
|
|
COMPILE = (APP / "templates" / "compile.html").read_text(encoding="utf-8")
|
|
OPTS = (APP / "templates" / "_form_options.html").read_text(encoding="utf-8") # wspólne opcje
|
|
TABLES = (APP / "templates" / "_result_tables.html").read_text(encoding="utf-8") # wspólne tabele
|
|
MAIN = (APP / "main.py").read_text(encoding="utf-8")
|
|
|
|
from app.house_systems import HOUSE_SYSTEMS, LIMITED, label as house_label # noqa: E402
|
|
CLIENT = (APP / "clients" / "logic_client.py").read_text(encoding="utf-8")
|
|
|
|
|
|
def test_both_forms_use_shared_option_partial():
|
|
"""Opcje w jednym pliku — oba formularze go dołączają (koniec duplikacji)."""
|
|
assert '{% include "_form_options.html" %}' in CHART
|
|
assert '{% include "_form_options.html" %}' in COMPILE
|
|
|
|
|
|
def _render(name: str, **ctx) -> str:
|
|
"""Renderuje szablon z tymi samymi globalsami, co produkcja.
|
|
|
|
Szablony budują listę systemów PĘTLĄ po katalogu, więc sprawdzanie ich
|
|
surowego tekstu nic już nie mówi — trzeba je wykonać. Przy okazji test
|
|
łapie literówki w Jinja, których grep po źródle nigdy nie wyłapał."""
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
env = Environment(loader=FileSystemLoader(str(APP / "templates")), autoescape=True)
|
|
env.globals.update(HOUSE_SYSTEMS=HOUSE_SYSTEMS, HOUSE_LIMITED=LIMITED,
|
|
house_label=house_label, static=lambda p: p)
|
|
return env.get_template(name).render(**ctx)
|
|
|
|
|
|
def test_form_offers_every_system_the_engine_advertises():
|
|
"""Każdy system z katalogu ma checkbox. Katalog jest jednym źródłem nazw,
|
|
więc dopisanie systemu bez odświeżenia UI zapali się tutaj."""
|
|
html = _render("_form_options.html", form={})
|
|
for value, name in HOUSE_SYSTEMS:
|
|
assert f'name="house_systems" value="{value}"' in html, f"brak checkboxa {value}"
|
|
assert name in html, f"brak nazwy {name}"
|
|
|
|
|
|
def test_form_marks_systems_with_a_domain_limit():
|
|
"""Placidus i Koch nie istnieją za kołem podbiegunowym — UI ma uprzedzać
|
|
ZANIM ktoś policzy horoskop dla Tromsø, nie dopiero w ostrzeżeniu wyniku."""
|
|
html = _render("_form_options.html", form={})
|
|
assert LIMITED, "katalog nie oznacza żadnego systemu jako ograniczonego"
|
|
for value, name in HOUSE_SYSTEMS:
|
|
starred = f"{name} *" in html
|
|
assert starred == (value in LIMITED), f"{value}: gwiazdka={starred}"
|
|
|
|
|
|
def test_handler_accepts_and_passes_house_systems():
|
|
assert "house_systems: list[str] = Form([])" in MAIN
|
|
assert "house_systems=house_systems" in MAIN # przekazane do logiki
|
|
|
|
|
|
def test_client_forwards_house_systems_to_logic():
|
|
assert '"house_systems": house_systems' in CLIENT
|
|
|
|
|
|
def test_comparison_table_renders_only_for_multiple_systems():
|
|
"""Bez dodatkowych systemów nie pokazujemy pustej tabeli porównania."""
|
|
assert "result.house_systems | length > 1" in TABLES
|
|
assert "Porównanie systemów domów" in TABLES
|
|
|
|
|
|
def test_comparison_table_shows_cusp_per_system():
|
|
"""Tabela iteruje po systemach i po 12 domach — kusp w każdej komórce."""
|
|
assert "for hs in result.house_systems" in TABLES
|
|
assert "hs.cusps[i].in_sign" in TABLES
|
|
|
|
|
|
def test_checkbox_state_survives_submit():
|
|
"""Zaznaczone systemy zostają zaznaczone po przeliczeniu."""
|
|
html = _render("_form_options.html", form={"house_systems": ["koch", "campanus"]})
|
|
for value, _ in HOUSE_SYSTEMS:
|
|
marker = f'value="{value}" checked'
|
|
assert (marker in html) == (value in ("koch", "campanus")), value
|
|
|
|
|
|
def test_fallback_notice_is_shown_prominently():
|
|
"""Po fallbacku kosmogram wygląda bezbłędnie — ostrzeżenie jest jedynym
|
|
sygnałem, że podział domów jest z innego systemu."""
|
|
html = _render("_result_tables.html",
|
|
result={"house_warnings": ["UWAGA: system domów nie ma definicji"]})
|
|
assert "house-warning" in html
|
|
assert "UWAGA: system domów nie ma definicji" in html
|
|
|
|
|
|
def test_no_notice_when_nothing_was_substituted():
|
|
html = _render("_result_tables.html", result={"house_warnings": []})
|
|
assert "house-warning" not in html
|
|
|
|
|
|
# ── ścieżka PRZEZ handlery, nie obok nich ───────────────────────────────
|
|
# Dwa błędy przeszły przez komplet testów szablonowych, bo żaden nie wywołał
|
|
# POST-a: odwołanie do nieistniejącej zmiennej w handlerze strony głównej oraz
|
|
# użycie parametru formularza, którego w sygnaturze nie było. Oba dają 500 na
|
|
# żywo i oba łapie dopiero prawdziwe żądanie.
|
|
|
|
def _client_with_stub_logic(monkeypatch, chart):
|
|
import os
|
|
|
|
os.environ.pop("APP_PASSWORD", None)
|
|
from starlette.testclient import TestClient
|
|
|
|
from app.main import app, logic
|
|
|
|
monkeypatch.setattr(logic, "positions", lambda **kw: chart)
|
|
return TestClient(app)
|
|
|
|
|
|
def _minimal_chart():
|
|
cusps = [{"house": i + 1, "sign": "Aries", "in_sign": "0°00'00''",
|
|
"decimal": float(i * 30), "sign_glyph": "♈"} for i in range(12)]
|
|
ang = {k: {"name": k, "sign": "Aries", "in_sign": "0°00'00''", "decimal": 0.0,
|
|
"sign_glyph": "♈"} for k in ("Asc", "MC", "Dsc", "IC")}
|
|
return {"engine": "test", "positions": [], "cusps": cusps, "angles": ang,
|
|
"sign_glyphs": [{"sign": "Aries", "glyph": "♈"}],
|
|
"house_system": "porphyry", "house_system_requested": "placidus",
|
|
"house_warnings": ["UWAGA: system domów „placidus” nie ma definicji"]}
|
|
|
|
|
|
FORM = {"date": "1984-04-30", "time": "11:20", "tz_offset": "2",
|
|
"lat": "50.06", "lon": "19.94", "house_system": "placidus"}
|
|
|
|
|
|
def test_chart_page_survives_a_real_post(monkeypatch):
|
|
c = _client_with_stub_logic(monkeypatch, _minimal_chart())
|
|
r = c.post("/", data=FORM)
|
|
assert r.status_code == 200, r.text[:400]
|
|
assert "nie ma definicji" in r.text, "ostrzeżenie o fallbacku nie dotarło na stronę"
|
|
|
|
|
|
def test_compile_page_survives_a_real_post(monkeypatch):
|
|
c = _client_with_stub_logic(monkeypatch, _minimal_chart())
|
|
r = c.post("/compile", data=FORM)
|
|
assert r.status_code == 200, r.text[:400]
|
|
|
|
|
|
@pytest.mark.parametrize("orientation", ("asc", "aries"))
|
|
def test_wheel_orientation_reaches_the_renderer(monkeypatch, orientation):
|
|
"""Wybór obrotu koła musi dojść z formularza do rysunku — i wrócić
|
|
zaznaczony, żeby nie gubił się przy przeliczeniu."""
|
|
c = _client_with_stub_logic(monkeypatch, _minimal_chart())
|
|
r = c.post("/", data={**FORM, "wheel_orientation": orientation})
|
|
assert r.status_code == 200, r.text[:400]
|
|
assert f'value="{orientation}" checked' in r.text
|