338ec24e90
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m20s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m26s
Testy / Testy astrodemo (pull_request) Successful in 9m25s
Testy / Testy astroklient (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 6s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 5s
build / build (push) Successful in 12s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m21s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Testy astrodemo (push) Successful in 9m25s
Testy / Testy astroklient (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 6s
Testy / Kontrola składni wszystkich warstw (push) Successful in 4s
Astroklient przedstawiał się na stronie głównej jako „astrololo" — w tytule karty i w nagłówku. To najpoważniejszy z możliwych śladów: mówi klientowi wprost, że trzyma okrojoną wersję czegoś większego, i podaje frazę, po której da się wyszukać, czego mu nie dano. Cała reszta izolacji przestaje mieć znaczenie, gdy produkt sam się przedstawia nazwą rodzica. Wzięło się stąd, że nazwa była wpisana na sztywno w szablony, a szablony są WSPÓLNE — dokładnie ta sama przyczyna, co przy zaszytym w base.html warunku na administratora. Nazwa mieszka teraz w module `produkt.py`, który nakładka produktu nadpisuje; szablony o nią pytają, zamiast ją znać. astrodemo NIE było dotknięte: ma własne kopie szablonów i pokazuje „astrodemo". DRUGIE ŹRÓDŁO WYCIEKU, mniej oczywiste: nazwa siedziała też w identyfikatorach JavaScriptu, które widzi każdy, kto otworzy narzędzia deweloperskie — zdarzenie `astrololo:coords` i klucz `astrololo.form.v1` w localStorage. Przemianowane na neutralne `kosmogram:*`; te identyfikatory nie mają powodu nosić nazwy rodziny produktów. SKUTEK UBOCZNY: zmiana klucza w localStorage kasuje raz zapamiętane wersje robocze formularza. Sprawdzone, że NIE wyciekają: nagłówki X-Astrololo-* jadą wyłącznie między warstwami, a dokumentacja OpenAPI (gdzie zostaje tytuł aplikacji FastAPI) oddaje 404 także zalogowanemu. TEST rozdziela to, co widzi człowiek, od identyfikatorów wewnętrznych. Nazwa rdzenia wolno występować w nazwach loggerów, materiale do wyprowadzania kluczy i nagłówkach międzywarstwowych; nie wolno w szablonach, zasobach ani w treści odpowiedzi. Zakaz na wszystko byłby albo obejściem przez zmianę nazw kluczy kryptograficznych, albo martwą regułą wyłączaną przy pierwszej okazji. Sprawdzony w obie strony: po cofnięciu nakładki test pada. Testy: astroklient 15, presentation 368, astrodemo 28, logic 342, data 42, render 41. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
125 lines
5.1 KiB
Python
125 lines
5.1 KiB
Python
"""Poprawki zgłoszone przez partnerów: offset względem GMT i lokalizacja po
|
|
kliknięciu „Tu i teraz" (PRE-19, PRE-20).
|
|
|
|
JS-a tu nie uruchomimy, więc pilnujemy tego strukturalnie — tak samo jak
|
|
niezmienników klienta HTTP. Obie regresje są ciche (nic się nie wywala, tylko
|
|
formularz pokazuje nieprawdę), więc test na źródle jest tu wart więcej niż zwykle.
|
|
"""
|
|
import pathlib
|
|
|
|
import pytest
|
|
|
|
APP = pathlib.Path(__file__).resolve().parents[1] / "app"
|
|
TEMPLATES = APP / "templates"
|
|
GEO_JS = APP / "static" / "geo.js"
|
|
|
|
WITH_OFFSET = ("chart.html", "interpret.html", "timeline.html")
|
|
|
|
|
|
# ───────────────────────────── PRE-19: offset względem GMT ───────────────
|
|
|
|
@pytest.mark.parametrize("name", WITH_OFFSET)
|
|
def test_offset_label_says_relative_to_gmt(name):
|
|
"""Etykieta musi mówić, WZGLĘDEM CZEGO liczymy przesunięcie."""
|
|
html = (TEMPLATES / name).read_text(encoding="utf-8")
|
|
assert "Offset względem GMT" in html, f"{name}: etykieta nie mówi o GMT"
|
|
|
|
|
|
@pytest.mark.parametrize("name", WITH_OFFSET)
|
|
def test_offset_step_is_quarter_hour(name):
|
|
"""Krok 15 minut = 0,25 h. Strefy pół- i ćwierćgodzinne (Indie +5:30,
|
|
Nepal +5:45) są realne — przy kroku 1 h nie dało się ich ustawić."""
|
|
html = (TEMPLATES / name).read_text(encoding="utf-8")
|
|
assert 'name="tz_offset" step="0.25"' in html, f"{name}: krok inny niż 15 minut"
|
|
|
|
|
|
@pytest.mark.parametrize("name", WITH_OFFSET)
|
|
def test_offset_range_covers_real_zones(name):
|
|
html = (TEMPLATES / name).read_text(encoding="utf-8")
|
|
assert 'min="-12"' in html and 'max="14"' in html, f"{name}: brak sensownego zakresu stref"
|
|
|
|
|
|
# ──────────────────── PRE-20: „Tu i teraz" odświeża nazwę miejsca ────────
|
|
|
|
def _geo_source() -> str:
|
|
return GEO_JS.read_text(encoding="utf-8")
|
|
|
|
|
|
def _handler_body() -> str:
|
|
"""Ciało handlera zdarzenia kosmogram:coords (wysyłanego przez „Tu i teraz").
|
|
|
|
Szukamy KONKRETNIE `addEventListener`, nie samej nazwy zdarzenia — ta pada
|
|
także w komentarzu nagłówkowym pliku i wtedy wycięlibyśmy komentarz zamiast
|
|
kodu (test przechodziłby albo padał z niewłaściwego powodu).
|
|
"""
|
|
src = _geo_source()
|
|
start = src.index("addEventListener('kosmogram:coords'")
|
|
end = src.index("});", start)
|
|
return src[start:end]
|
|
|
|
|
|
def test_now_button_handler_refreshes_place_name():
|
|
"""REGRESJA: po „Tu i teraz" pineska i współrzędne skakały na nowe miejsce,
|
|
a w polu tekstowym zostawała stara nazwa — formularz pokazywał jedno miejsce,
|
|
a liczył dla innego."""
|
|
assert "reverseName(" in _handler_body(), (
|
|
"handler kosmogram:coords nie odświeża nazwy miejsca (reverseName)"
|
|
)
|
|
|
|
|
|
def test_reverse_name_clears_field_before_lookup():
|
|
"""Nazwa jest czyszczona ZANIM przyjdzie odpowiedź. Gdyby /reverse nie
|
|
odpowiedziało (brak sieci), zostałaby stara nazwa — czyli pole kłamałoby
|
|
o lokalizacji. Lepiej puste pole niż nieprawdziwe."""
|
|
src = _geo_source()
|
|
start = src.index("function reverseName")
|
|
body = src[start:src.index("\n }", start)]
|
|
# sposób zapisu może się zmienić (bezpośrednie .value albo setVal, które
|
|
# dodatkowo zgłasza `change`) — pilnujemy KOLEJNOŚCI, nie składni
|
|
clear_at = max(body.find("searchEl.value = ''"), body.find("setVal(searchEl, '')"))
|
|
fetch_at = body.find("fetch(")
|
|
assert clear_at != -1, "reverseName nie czyści pola nazwy"
|
|
assert clear_at < fetch_at, "pole czyszczone dopiero po zapytaniu — stara nazwa zdąży zostać"
|
|
|
|
|
|
def test_now_button_still_syncs_map():
|
|
"""Odświeżenie nazwy nie może zastąpić przesunięcia pineski i widoku mapy."""
|
|
body = _handler_body()
|
|
assert "marker.setLatLng" in body and "map.setView" in body
|
|
|
|
|
|
# ---- strefa czasowa z lokalizacji (PRE-03) ----
|
|
|
|
def test_setcoords_resolves_timezone():
|
|
"""Nowe współrzędne (mapa/wyszukiwarka) mają odświeżyć strefę i offset."""
|
|
src = _geo_source()
|
|
body = src[src.index("function setCoords"):src.index("\n }", src.index("function setCoords"))]
|
|
assert "resolveTz()" in body
|
|
|
|
|
|
def test_now_button_resolves_timezone():
|
|
"""„Tu i teraz" to nowe miejsce → nowa strefa."""
|
|
assert "resolveTz()" in _handler_body()
|
|
|
|
|
|
def test_date_change_recomputes_timezone():
|
|
"""Czas letni zależy od daty — zmiana daty musi przeliczyć offset."""
|
|
src = _geo_source()
|
|
assert "dateEl.addEventListener('change', resolveTz)" in src
|
|
|
|
|
|
def test_timezone_hint_does_not_clobber_manual_offset_on_load():
|
|
"""Na wejściu podpowiadamy strefę TYLKO gdy offset wygląda na nieustawiony —
|
|
inaczej nadpisalibyśmy wartość ręcznie wpisaną i wysłaną przez użytkownika."""
|
|
src = _geo_source()
|
|
assert "parseFloat(tzEl.value) === 0" in src
|
|
|
|
|
|
def test_timezone_fills_offset_field_and_shows_zone():
|
|
src = _geo_source()
|
|
start = src.index("function resolveTz")
|
|
body = src[start:src.index("\n }", start)]
|
|
assert "/timezone?lat=" in body
|
|
assert "setVal(tzEl, d.offset)" in body # wypełnia pole offsetu
|
|
assert "d.label" in body # pokazuje wykrytą strefę
|