Structure, config loader, personality/tools/memory from clawd, venv, 22 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
162 lines
4.7 KiB
Python
162 lines
4.7 KiB
Python
"""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"
|