fix(testy): awaria magazynu kont wymuszana podmianą, nie prawami pliku
build / build (push) Successful in 7s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 6s
Testy / Kontrola składni wszystkich warstw (push) Successful in 5s
build / build (push) Successful in 7s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 6s
Testy / Kontrola składni wszystkich warstw (push) Successful in 5s
Dwa testy z PR #71 przechodziły lokalnie i padały w CI. Przyczyna nie miała nic wspólnego z badaną rzeczą: wymuszały awarię przez chmod 000 i chmod 555, a CI działa jako ROOT — root omija bity uprawnień w Linuksie, więc odczyt i zapis się udawały i asercje leciały na komunikat, którego nie było. Test zależny od tego, KTO go uruchamia, jest gorszy niż jego brak: daje fałszywe poczucie pokrycia i zapala się w miejscu niezwiązanym z tym, co sprawdza. Teraz awarię wymuszamy podmianą dokładnie tego punktu, w którym system plików mówi „nie": odczytu pliku kont (osłona przepuszcza wszystko inne, żeby szablony nadal się wczytywały) oraz mkstemp przy zapisie. To drugie jest celowe — mkstemp wywala się PIERWSZY przy katalogu tylko do odczytu, jeszcze zanim dojdzie do zapisu i podmiany, i właśnie tego dotyczyła naprawiana poprawka. Sprawdzian na PRAWDZIWYCH prawach zostaje, ale z pominięciem przy uruchomieniu z roota — jako kontrola, że podmiana odpowiada rzeczywistości, a nie tylko sama sobie. W CI się nie wykona i to jest w porządku: znaczyłby tam tyle co nic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit was merged in pull request #73.
This commit is contained in:
@@ -12,6 +12,7 @@ DWIE WŁASNOŚCI, KTÓRYCH PILNUJE TEN PLIK
|
|||||||
pozycji w menu, brak rysunków w źródle strony.
|
pozycji w menu, brak rysunków w źródle strony.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -315,40 +316,80 @@ def test_deleting_an_account_cannot_touch_the_administrator(env, monkeypatch):
|
|||||||
# Ekran kont to JEDYNE miejsce, z którego administrator może naprawić problem
|
# 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
|
# 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.
|
# (tak było na pierwszym wdrożeniu) zostawia go z niczym.
|
||||||
|
#
|
||||||
|
# AWARIĘ WYMUSZAMY PODMIANĄ, NIE PRAWAMI PLIKU. Pierwsza wersja robiła chmod 000
|
||||||
|
# i chmod 555 — i przechodziła lokalnie, a padała w CI, bo CI działa jako ROOT,
|
||||||
|
# a root omija bity uprawnień w Linuksie. Test zależny od tego, kto go uruchamia,
|
||||||
|
# jest gorszy niż jego brak: daje fałszywe poczucie pokrycia i zapala się
|
||||||
|
# w miejscu, które z badaną rzeczą nie ma nic wspólnego.
|
||||||
|
#
|
||||||
|
# Podmiana jest przy okazji CELNIEJSZA: sprawdzamy dokładnie ten punkt, w którym
|
||||||
|
# system plików mówi „nie", zamiast liczyć, że da się go do tego zmusić.
|
||||||
|
|
||||||
def _unreadable(tmp_path, monkeypatch, mode):
|
def _fail_reading_accounts(monkeypatch, exc=PermissionError(13, "Permission denied")):
|
||||||
d = tmp_path / "stan"
|
"""Odczyt PLIKU KONT wywala się; wszystko inne (szablony!) czyta normalnie."""
|
||||||
d.mkdir()
|
import builtins
|
||||||
monkeypatch.setenv("ACCOUNTS_FILE", str(d / "accounts.json"))
|
|
||||||
d.chmod(mode)
|
from app import accounts as store
|
||||||
return d
|
|
||||||
|
real_open, target = builtins.open, store.store_path()
|
||||||
|
|
||||||
|
def guarded(file, *a, **kw):
|
||||||
|
if str(file) == target:
|
||||||
|
raise exc
|
||||||
|
return real_open(file, *a, **kw)
|
||||||
|
|
||||||
|
monkeypatch.setattr(builtins, "open", guarded)
|
||||||
|
|
||||||
|
|
||||||
def test_unreadable_store_explains_itself_instead_of_500(env, tmp_path, monkeypatch):
|
def test_unreadable_store_explains_itself_instead_of_500(env, tmp_path, monkeypatch):
|
||||||
d = _unreadable(tmp_path, monkeypatch, 0o000)
|
monkeypatch.setenv("ACCOUNTS_FILE", str(tmp_path / "stan" / "accounts.json"))
|
||||||
try:
|
_fail_reading_accounts(monkeypatch)
|
||||||
c = _client(monkeypatch)
|
|
||||||
r = c.get("/accounts", headers=_auth("szef", "tajne-szefa"))
|
c = _client(monkeypatch)
|
||||||
assert r.status_code == 200, "problem z magazynem nie może wywalać strony"
|
r = c.get("/accounts", headers=_auth("szef", "tajne-szefa"))
|
||||||
assert "Nie mogę odczytać pliku kont" in r.text
|
assert r.status_code == 200, "problem z magazynem nie może wywalać strony"
|
||||||
assert str(d / "accounts.json") in r.text, "komunikat ma podać ŚCIEŻKĘ"
|
assert "Nie mogę odczytać pliku kont" in r.text
|
||||||
finally:
|
assert "accounts.json" in r.text, "komunikat ma podać ŚCIEŻKĘ"
|
||||||
d.chmod(0o755)
|
|
||||||
|
|
||||||
|
|
||||||
def test_read_only_store_refuses_to_save_with_a_reason(env, tmp_path, monkeypatch):
|
def test_read_only_store_refuses_to_save_with_a_reason(env, tmp_path, monkeypatch):
|
||||||
d = _unreadable(tmp_path, monkeypatch, 0o555)
|
"""Podmieniamy mkstemp, bo to ON wywala się PIERWSZY przy katalogu tylko do
|
||||||
|
odczytu — jeszcze zanim dojdzie do zapisu i podmiany pliku. Obudowanie samego
|
||||||
|
os.replace niczego by nie dało i właśnie tego dotyczyła poprawka."""
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
monkeypatch.setenv("ACCOUNTS_FILE", str(tmp_path / "stan" / "accounts.json"))
|
||||||
|
monkeypatch.setattr(tempfile, "mkstemp",
|
||||||
|
lambda *a, **kw: (_ for _ in ()).throw(
|
||||||
|
PermissionError(13, "Read-only file system")))
|
||||||
|
|
||||||
|
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"])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(os.geteuid() == 0,
|
||||||
|
reason="root omija bity uprawnień — sprawdzian nic by nie znaczył")
|
||||||
|
def test_real_file_permissions_produce_the_same_message(env, tmp_path, monkeypatch):
|
||||||
|
"""Kontrola, że podmiana wyżej odpowiada RZECZYWISTOŚCI, a nie tylko sama
|
||||||
|
sobie. Pomijana przy uruchomieniu z roota — czyli w CI."""
|
||||||
|
d = tmp_path / "stan"
|
||||||
|
d.mkdir()
|
||||||
|
monkeypatch.setenv("ACCOUNTS_FILE", str(d / "accounts.json"))
|
||||||
|
d.chmod(0o000)
|
||||||
try:
|
try:
|
||||||
c = _client(monkeypatch)
|
c = _client(monkeypatch)
|
||||||
r = c.post("/accounts/create", headers=_auth("szef", "tajne-szefa"),
|
r = c.get("/accounts", headers=_auth("szef", "tajne-szefa"))
|
||||||
follow_redirects=False,
|
assert r.status_code == 200
|
||||||
data={"login": "ala", "password": "x", "granted": ["chart"]})
|
assert "Nie mogę odczytać pliku kont" in r.text
|
||||||
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:
|
finally:
|
||||||
d.chmod(0o755)
|
d.chmod(0o755)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user