"""Group access to the observation store — regression coverage for issue #54. Two defects: (1) a non-group user's stat() on the store directory raised PermissionError straight through open_store_readonly(), crashing status/TUI instead of degrading to the Store fault view; (2) even group members could not open the WAL-mode store because root-created sidecars lacked group write and the store directory lacked group execute-then-write. """ import sqlite3 import sys from pathlib import Path import pytest sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from fenris.store import DEFAULT_STORE_PATH, init_store from fenris.status import StoreFault, open_store_readonly def test_stat_permission_error_becomes_store_fault(monkeypatch, tmp_path): """stat() denied (non-group user on a 2750 dir) → StoreFault, not crash.""" store = tmp_path / "observations.db" store.write_bytes(b"") import pathlib def denied(self, follow_symlinks=True): raise PermissionError(13, "Permission denied") monkeypatch.setattr(pathlib.Path, "exists", denied) with pytest.raises(StoreFault): open_store_readonly(store) def test_connect_failure_becomes_store_fault(tmp_path): """sqlite failures stay wrapped as StoreFault (existing contract).""" garbage = tmp_path / "observations.db" garbage.write_bytes(b"not a database" * 100) with pytest.raises(StoreFault): open_store_readonly(garbage) def test_init_store_leaves_files_group_writable(tmp_path): """Root-created stores must stay readable by WAL readers: db and sidecars need group write after init_store (issue #54).""" store = tmp_path / "observations.db" conn = init_store(store) try: assert (store.stat().st_mode & 0o060) == 0o060, "db not group rw" wal = store.with_name(store.name + "-wal") shm = store.with_name(store.name + "-shm") if wal.exists(): assert (wal.stat().st_mode & 0o060) == 0o060, "wal not group rw" if shm.exists(): assert (shm.stat().st_mode & 0o060) == 0o060, "shm not group rw" finally: conn.close() def test_readonly_open_works_after_init_store(tmp_path): """The shipped read path opens a store created by init_store.""" store = tmp_path / "observations.db" writer = init_store(store) writer.execute("INSERT INTO monitoring_periods (started_at) VALUES ('2026-01-01T00:00:00+00:00')") writer.commit() conn = open_store_readonly(store) assert conn is not None conn.close() writer.close() def test_packaging_ships_group_access(): """tmpfiles must create the store dir group-writable; collect unit must keep the umask loose so root-created sidecars stay group-accessible.""" repo = Path(__file__).resolve().parent.parent assert "2770" in (repo / "packaging" / "tmpfiles.d" / "fenris.conf").read_text() assert "2750" not in (repo / "packaging" / "tmpfiles.d" / "fenris.conf").read_text() assert "UMask=002" in (repo / "units" / "fenris-collect.service").read_text()