"""Tests for configuration loading and merging.""" import copy from pathlib import Path import pytest from derp.config import DEFAULTS, _merge, load, resolve_config class TestMerge: """Test deep merge logic.""" def test_flat_override(self): base = {"a": 1, "b": 2} result = _merge(base, {"b": 99}) assert result == {"a": 1, "b": 99} def test_nested_merge(self): base = {"server": {"host": "a", "port": 1}} result = _merge(base, {"server": {"port": 2}}) assert result == {"server": {"host": "a", "port": 2}} def test_list_replacement(self): base = {"channels": ["#old"]} result = _merge(base, {"channels": ["#new"]}) assert result == {"channels": ["#new"]} def test_empty_override(self): base = {"a": 1, "b": 2} result = _merge(base, {}) assert result == {"a": 1, "b": 2} def test_new_keys(self): base = {"a": 1} result = _merge(base, {"b": 2}) assert result == {"a": 1, "b": 2} def test_base_not_mutated(self): base = {"server": {"host": "original"}} original = copy.deepcopy(base) _merge(base, {"server": {"host": "changed"}}) assert base == original class TestLoad: """Test TOML file loading.""" def test_minimal_toml(self, tmp_path: Path): config_file = tmp_path / "test.toml" config_file.write_text('[server]\nnick = "testbot"\n') result = load(config_file) # Overridden value assert result["server"]["nick"] == "testbot" # Default preserved assert result["server"]["host"] == "irc.libera.chat" assert result["server"]["port"] == 6697 def test_full_override(self, tmp_path: Path): config_file = tmp_path / "test.toml" config_file.write_text( '[server]\nhost = "custom.irc"\nport = 6667\n' '[bot]\nprefix = "."\n' ) result = load(config_file) assert result["server"]["host"] == "custom.irc" assert result["server"]["port"] == 6667 assert result["bot"]["prefix"] == "." # Unset defaults still present assert result["bot"]["channels"] == ["#test"] class TestChannelConfig: """Test channel-level configuration merging.""" def test_channel_config_loaded(self, tmp_path: Path): config_file = tmp_path / "test.toml" config_file.write_text( '[channels."#ops"]\nplugins = ["core", "dns"]\n' ) result = load(config_file) assert "#ops" in result["channels"] assert result["channels"]["#ops"]["plugins"] == ["core", "dns"] def test_default_channels_empty(self): assert DEFAULTS["channels"] == {} def test_default_logging_format(self): assert DEFAULTS["logging"]["format"] == "text" class TestResolveConfig: """Test config path resolution and fallback.""" def test_explicit_path(self, tmp_path: Path): config_file = tmp_path / "explicit.toml" config_file.write_text('[server]\nnick = "explicit"\n') result = resolve_config(str(config_file)) assert result["server"]["nick"] == "explicit" def test_no_file_fallback(self, tmp_path: Path, monkeypatch: "pytest.MonkeyPatch"): # Run from tmp_path so config/derp.toml isn't found monkeypatch.chdir(tmp_path) result = resolve_config(str(tmp_path / "nonexistent.toml")) # Should fall back to DEFAULTS assert result["server"]["host"] == DEFAULTS["server"]["host"] assert result["bot"]["prefix"] == DEFAULTS["bot"]["prefix"] def test_defaults_not_mutated(self): original = copy.deepcopy(DEFAULTS) resolve_config(None) assert DEFAULTS == original