"""Config loading: toml knobs, env-only username, unknown-key warning.""" from __future__ import annotations import os from pathlib import Path import pytest from bggpipe.config import Config, load_config def test_defaults_when_no_file(tmp_path, monkeypatch): # no config.toml in cwd and no explicit path -> defaults (an EXPLICIT # missing path errors instead; see the dedicated test) monkeypatch.delenv("BGG_USERNAME", raising=False) monkeypatch.chdir(tmp_path) cfg = load_config() assert cfg == Config() assert cfg.cache_dir == Path("data/bgg_cache") def test_reads_toml(tmp_path, monkeypatch): monkeypatch.delenv("BGG_USERNAME", raising=False) p = tmp_path / "config.toml" p.write_text('data_dir = "elsewhere"\nrate_limit_seconds = 3\n') cfg = load_config(p) assert cfg.data_dir == Path("elsewhere") assert cfg.rate_limit_seconds == 3.0 assert cfg.matches_path == Path("elsewhere/matches.csv") def test_username_comes_from_env_only(tmp_path, monkeypatch): # the account has exactly one home: BGG_USERNAME in the environment — # a bgg_username key in config.toml is deliberately ignored p = tmp_path / "config.toml" p.write_text('bgg_username = "from_toml"\n') monkeypatch.setenv("BGG_USERNAME", "from_env") assert load_config(p).bgg_username == "from_env" monkeypatch.delenv("BGG_USERNAME") assert load_config(p).bgg_username == "" def test_unknown_toml_keys_warn(tmp_path, monkeypatch): # a typo'd knob must not silently fall back to defaults monkeypatch.delenv("BGG_USERNAME", raising=False) p = tmp_path / "config.toml" p.write_text('photo_dir = "oops"\n') with pytest.warns(UserWarning, match="photo_dir"): load_config(p) def test_explicit_missing_config_errors_instead_of_silent_defaults(tmp_path): with pytest.raises(FileNotFoundError, match="does not exist"): load_config(tmp_path / "nope.toml") def test_vision_blocks_apply_only_for_the_active_provider(tmp_path): p = tmp_path / "config.toml" p.write_text( """ vision_provider = "openai-compatible" [vision.anthropic] model = "claude-sonnet-5" [vision."openai-compatible"] base_url = "http://localhost:11434/v1" model = "qwen2.5vl:7b" key_env = "" """ ) cfg = load_config(p) assert cfg.model == "qwen2.5vl:7b" # the active block's model wins assert cfg.vision_base_url == "http://localhost:11434/v1" assert cfg.vision_key_env == "" # flip the provider: the other block applies, this one is inert p.write_text(p.read_text().replace('"openai-compatible"\n', '"anthropic"\n', 1)) cfg = load_config(p) assert cfg.model == "claude-sonnet-5" assert cfg.vision_base_url == "" # untouched default def test_vision_block_typos_warn(tmp_path): p = tmp_path / "config.toml" p.write_text( """ [vision.anthropic] modle = "oops" [vision.anthorpic] model = "claude-sonnet-5" """ ) with ( pytest.warns(UserWarning, match="modle"), pytest.warns(UserWarning, match="anthorpic"), ): load_config(p) def _git(tmp_path, *args): import subprocess subprocess.run( ["git", *args], cwd=tmp_path, check=True, capture_output=True, env={ "PATH": os.environ["PATH"], "HOME": str(tmp_path), "GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t", "GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t", }, ) def test_tracked_data_warning_fires_only_inside_a_repo(tmp_path): """The clone-and-run trap: data at git-tracked paths is one panicked `git reset --hard` from destruction. Outside a repo, silence.""" data = tmp_path / "data" data.mkdir() (data / "matches.csv").write_text("x") cfg = Config(data_dir=data) assert cfg.tracked_data_warning() is None # no repo, no trap _git(tmp_path, "init") assert cfg.tracked_data_warning() is None # repo, but nothing tracked _git(tmp_path, "add", "data/matches.csv") _git(tmp_path, "commit", "-m", "seed") warning = cfg.tracked_data_warning() assert warning and "git-TRACKED" in warning and ".own_repo" in warning # the owner's exemption is a gitignored marker: a fresh clone of this # repo would NOT carry it, so only the author's checkout is silenced (data / ".own_repo").touch() assert cfg.tracked_data_warning() is None