Files
bggpipe/tests/test_config.py
T
Eric WagonerandClaude Fable 5 118503e4e0 Per-provider vision blocks; parser repairs local models' almost-JSON
config.toml now carries a [vision.<provider>] block per backend —
model/base_url/key_env — with vision_provider picking the active one,
so the committed file documents every recipe and switching is a
one-line flip. Only the active block applies; typo'd block names and
keys warn like every other config mistake.

First real Ollama run (qwen2.5vl:7b) surfaced what local models emit:
almost-JSON with trailing commas. parse_vision_response now makes one
cheap repair pass before declaring a response unusable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-03 18:39:27 -04:00

99 lines
3.0 KiB
Python

"""Config loading: toml knobs, env-only username, unknown-key warning."""
from __future__ import annotations
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)