320a0ab24e
build-render / build (push) Failing after 8s
build-swisseph / build (push) Successful in 9s
build / build (push) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 5s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 4s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Failing after 4s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 6s
Testy / Kontrola składni wszystkich warstw (push) Failing after 4s
Ukrywanie jest teraz nadrzędne wobec wygody i czytelności komunikatów. Persona: konto z uprawnieniami files + files_input, zatrudnione wyłącznie do wgrywania plików. Nie ma się dowiedzieć, po co je wgrywa ani co program będzie robił — bo to rozgada. Audyt sześciu kanałów wycieku (statyki, HTML, sondowanie HTTP, ekran plików, odpowiedzi JSON i błędy, pozostałe warstwy) potwierdził 26 wycieków, każdy odtworzony uruchomionym kodem i zweryfikowany adwersarialnie. Ani jeden nie był przyciskiem. ZASÓB JEST CZĘŚCIĄ FUNKCJI /static/ omijało CAŁĄ bramkę (PUBLIC_PREFIXES), więc każdy skrypt i arkusz pobierał ktokolwiek, także niezalogowany, pod zgadywalnym adresem — a ich treść wymienia ekrany, dostawców modeli i przeznaczenie plików. Ruch ten nie trafiał przy tym ani do dziennika, ani pod limit żądań, więc wyciek był niewidoczny. Zasoby idą teraz trasą z bramką; każdy ma w features.STATIC uprawnienie swojego ekranu. Publiczny został jeden base.css, bo potrzebuje go ekran logowania. KOMENTARZ NIE JEDZIE NA DRUT Komentarze w CSS/JS opisywały funkcje pełnymi zdaniami po polsku — łącznie z „Wstrzymane widzi tylko administrator", czyli i mechanizmem kwarantanny, i istnieniem konta o wyższych uprawnieniach. _asset_body() usuwa je przy serwowaniu; w repozytorium zostają. styles.css rozbity na base.css + arkusz na ekran + x-ai.css. Jeden plik z wszystkimi selektorami był spisem treści programu. Podział zrobiony mechanicznie, z osobnym sprawdzeniem, że żaden ekran nie stracił reguły. base.html ładował skrypty kosmogramu na KAŻDEJ stronie — konto mające wyłącznie Pliki pobierało je przy wejściu na swój jedyny ekran, razem ze wzmianką o „przyszłej zakładce". Teraz dokłada je ekran, który ich używa. RÓŻNICA JEST INFORMACJĄ Komunikat po wgraniu pliku różnił się zależnie od wyniku walidacji — czyli był wyrocznią do odgadywania reguł, które ma znać tylko administrator — i mówił wprost, że plik „musi zatwierdzić administrator". Teraz jest jeden, ten sam. _logic_error wypisywał na ekran nazwę trasy, nazwę podsystemu, nazwę gałęzi rozwojowej i wewnętrzny host:port. Jedno zdanie dla wszystkich awarii, szczegóły do dziennika. Odsiew w jednym punkcie, nie w siedemnastu wywołaniach. Ponadto: stopka nie ogłasza architektury, /health nie nazywa warstwy, konto bez ekranów dostaje 404 zamiast tłumaczenia, ekran plików mówi o plikach zamiast o „bazach interpretacyjnych", klasy .house-warning i .account-card przemianowane na neutralne, a logic/data/render/engine-swisseph nie wystawiają już /docs ani /openapi.json i nie publikują portów na hoście. ZAPORA SŁOWNIKOWA test_slownik_zakazany.py nie sprawdza miejsc, tylko przechodzi wszystko, co dane konto może pobrać, i szuka słów, które nie mają prawa paść (87 pozycji dla tej persony). Nazwy funkcji, adresy ekranów i nazwy zasobów biorą się wprost z katalogu, więc nowa funkcja obejmuje się sama. Kontrola pozytywna pilnuje, żeby test nie przechodził dlatego, że program jest pusty. Sprawdzone: zapora puszczona na treść sprzed poprawek daje 16 trafień na samym styles.css i łapie każdy ze zneutralizowanych komunikatów. 358 testów zielonych, ekrany obejrzane w przeglądarce. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
"""Cache-busting plików statycznych (PRE-26).
|
|
|
|
Po deployu przeglądarka trzymała stare styles.css / *.js (ten sam URL). Doklejamy
|
|
hash treści → zmiana pliku zmienia URL. Regresja byłaby CICHA (użytkownik widzi
|
|
stary interfejs), więc pilnujemy strukturalnie.
|
|
"""
|
|
import os
|
|
import pathlib
|
|
import re
|
|
|
|
os.environ.pop("APP_PASSWORD", None) # import app.main bez bramki logowania
|
|
|
|
from app.main import _asset_version, static_url
|
|
|
|
TEMPLATES = pathlib.Path(__file__).resolve().parents[1] / "app" / "templates"
|
|
|
|
|
|
def test_static_url_carries_content_hash():
|
|
assert re.fullmatch(r"/static/base\.css\?v=[0-9a-f]{8}", static_url("base.css"))
|
|
|
|
|
|
def test_version_is_content_based_so_differs_between_files():
|
|
assert _asset_version("base.css") != _asset_version("formsync.js")
|
|
|
|
|
|
def test_missing_file_does_not_crash():
|
|
assert static_url("nie-ma-takiego.js").endswith("?v=0")
|
|
|
|
|
|
def test_no_template_serves_raw_static_paths():
|
|
"""Żaden href/src nie może iść na surowe /static/... — omijałoby cache-busting."""
|
|
for f in TEMPLATES.glob("*.html"):
|
|
txt = f.read_text(encoding="utf-8")
|
|
assert 'src="/static/' not in txt, f"{f.name}: surowy src bez cache-bustingu"
|
|
assert 'href="/static/' not in txt, f"{f.name}: surowy href bez cache-bustingu"
|
|
|
|
|
|
def test_base_uses_helper_for_the_shared_stylesheet():
|
|
base = (TEMPLATES / "base.html").read_text(encoding="utf-8")
|
|
assert "static('base.css')" in base
|
|
|
|
|
|
def test_version_follows_what_is_sent_not_what_is_on_disk():
|
|
"""Adres niesie hash treści WYSYŁANEJ, bo komentarze są przy serwowaniu
|
|
usuwane. Gdyby hash szedł z pliku na dysku, poprawka w samym komentarzu
|
|
podbijałaby adres i kasowała cache przeglądarki bez żadnej zmiany dla niej."""
|
|
import hashlib
|
|
|
|
from app.main import _asset_body
|
|
|
|
assert _asset_version("base.css") == hashlib.md5(_asset_body("base.css")).hexdigest()[:8]
|