fix(dane): bazy zastane na udziale zostają w użyciu po przejściu na rejestr
build / build (push) Successful in 7s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m35s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 4m52s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 7s
Testy / Kontrola składni wszystkich warstw (push) Successful in 5s
build / build (push) Successful in 7s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m35s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 4m52s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 7s
Testy / Kontrola składni wszystkich warstw (push) Successful in 5s
Bez tego wdrożenie DAN-27 WYŁĄCZYŁOBY WYSZUKIWANIE. Dotąd bazy działały domyślnie (wyłączało się je jawnie przez DISABLED_BASES). Po przejściu na rejestr plik bez wpisu w stanie dostaje `ready`, czyli NIE w użyciu — a stan po wdrożeniu jest pusty. Efekt: żadna baza nie jest aktywna i program nagle niczego nie znajduje. Ta cicha zmiana zachowania byłaby gorsza od awarii, bo wygląda jak pusta baza, a nie jak zepsuty deploy — i szukałoby się jej w warstwie danych albo w indeksie. Teraz brak PLIKU stanu oznacza pierwsze uruchomienie i bazy zastane są przyjmowane jako aktywne. Pusty słownik przy ISTNIEJĄCYM pliku to co innego: ktoś świadomie wszystko odstawił, więc nie wskrzeszamy — osobny test tego pilnuje. Rozróżnienie jest celowe i też pod testem: `ready` dotyczy plików WGRANYCH przez ekran (te ktoś musi świadomie włączyć), a nie zastanych przy przejściu na rejestr. Inaczej nowa baza wchodziłaby do wyników sama, bez niczyjej decyzji. Przyjęcie działa też na udziale tylko do odczytu: zapis stanu wtedy nie przechodzi, więc powtórzy się przy każdym uruchomieniu — zachowanie to samo, koszt żaden. Dwa testy opisujące STARE zachowanie zostały poprawione, bo to one były błędne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit was merged in pull request #69.
This commit is contained in:
@@ -202,6 +202,30 @@ def _scan(root: Path) -> list[Path]:
|
|||||||
if p.is_file() and not p.name.startswith((".", "~$"))]
|
if p.is_file() and not p.name.startswith((".", "~$"))]
|
||||||
|
|
||||||
|
|
||||||
|
def _adopt_existing(root: Path) -> dict:
|
||||||
|
"""Pierwsze uruchomienie: bazy zastane na udziale są OD RAZU w użyciu.
|
||||||
|
|
||||||
|
Bez tego wdrożenie DAN-27 wyłączyłoby wyszukiwanie. Dotąd bazy działały
|
||||||
|
domyślnie (wyłączało się je jawnie przez DISABLED_BASES); po przejściu na
|
||||||
|
rejestr plik bez wpisu dostaje `ready`, czyli NIE w użyciu — więc pusty stan
|
||||||
|
po wdrożeniu oznaczałby, że program nagle niczego nie znajduje. Ta cicha
|
||||||
|
zmiana zachowania byłaby gorsza od awarii, bo wygląda jak pusta baza.
|
||||||
|
|
||||||
|
Rozróżnienie jest celowe: `ready` dotyczy plików WGRANYCH przez ekran (te
|
||||||
|
ktoś musi świadomie włączyć), a nie zastanych przy przejściu na rejestr.
|
||||||
|
|
||||||
|
Zapis stanu może się nie udać (udział read-only) — wtedy trudno, przy każdym
|
||||||
|
uruchomieniu przyjmiemy je na nowo. Zachowanie jest to samo, koszt żaden."""
|
||||||
|
files = {str(p.relative_to(root)): {"status": ACTIVE, "adopted_at": _now()}
|
||||||
|
for p in _scan(root)}
|
||||||
|
data = {"files": files, "rules": {**DEFAULT_RULES}}
|
||||||
|
try:
|
||||||
|
_write_state(root, data)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def registry(root: Path | str, *, for_admin: bool = False) -> list[dict]:
|
def registry(root: Path | str, *, for_admin: bool = False) -> list[dict]:
|
||||||
"""Pliki na udziale wraz ze stanem. `for_admin` odsłania kwarantannę i powody.
|
"""Pliki na udziale wraz ze stanem. `for_admin` odsłania kwarantannę i powody.
|
||||||
|
|
||||||
@@ -209,7 +233,9 @@ def registry(root: Path | str, *, for_admin: bool = False) -> list[dict]:
|
|||||||
dochodziły do przeglądarki i były tylko ukrywane stylem, wystarczyłby podgląd
|
dochodziły do przeglądarki i były tylko ukrywane stylem, wystarczyłby podgląd
|
||||||
źródła strony, żeby poznać reguły walidacji."""
|
źródła strony, żeby poznać reguły walidacji."""
|
||||||
root = Path(root)
|
root = Path(root)
|
||||||
data = _read_state(root)
|
# Brak PLIKU stanu = pierwsze uruchomienie. Pusty słownik przy istniejącym
|
||||||
|
# pliku to co innego: ktoś świadomie wszystko odstawił, więc nie wskrzeszamy.
|
||||||
|
data = _read_state(root) if state_path(root).exists() else _adopt_existing(root)
|
||||||
out: list[dict] = []
|
out: list[dict] = []
|
||||||
for p in _scan(root):
|
for p in _scan(root):
|
||||||
rel = str(p.relative_to(root))
|
rel = str(p.relative_to(root))
|
||||||
|
|||||||
@@ -32,19 +32,24 @@ def _xlsx(path, rows=3, header=("id", "opis")):
|
|||||||
|
|
||||||
# ── rejestr i stany ──────────────────────────────────────────────────────
|
# ── rejestr i stany ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
def test_new_file_is_visible_but_not_in_use(root):
|
def test_a_file_dropped_on_the_share_is_adopted_as_active(root):
|
||||||
|
"""Baza położona na udziale poza aplikacją (np. przez NFS) ma działać —
|
||||||
|
tak było przed DAN-27 i tak ma zostać. Plik WGRANY EKRANEM to inna sprawa:
|
||||||
|
ten wymaga świadomego włączenia (patrz test niżej)."""
|
||||||
_xlsx(root / "baza.xlsx")
|
_xlsx(root / "baza.xlsx")
|
||||||
entry = files.registry(root)[0]
|
entry = files.registry(root)[0]
|
||||||
assert entry["status"] == files.READY
|
assert entry["status"] == files.ACTIVE
|
||||||
assert entry["in_use"] is False, "nowy plik nie może sam wejść do wyszukiwania"
|
assert entry["in_use"] is True
|
||||||
|
|
||||||
|
|
||||||
def test_only_active_files_reach_the_search(root):
|
def test_only_active_files_reach_the_search(root):
|
||||||
_xlsx(root / "a.xlsx")
|
_xlsx(root / "a.xlsx")
|
||||||
_xlsx(root / "b.xlsx")
|
_xlsx(root / "b.xlsx")
|
||||||
assert files.usable_paths(root) == []
|
files.registry(root) # przyjęcie zastanych
|
||||||
files.set_status(root, "a.xlsx", files.ACTIVE)
|
files.set_status(root, "b.xlsx", files.READY) # świadome odstawienie
|
||||||
assert [pathlib.Path(p).name for p in files.usable_paths(root)] == ["a.xlsx"]
|
assert [pathlib.Path(p).name for p in files.usable_paths(root)] == ["a.xlsx"]
|
||||||
|
files.set_status(root, "b.xlsx", files.ACTIVE)
|
||||||
|
assert len(files.usable_paths(root)) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_state_survives_a_restart(root):
|
def test_state_survives_a_restart(root):
|
||||||
@@ -160,3 +165,50 @@ def test_delete_removes_the_file_and_its_entry(root):
|
|||||||
assert not (root / "a.xlsx").exists()
|
assert not (root / "a.xlsx").exists()
|
||||||
assert files.registry(root, for_admin=True) == []
|
assert files.registry(root, for_admin=True) == []
|
||||||
assert files.usable_paths(root) == []
|
assert files.usable_paths(root) == []
|
||||||
|
|
||||||
|
|
||||||
|
# ── przejście na rejestr nie może wyłączyć wyszukiwania ─────────────────
|
||||||
|
|
||||||
|
def test_bases_already_on_the_share_stay_in_use_after_the_switch(root):
|
||||||
|
"""Dotąd bazy działały domyślnie (wyłączało się je przez DISABLED_BASES).
|
||||||
|
Po przejściu na rejestr pusty stan oznaczałby, że program nagle niczego nie
|
||||||
|
znajduje — cicha zmiana zachowania gorsza od awarii, bo wygląda jak pusta baza."""
|
||||||
|
_xlsx(root / "main_base.xlsx")
|
||||||
|
_xlsx(root / "zodiac_pl.xlsx")
|
||||||
|
assert len(files.usable_paths(root)) == 2, "zastane bazy wypadły z wyszukiwania"
|
||||||
|
assert all(e["in_use"] for e in files.registry(root))
|
||||||
|
|
||||||
|
|
||||||
|
def test_adoption_happens_once_and_respects_later_decisions(root):
|
||||||
|
"""Po przyjęciu stan jest zapisany, więc świadome odstawienie bazy ZOSTAJE —
|
||||||
|
kolejny odczyt nie może jej wskrzesić."""
|
||||||
|
_xlsx(root / "a.xlsx")
|
||||||
|
_xlsx(root / "b.xlsx")
|
||||||
|
files.registry(root) # przyjęcie
|
||||||
|
files.set_status(root, "a.xlsx", files.READY) # świadome odstawienie
|
||||||
|
assert [pathlib.Path(p).name for p in files.usable_paths(root)] == ["b.xlsx"]
|
||||||
|
files.set_status(root, "b.xlsx", files.READY) # odstawiamy wszystko
|
||||||
|
assert files.usable_paths(root) == [], "pusty wybór został wskrzeszony"
|
||||||
|
|
||||||
|
|
||||||
|
def test_uploaded_files_still_need_an_explicit_switch_on(root):
|
||||||
|
"""Przyjęcie dotyczy TYLKO baz zastanych. Plik wgrany ekranem ktoś musi
|
||||||
|
świadomie włączyć — inaczej nowa baza wchodziłaby do wyników sama."""
|
||||||
|
_xlsx(root / "zastana.xlsx")
|
||||||
|
files.registry(root)
|
||||||
|
out = files.store_upload(root, "nowa.xlsx", _xlsx(root / "tmp.xlsx").read_bytes())
|
||||||
|
assert out["accepted"] is True
|
||||||
|
names = [pathlib.Path(p).name for p in files.usable_paths(root)]
|
||||||
|
assert "nowa.xlsx" not in names, "wgrana baza weszła do wyników bez decyzji"
|
||||||
|
|
||||||
|
|
||||||
|
def test_adoption_survives_a_read_only_share(root, monkeypatch):
|
||||||
|
"""Na udziale tylko do odczytu stanu nie da się zapisać — zachowanie ma
|
||||||
|
zostać to samo, tylko przyjęcie powtórzy się przy każdym uruchomieniu."""
|
||||||
|
_xlsx(root / "a.xlsx")
|
||||||
|
|
||||||
|
def boom(*a, **kw):
|
||||||
|
raise OSError("read-only file system")
|
||||||
|
|
||||||
|
monkeypatch.setattr(files, "_write_state", boom)
|
||||||
|
assert len(files.usable_paths(root)) == 1
|
||||||
|
|||||||
Reference in New Issue
Block a user