fix(konta): awaria magazynu tłumaczy się zamiast dawać gołe 500
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Failing after 4m55s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 17s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m37s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 4m53s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m29s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 19s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Failing after 4m55s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 17s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m37s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 4m53s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m29s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 19s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
Ekran „Konta" wywalał się na produkcji błędem 500 bez słowa wyjaśnienia. Odtworzone lokalnie: `_read()` łapał wyłącznie brak pliku i zły JSON, więc każdy inny błąd systemu plików — a na udziale NFS to głównie prawa — leciał na wierzch jako nieobsłużony wyjątek. To jest szczególnie zły sposób na awarię AKURAT TUTAJ: ekran kont jest jedynym miejscem, z którego administrator może taki problem naprawić, a gołe 500 nie mówi mu ani co, ani gdzie. Teraz każdy błąd magazynu ma twarz: osobny wyjątek AccountsUnavailable niosący ŚCIEŻKĘ i powód z systemu operacyjnego, plus podpowiedź najczęstszej przyczyny (prawa katalogu na udziale albo wolumen zamontowany tylko do odczytu). Strona renderuje się normalnie z tym komunikatem u góry. Objęte są wszystkie cztery drogi zapisu, a nie tylko odczyt. W szczególności mkstemp: przy katalogu tylko do odczytu wywala się ONO pierwsze, jeszcze zanim dojdzie do zapisu i podmiany — więc obudowanie samego os.replace nic by nie dało (złapane testem, nie przeglądem kodu). USZKODZONY PLIK NIE JEST NADPISYWANY. Wcześniej niepoprawny JSON dawał pusty zbiór kont, co przy pierwszym zapisie skasowałoby WSZYSTKIE konta bez śladu. Teraz to odmowa z komunikatem — plik zostaje nietknięty, a test tego pilnuje. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -309,3 +309,62 @@ def test_deleting_an_account_cannot_touch_the_administrator(env, monkeypatch):
|
||||
assert r.status_code == 303
|
||||
assert store.exists("ala"), "kasowanie nieistniejącego konta ruszyło inne"
|
||||
assert c.get("/", headers=admin).status_code == 200
|
||||
|
||||
|
||||
# ── awaria magazynu kont ─────────────────────────────────────────────────
|
||||
# Ekran kont to JEDYNE miejsce, z którego administrator może naprawić problem
|
||||
# z magazynem — więc musi na nim przeczytać, co i gdzie jest nie tak. Gołe 500
|
||||
# (tak było na pierwszym wdrożeniu) zostawia go z niczym.
|
||||
|
||||
def _unreadable(tmp_path, monkeypatch, mode):
|
||||
d = tmp_path / "stan"
|
||||
d.mkdir()
|
||||
monkeypatch.setenv("ACCOUNTS_FILE", str(d / "accounts.json"))
|
||||
d.chmod(mode)
|
||||
return d
|
||||
|
||||
|
||||
def test_unreadable_store_explains_itself_instead_of_500(env, tmp_path, monkeypatch):
|
||||
d = _unreadable(tmp_path, monkeypatch, 0o000)
|
||||
try:
|
||||
c = _client(monkeypatch)
|
||||
r = c.get("/accounts", headers=_auth("szef", "tajne-szefa"))
|
||||
assert r.status_code == 200, "problem z magazynem nie może wywalać strony"
|
||||
assert "Nie mogę odczytać pliku kont" in r.text
|
||||
assert str(d / "accounts.json") in r.text, "komunikat ma podać ŚCIEŻKĘ"
|
||||
finally:
|
||||
d.chmod(0o755)
|
||||
|
||||
|
||||
def test_read_only_store_refuses_to_save_with_a_reason(env, tmp_path, monkeypatch):
|
||||
d = _unreadable(tmp_path, monkeypatch, 0o555)
|
||||
try:
|
||||
c = _client(monkeypatch)
|
||||
r = c.post("/accounts/create", headers=_auth("szef", "tajne-szefa"),
|
||||
follow_redirects=False,
|
||||
data={"login": "ala", "password": "x", "granted": ["chart"]})
|
||||
assert r.status_code == 303
|
||||
# Komunikat jedzie w parametrze zapytania, więc jest zakodowany —
|
||||
# porównanie na surowym nagłówku sprawdzałoby procenty, nie treść.
|
||||
from urllib.parse import unquote_plus
|
||||
|
||||
assert "Nie mogę zapisać pliku kont" in unquote_plus(r.headers["location"])
|
||||
finally:
|
||||
d.chmod(0o755)
|
||||
|
||||
|
||||
def test_a_corrupt_file_is_never_silently_overwritten(env, tmp_path, monkeypatch):
|
||||
"""Nadpisanie uszkodzonego pliku pustym zbiorem skasowałoby WSZYSTKIE konta.
|
||||
Lepiej odmówić i powiedzieć, co jest nie tak."""
|
||||
path = tmp_path / "accounts.json"
|
||||
path.write_text("{to nie jest json", encoding="utf-8")
|
||||
monkeypatch.setenv("ACCOUNTS_FILE", str(path))
|
||||
|
||||
c = _client(monkeypatch)
|
||||
r = c.get("/accounts", headers=_auth("szef", "tajne-szefa"))
|
||||
assert r.status_code == 200 and "uszkodzony" in r.text
|
||||
|
||||
c.post("/accounts/create", headers=_auth("szef", "tajne-szefa"),
|
||||
follow_redirects=False, data={"login": "ala", "password": "x"})
|
||||
assert path.read_text(encoding="utf-8") == "{to nie jest json", \
|
||||
"uszkodzony plik został nadpisany — konta by zniknęły"
|
||||
|
||||
Reference in New Issue
Block a user