mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-06 03:20:49 +02:00
Clear every read-only marker when the store becomes writable again
"Erneut prüfen" refreshed the banner but left the window title and the status bar reading "NUR LESEN" -- both were built once and never updated -- while the banner itself asked for a restart that the recheck exists to avoid. Title, status bar and banner are refreshed from one place now, so none of them can be left behind, and the main window owns the title instead of app.py setting it once at startup. The banner points at "Erneut prüfen" and says outright that no restart is needed. Since the startup pass was skipped, its task list is empty rather than current, so a successful recheck offers the housekeeper run that fills it. UI tests cover both directions of the recheck; their Tk root moved into a shared conftest fixture, because a second root in another module invalidates the icon images bound to the first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ae53c168fd
commit
528c24ad42
@@ -0,0 +1,105 @@
|
||||
import os
|
||||
import stat
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("tkinter")
|
||||
|
||||
from ccma.config import AppConfig # noqa: E402
|
||||
from ccma.storage.repository import MemberRepository # noqa: E402
|
||||
|
||||
# Root ignores the permission bits that stand in for a read-only mount here.
|
||||
pytestmark = pytest.mark.skipif(
|
||||
hasattr(os, "geteuid") and os.geteuid() == 0, reason="root bypasses file permissions"
|
||||
)
|
||||
|
||||
|
||||
def _set_writable(root, writable: bool) -> None:
|
||||
for path in sorted(root.rglob("*"), reverse=True) + [root]:
|
||||
mode = stat.S_IMODE(path.stat().st_mode)
|
||||
path.chmod(mode | stat.S_IWUSR if writable else mode & ~stat.S_IWUSR)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def read_only_window(tk_root, tmp_path):
|
||||
from ccma.ui.main_window import MainWindow
|
||||
|
||||
root_path = tmp_path / "store"
|
||||
repository = MemberRepository(root_path)
|
||||
repository.initialize()
|
||||
repository.create_member(first_name="Ada", last_name="Lovelace", birth_date="1990-01-01")
|
||||
_set_writable(root_path, False)
|
||||
|
||||
window = MainWindow(tk_root, MemberRepository(root_path), AppConfig(store_path=str(root_path)), [], [])
|
||||
window.pack(fill="both", expand=True)
|
||||
tk_root.update()
|
||||
yield window
|
||||
window.destroy()
|
||||
tk_root.title("")
|
||||
tk_root.update()
|
||||
_set_writable(root_path, True)
|
||||
|
||||
|
||||
def test_a_read_only_store_is_marked_everywhere(read_only_window, tk_root):
|
||||
assert "NUR LESEN" in tk_root.title()
|
||||
assert "NUR LESEN" in read_only_window.store_var.get()
|
||||
assert read_only_window.messages.winfo_manager(), "der Warnbanner fehlt"
|
||||
|
||||
|
||||
def test_the_banner_does_not_ask_for_a_restart(read_only_window):
|
||||
banner = read_only_window.messages.winfo_children()[0]
|
||||
text = " ".join(
|
||||
str(child.cget("text"))
|
||||
for child in banner.winfo_children()[0].winfo_children()
|
||||
if "text" in child.keys()
|
||||
)
|
||||
# The banner used to end with "und CCMA neu starten", which is wrong -- the
|
||||
# session recovers in place.
|
||||
assert "Erneut prüfen" in text
|
||||
assert "neu starten" not in text.casefold()
|
||||
assert "Neustart ist dafür nicht nötig" in text
|
||||
|
||||
|
||||
def test_a_remounted_store_clears_every_read_only_marker(read_only_window, tk_root, monkeypatch):
|
||||
from tkinter import messagebox
|
||||
|
||||
_set_writable(read_only_window.repository.root, True)
|
||||
asked = []
|
||||
monkeypatch.setattr(
|
||||
messagebox, "askyesno", lambda *args, **kwargs: asked.append(args) or False
|
||||
)
|
||||
|
||||
read_only_window._recheck_store_access()
|
||||
tk_root.update()
|
||||
|
||||
assert read_only_window.repository.read_only is False
|
||||
assert "NUR LESEN" not in tk_root.title()
|
||||
assert "NUR LESEN" not in read_only_window.store_var.get()
|
||||
assert not read_only_window.messages.winfo_manager(), "der Warnbanner steht noch"
|
||||
assert read_only_window.status_var.get() == "Der Store ist wieder beschreibbar."
|
||||
assert asked, "der übersprungene Hausmeisterlauf wird nicht angeboten"
|
||||
|
||||
|
||||
def test_the_skipped_housekeeper_run_can_be_started_right_away(
|
||||
read_only_window, tk_root, monkeypatch
|
||||
):
|
||||
from tkinter import messagebox
|
||||
|
||||
_set_writable(read_only_window.repository.root, True)
|
||||
monkeypatch.setattr(messagebox, "askyesno", lambda *args, **kwargs: True)
|
||||
|
||||
read_only_window._recheck_store_access()
|
||||
tk_root.update()
|
||||
|
||||
assert (read_only_window.repository.root / "housekeeper.json").is_file()
|
||||
assert read_only_window.status_var.get().startswith("Hausmeisterlauf beendet")
|
||||
|
||||
|
||||
def test_a_store_that_is_still_read_only_keeps_its_markers(read_only_window, tk_root):
|
||||
read_only_window._recheck_store_access()
|
||||
tk_root.update()
|
||||
|
||||
assert "NUR LESEN" in tk_root.title()
|
||||
assert "NUR LESEN" in read_only_window.store_var.get()
|
||||
assert read_only_window.messages.winfo_manager()
|
||||
assert read_only_window.status_var.get() == "Der Store ist weiterhin schreibgeschützt."
|
||||
Reference in New Issue
Block a user