"""Comprehensive tests for src/config.py.""" import json import pytest from pathlib import Path from src.config import Config, load_config @pytest.fixture def tmp_config(tmp_path): """Create a temporary config file and return its path.""" data = { "bot": { "name": "TestBot", "default_model": "sonnet", "owner": None, "admins": [] }, "channels": {}, "heartbeat": { "enabled": True, "interval_minutes": 30 }, "ollama": { "url": "http://localhost:11434" }, "paths": { "personality": "personality/", "tools": "tools/", "memory": "memory/" } } config_file = tmp_path / "config.json" config_file.write_text(json.dumps(data, indent=2)) return config_file class TestConfigLoad: def test_load_from_path(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("bot.name") == "TestBot" def test_load_config_convenience(self, tmp_config): cfg = load_config(tmp_config) assert cfg.get("bot.name") == "TestBot" def test_load_missing_file_raises(self, tmp_path): with pytest.raises(FileNotFoundError): Config(tmp_path / "nonexistent.json") def test_load_invalid_json_raises(self, tmp_path): bad = tmp_path / "bad.json" bad.write_text("not json {{{") with pytest.raises(json.JSONDecodeError): Config(bad) class TestConfigGet: def test_top_level_key(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("channels") == {} def test_nested_key(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("bot.default_model") == "sonnet" def test_deeply_nested(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("heartbeat.interval_minutes") == 30 def test_missing_key_returns_none(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("nonexistent") is None def test_missing_key_returns_default(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("nonexistent", "fallback") == "fallback" def test_missing_nested_key_returns_default(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("bot.nonexistent.deep", 42) == 42 def test_null_value_returned_as_none(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("bot.owner") is None def test_list_value(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("bot.admins") == [] def test_bool_value(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("heartbeat.enabled") is True class TestConfigSet: def test_set_existing_key(self, tmp_config): cfg = Config(tmp_config) cfg.set("bot.name", "NewName") assert cfg.get("bot.name") == "NewName" def test_set_new_nested_key(self, tmp_config): cfg = Config(tmp_config) cfg.set("new.nested.key", 42) assert cfg.get("new.nested.key") == 42 def test_set_top_level_key(self, tmp_config): cfg = Config(tmp_config) cfg.set("debug", True) assert cfg.get("debug") is True def test_set_overwrites_non_dict(self, tmp_config): cfg = Config(tmp_config) cfg.set("bot.name.sub", "value") assert cfg.get("bot.name.sub") == "value" class TestConfigSave: def test_save_persists_changes(self, tmp_config): cfg = Config(tmp_config) cfg.set("bot.name", "Saved") cfg.save() cfg2 = Config(tmp_config) assert cfg2.get("bot.name") == "Saved" def test_save_preserves_structure(self, tmp_config): cfg = Config(tmp_config) cfg.set("new_key", "new_value") cfg.save() raw = json.loads(tmp_config.read_text()) assert raw["bot"]["name"] == "TestBot" assert raw["new_key"] == "new_value" class TestConfigReload: def test_reload_picks_up_external_changes(self, tmp_config): cfg = Config(tmp_config) assert cfg.get("bot.name") == "TestBot" data = json.loads(tmp_config.read_text()) data["bot"]["name"] = "ExternalChange" tmp_config.write_text(json.dumps(data)) cfg.reload() assert cfg.get("bot.name") == "ExternalChange" class TestConfigRaw: def test_raw_returns_dict(self, tmp_config): cfg = Config(tmp_config) raw = cfg.raw() assert isinstance(raw, dict) assert raw["bot"]["name"] == "TestBot" def test_raw_is_reference(self, tmp_config): cfg = Config(tmp_config) raw = cfg.raw() raw["bot"]["name"] = "Mutated" assert cfg.get("bot.name") == "Mutated"