diff --git a/packaging/fenris.conf b/packaging/fenris.conf index f90a086..41aa44d 100644 --- a/packaging/fenris.conf +++ b/packaging/fenris.conf @@ -8,4 +8,9 @@ # # device = /dev/disk/by-id/nvme-Samsung_SSD_980_PRO_500GB_S5PANS0T123456 # +# The observation store path is optional and defaults to +# /var/lib/fenris/observations.db when unset: +# +# store_path = /var/lib/fenris/observations.db +# # See https://git.bongbetic.com/xavierk/Fenris for documentation. diff --git a/pyproject.toml b/pyproject.toml index 26bdeff..5d834b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "fenris" -version = "0.3.1" +version = "0.3.2" description = "NVMe wear monitor with persistent TUI" requires-python = ">=3.9" dependencies = [ diff --git a/src/fenris/store.py b/src/fenris/store.py index 7721778..01396f1 100644 --- a/src/fenris/store.py +++ b/src/fenris/store.py @@ -15,9 +15,19 @@ from typing import Optional SCHEMA_VERSION = 1 +# Packaged default placement (spec §8.3). The config may override it, but a +# fresh install that sets only the device selector must collect cleanly. +DEFAULT_STORE_PATH = Path("/var/lib/fenris/observations.db") + + def get_store_path(config: dict) -> Path: - """Get the store path from config.""" - return Path(config["store_path"]) + """Get the store path from config. + + Falls back to the packaged default when the config does not pin one, + so a fresh install whose config holds only the device selector works + instead of crashing with KeyError 'store_path' (issue #53). + """ + return Path(config.get("store_path", DEFAULT_STORE_PATH)) def init_store(store_path: Path) -> sqlite3.Connection: diff --git a/tests/test_store_config.py b/tests/test_store_config.py new file mode 100644 index 0000000..cf9459c --- /dev/null +++ b/tests/test_store_config.py @@ -0,0 +1,56 @@ +"""Store path resolution from config — regression coverage for issue #53. + +A fresh install ships a placeholder-commented config whose only required +key is the device selector. The collector must not crash with +KeyError 'store_path' when the key is absent. +""" +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + +from fenris.store import DEFAULT_STORE_PATH, get_store_path + + +REPO_ROOT = Path(__file__).resolve().parent.parent +TEMPLATE = REPO_ROOT / "packaging" / "fenris.conf" + + +def _parse_like_load_config(text: str) -> dict: + """Mirror collect.load_config()'s key=value parsing rules.""" + config = {} + for line in text.splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + if "=" in line: + key, value = line.split("=", 1) + config[key.strip()] = value.strip() + return config + + +def test_missing_store_path_falls_back_to_default(): + """Config with only the device selector resolves to the packaged default.""" + assert get_store_path({"device": "/dev/nvme0n1"}) == DEFAULT_STORE_PATH + + +def test_explicit_store_path_wins(): + """An explicit store_path override is honored.""" + assert get_store_path({"store_path": "/tmp/other.db"}) == Path("/tmp/other.db") + + +def test_packaged_template_yields_collectable_config(): + """The packaged template, once a device is set, must be collector-ready. + + Reproduces the fresh-install path: parse packaging/fenris.conf the way + collect.load_config() does, add the device selector, then resolve the + store. Issue #53 made this raise KeyError. + """ + config = _parse_like_load_config(TEMPLATE.read_text()) + config["device"] = "/dev/nvme0n1" + assert get_store_path(config) == DEFAULT_STORE_PATH + + +def test_template_documents_store_path(): + """The template must mention store_path so admins know it is overridable.""" + assert "store_path" in TEMPLATE.read_text()