"""Tests for the settings.local.toml overlay loader. The overlay is the dev-side path for keeping host-specific secrets (notably `[database].url`) out of the tracked `settings.toml` without forcing every interactive shell to export env vars. The footgun this test suite guards against: a one-key local override (e.g. a single `[database] url = "..."`) silently wiping every other key in that section. The merge is section-level, not shallow. """ from __future__ import annotations from pathlib import Path import pytest # --------------------------------------------------------------------------- # Pure-Python: _overlay_sections # --------------------------------------------------------------------------- def test_overlay_preserves_sibling_keys_in_overlapping_section(): """The exact regression target — local `url` must not wipe `pool_size`.""" from second_brain.config import _overlay_sections base = {"database": {"url": "", "pool_size": 5}} local = {"database": {"url": "postgresql://x"}} out = _overlay_sections(base, local) assert out["database"]["url"] == "postgresql://x" assert out["database"]["pool_size"] == 5 def test_overlay_section_only_in_base_survives(): from second_brain.config import _overlay_sections base = {"database": {"url": "a"}, "scheduler": {"interval": 30}} local = {"database": {"url": "b"}} out = _overlay_sections(base, local) assert out["scheduler"] == {"interval": 30} assert out["database"]["url"] == "b" def test_overlay_section_only_in_local_appears(): from second_brain.config import _overlay_sections base = {"database": {"url": "a"}} local = {"embeddings": {"ollama_url": "http://x"}} out = _overlay_sections(base, local) assert out["database"] == {"url": "a"} assert out["embeddings"] == {"ollama_url": "http://x"} def test_overlay_scalar_in_overlay_replaces_dict_in_base(): """Non-dict overlay values replace outright — no merge attempt.""" from second_brain.config import _overlay_sections base = {"feature": {"a": 1, "b": 2}} local = {"feature": "off"} # scalar wins outright out = _overlay_sections(base, local) assert out["feature"] == "off" def test_overlay_dict_in_overlay_replaces_scalar_in_base(): from second_brain.config import _overlay_sections base = {"feature": "off"} local = {"feature": {"a": 1}} out = _overlay_sections(base, local) assert out["feature"] == {"a": 1} def test_overlay_empty_overlay_is_identity(): from second_brain.config import _overlay_sections base = {"database": {"url": "a", "pool_size": 5}} assert _overlay_sections(base, {}) == base assert _overlay_sections(base, None) == base def test_overlay_only_merges_one_level(): """Doc: the overlay is section-level. Nested dicts inside a section replace, not deep-merge. This pins the documented depth.""" from second_brain.config import _overlay_sections base = {"db": {"url": "a", "pool": {"min": 1, "max": 10}}} local = {"db": {"pool": {"max": 20}}} out = _overlay_sections(base, local) # pool replaced wholesale — `min` does NOT survive. This is deliberate. assert out["db"]["pool"] == {"max": 20} assert out["db"]["url"] == "a" # --------------------------------------------------------------------------- # load_config: end-to-end with real TOML files on disk # --------------------------------------------------------------------------- def _write(path: Path, body: str) -> None: path.write_text(body, encoding="utf-8") @pytest.fixture(autouse=True) def _reset_config(): """Drop the cached singleton before/after each test so overlay state doesn't leak between cases.""" from second_brain.config import reset_config_singleton reset_config_singleton() yield reset_config_singleton() def test_load_config_picks_up_sibling_local_overlay(tmp_path): from second_brain.config import load_config base = tmp_path / "settings.toml" local = tmp_path / "settings.local.toml" _write( base, '[database]\nurl = ""\n\n[embeddings]\nollama_url = "http://ollama:11434"\nmodel = "nomic-embed-text"\n', ) _write( local, '[database]\nurl = "postgresql+psycopg://lovebug:x@127.0.0.1:5433/petalbrain"\n\n[embeddings]\nollama_url = "http://127.0.0.1:11434"\n', ) cfg = load_config(base) # url from local wins; default model survives (not in local). assert cfg.database["url"].startswith("postgresql+psycopg://") assert cfg.embeddings["ollama_url"] == "http://127.0.0.1:11434" assert cfg.embeddings["model"] == "nomic-embed-text" def test_load_config_no_local_overlay_is_inert(tmp_path): from second_brain.config import load_config base = tmp_path / "settings.toml" _write(base, '[database]\nurl = "from-base"\n') cfg = load_config(base) assert cfg.database["url"] == "from-base" def test_load_config_local_overlay_at_explicit_path(tmp_path): """Doc: when an explicit (or SECOND_BRAIN_CONFIG) path is used, the overlay sibling is resolved next to THAT path — not next to the repo's config dir.""" from second_brain.config import load_config subdir = tmp_path / "etc" subdir.mkdir() base = subdir / "settings.toml" local = subdir / "settings.local.toml" _write(base, '[database]\nurl = "base"\n') _write(local, '[database]\nurl = "overlay-wins"\n') cfg = load_config(base) assert cfg.database["url"] == "overlay-wins"