fd79513ce2
Testy / Testy warstwy logicznej (silnik) (pull_request) Failing after 4s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Failing after 3s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Failing after 3s
Testy / Testy astrodemo (pull_request) Failing after 3s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 2s
Testy / Kontrola składni wszystkich warstw (pull_request) Failing after 3s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 3s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Failing after 3s
Testy / Testy astrodemo (push) Failing after 3s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 2s
Testy / Kontrola składni wszystkich warstw (push) Failing after 3s
build / build (push) Successful in 9s
Trzeci z pięciu kroków. Zmieniłem jego zakres wobec planu i warto wiedzieć dlaczego: pierwotnie miała to być deduplikacja pięciu kopii link_crypto.py, ale po zrobieniu PR 2 widać, że astroklienta blokuje co innego — ekrany, których nie ma mieć, siedzą wewnątrz jednego main.py. Deduplikacja kryptografii jest realnym długiem, ale niczego nie blokuje. PODZIAŁ. main.py (1023 linie) rozpadł się na podstawa.py (wspólne obiekty i pomocnicy), jedenaście modułów w app/ekrany/ i main.py, który jest już samym ZŁOŻENIEM: lista importów JEST definicją produktu. Podział zrobiony mechanicznie, z osobnym sprawdzeniem, że żadna sekcja nie wołała pomocnika z innej (nie wołała). KATALOG Z REJESTRACJI. Dotąd wszystkie funkcje były wypisane w features.py, więc obraz produktu, który części z nich nie ma, i tak niósł ich nazwy — spis funkcji, których nie ma jak włączyć. Teraz ekran zgłasza siebie, swoje trasy i swoje zasoby przy imporcie własnego modułu, a features.py nie wymienia ani jednego ekranu. Kolejność w nawigacji jest jawna (`kolejnosc`), żeby nie rządziła nią kolejność importów. To samo dotyczy nawigacji administratora: odsyłacz do ekranu kont był wpisany na sztywno w base.html, więc w węższym produkcie zostawał martwy link i nazwa ekranu, którego nie ma. EKSPORT JAKO MODUŁ. Zgodnie z ustaleniem eksport jest funkcją administracyjną, więc musi dać się usunąć. app/moduly/eksport/ zabiera arkusz, trasę PDF-a i akcję formularza. „Można, ale nie temu kontu" i „nie ma takiej możliwości" to dwie różne gwarancje, a eksport wynosi najwięcej treści baz naraz. MOST ODKRYWA MODUŁY. Skoro modułów jest więcej niż jeden, most nie może ich znać z nazwy — nazwa nieobecnego modułu jechałaby do obrazu, w którym go nie ma. Przechodzi więc po podkatalogach app/moduly/ i pyta każdy, co wnosi. Katalog generowania przeniesiony z app/dodatki na app/moduly/dodatki. ZNALEZIONE PRZY OKAZJI. Po wydzieleniu eksportu okazało się, że jego ścieżki SZCZĘŚLIWEJ nie sprawdzał żaden test — badano wyłącznie odmowę dla konta bez uprawnienia. Moduł dostaje zależności z wywołania montującego, więc brak jednej z nich wyszedłby dopiero przy pierwszym kliknięciu. Dopisany test funkcjonalny (realny arkusz, sprawdzany aż do nagłówka ZIP-a) i brakujące zależności. Cztery komentarze w plikach współdzielonych wymieniały zakładkę „Skompiluj", w tym wheelzoom.js ze wzmianką o „przyszłej zakładce" — to samo zgłoszenie, które audyt podnosił wcześniej. TEST ZŁOŻENIA. Buduje węższy produkt NAPRAWDĘ: kopiuje drzewo, usuwa cztery ekrany i oba moduły, uruchamia aplikację w OSOBNYM PROCESIE (importy są zapamiętywane, więc sprawdzanie tego w procesie, który moduł już zaimportował, dawałoby wynik fałszywie pozytywny) i sprawdza, że wstaje, że zachowane ekrany oddają 200, że usunięte oddają 404 (nie 403 i nie 500), że katalog opisuje ten obraz, i że w nawigacji nie ma martwych odsyłaczy. Napisałem najpierw ostrzejszy test — „nazwa ekranu nie pada poza jego modułem" — i go wyrzuciłem: zgłaszał wzmianki o Horoskopie w plikach współdzielonych, choć astroklient Horoskop MA. Ślad ma znaczenie wyłącznie wobec konkretnego złożenia, więc sprawdzenie należy do produktu, nie do mechanizmu. Testy: presentation 368, logic 342, data 37, render 41. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
114 lines
5.1 KiB
Python
114 lines
5.1 KiB
Python
"""Wspólne dane formularza między zakładkami + pole imienia (PRE-21).
|
|
|
|
Znów test strukturalny na źródle: synchronizacja żyje w JS, a jej awaria jest
|
|
CICHA — formularz po prostu przestaje pamiętać dane i użytkownik wpisuje je od
|
|
nowa. Pilnujemy więc reguł, które łatwo zgubić przy kolejnych zmianach.
|
|
"""
|
|
import pathlib
|
|
|
|
import pytest
|
|
|
|
from tests.skladanie import zloz_kod
|
|
|
|
APP = pathlib.Path(__file__).resolve().parents[1] / "app"
|
|
TEMPLATES = APP / "templates"
|
|
SYNC_JS = (APP / "static" / "formsync.js").read_text(encoding="utf-8")
|
|
GEO_JS = (APP / "static" / "geo.js").read_text(encoding="utf-8")
|
|
BASE_HTML = (TEMPLATES / "base.html").read_text(encoding="utf-8")
|
|
# Trasy mieszkają w app/ekrany/* — patrz tests/skladanie.py.
|
|
MAIN_PY = zloz_kod()
|
|
|
|
WITH_FORM = ("chart.html", "interpret.html", "timeline.html")
|
|
|
|
# Pola, które mają wędrować między zakładkami. Gdy dojdzie nowe wspólne pole,
|
|
# ten wykaz ma się nie rozjechać z formsync.js.
|
|
SHARED = ("person", "date", "time", "tz_offset", "lat", "lon",
|
|
"house_system", "zodiac", "place",
|
|
"stations", "tables", "house_systems") # opcje też wędrują (regresja: podsumowanie liczyło się bez nich)
|
|
|
|
|
|
def test_option_checkboxes_are_synced_by_type():
|
|
"""Stacje/tabele to checkboxy (.checked), a porównanie domów to wiele checkboxów
|
|
tej samej nazwy (lista) — formsync musi umieć oba, nie tylko .value."""
|
|
assert "'check'" in SYNC_JS and "'multi'" in SYNC_JS
|
|
assert "el.checked" in SYNC_JS
|
|
|
|
|
|
# ─────────────────────────────── pole imienia ────────────────────────────
|
|
|
|
@pytest.mark.parametrize("name", WITH_FORM)
|
|
def test_person_field_present_on_every_form(name):
|
|
"""Imię i nazwisko trafia do nagłówka raportu PDF (PRE-24) — musi dać się
|
|
je wpisać z każdej zakładki, bo dane są wspólne."""
|
|
html = (TEMPLATES / name).read_text(encoding="utf-8")
|
|
assert 'name="person"' in html, f"{name}: brak pola imienia"
|
|
|
|
|
|
@pytest.mark.parametrize("name", WITH_FORM)
|
|
def test_person_value_survives_submit(name):
|
|
"""Po wysłaniu formularza pole ma pokazywać to, co wpisano — inaczej znikałoby
|
|
przy każdym przeliczeniu."""
|
|
html = (TEMPLATES / name).read_text(encoding="utf-8")
|
|
assert "form.person" in html, f"{name}: wartość imienia nie wraca po POST"
|
|
|
|
|
|
def test_handlers_accept_and_echo_person():
|
|
"""Każdy handler renderujący WSPÓLNY formularz przyjmuje `person` i wkłada go
|
|
z powrotem do `form` — inaczej imię znikałoby po przeliczeniu.
|
|
Cztery: Horoskop, Interpretacje, Kalendarz, Skompiluj (PRE-23). Synastria ma
|
|
własne `a_person`/`b_person` (dwie osoby), więc echo `"person": person` = 4."""
|
|
assert MAIN_PY.count(" person: str = Form") == 4 # spacja z przodu wyklucza a_/b_person
|
|
assert MAIN_PY.count('"person": person') == 4
|
|
|
|
|
|
# ─────────────────────── synchronizacja między zakładkami ────────────────
|
|
|
|
@pytest.mark.parametrize("key", SHARED)
|
|
def test_every_shared_field_is_synced(key):
|
|
assert f"'{key}'" in SYNC_JS, f"pole {key} nie jest synchronizowane"
|
|
|
|
|
|
@pytest.mark.parametrize("name", WITH_FORM)
|
|
def test_sync_script_loaded_before_map_script(name):
|
|
"""KOLEJNOŚĆ MA ZNACZENIE: formsync z `defer` wykonuje się przed geo.js
|
|
(skrypty defer idą w kolejności dokumentu), więc mapa startuje już
|
|
z odtworzonymi współrzędnymi zamiast skakać po chwili.
|
|
|
|
Skrypt siedzi teraz w bloku `head_scripts` EKRANU, nie w base.html. Ładowany
|
|
globalnie docierał także do konta, które ma wyłącznie Pliki — a jego treść
|
|
wymienia z nazwy zakładki, których takie konto nie ma."""
|
|
html = (TEMPLATES / name).read_text(encoding="utf-8")
|
|
assert "head_scripts" in html and "formsync.js" in html
|
|
przed_geo = html.split("formsync.js")[0]
|
|
assert "defer" in przed_geo.rsplit("<script", 1)[-1] + "defer"
|
|
|
|
|
|
def test_sync_reacts_to_other_browser_tab():
|
|
"""Zmiana w drugiej otwartej karcie ma dociągnąć się tutaj (zdarzenie storage)."""
|
|
assert "'storage'" in SYNC_JS
|
|
|
|
|
|
def test_sync_persists_on_submit():
|
|
assert "'submit'" in SYNC_JS
|
|
|
|
|
|
def test_corrupt_storage_does_not_break_the_page():
|
|
"""Uszkodzony wpis w localStorage nie może zablokować formularza."""
|
|
assert "catch" in SYNC_JS and "JSON.parse" in SYNC_JS
|
|
|
|
|
|
def test_map_changes_are_reported_to_sync():
|
|
"""geo.js ustawia pola z KODU (`.value = ...`), co samo z siebie nie wywołuje
|
|
zdarzeń — bez jawnego `change` synchronizacja przegapiłaby wybór z mapy."""
|
|
assert "function setVal" in GEO_JS
|
|
assert "dispatchEvent(new Event('change'" in GEO_JS
|
|
# wszystkie miejsca ustawiające pola idą przez setVal
|
|
assert "latEl.value =" not in GEO_JS.replace("el.value = value", "")
|
|
assert "searchEl.value = d.name" not in GEO_JS
|
|
|
|
|
|
def test_sync_does_not_wipe_fields_missing_on_a_tab():
|
|
"""Zakładka bez danego pola (np. Sygnifikatory) nie może wyczyścić wartości
|
|
zapamiętanej z innej zakładki. `collect` pomija pole, którego nie ma na stronie."""
|
|
assert "if (!els.length) return;" in SYNC_JS
|