Files
timmy-config/tests/test_config_overlay.py

137 lines
4.6 KiB
Python

"""Tests for config overlay system."""
import os
import json
import tempfile
import yaml
import pytest
def test_deep_merge_dicts():
"""Deep merge should recursively merge dicts."""
from config_overlay import deep_merge
base = {"a": {"b": 1, "c": 2}, "d": 3}
overlay = {"a": {"b": 10, "e": 5}}
result = deep_merge(base, overlay)
assert result == {"a": {"b": 10, "c": 2, "e": 5}, "d": 3}
def test_deep_merge_lists_replaced():
"""Lists should be replaced, not extended."""
from config_overlay import deep_merge
base = {"items": [1, 2, 3]}
overlay = {"items": [4, 5]}
result = deep_merge(base, overlay)
assert result == {"items": [4, 5]}
def test_deep_merge_scalars_overridden():
"""Scalar values should be overridden by overlay."""
from config_overlay import deep_merge
base = {"name": "base", "count": 10}
overlay = {"name": "override", "count": 20}
result = deep_merge(base, overlay)
assert result == {"name": "override", "count": 20}
def test_deep_merge_none_removes_key():
"""None in overlay should remove the key from base."""
from config_overlay import deep_merge
base = {"a": 1, "b": 2, "c": 3}
overlay = {"b": None}
result = deep_merge(base, overlay)
assert result == {"a": 1, "c": 3}
def test_deep_merge_empty_overlay():
"""Empty overlay should return base unchanged."""
from config_overlay import deep_merge
base = {"a": 1, "b": {"c": 2}}
result = deep_merge(base, {})
assert result == base
def test_load_config_no_env(tmp_path):
"""Load config without overlay should return base."""
from config_overlay import load_config
base = {"model": "test", "agent": {"max_turns": 10}}
path = tmp_path / "config.yaml"
path.write_text(yaml.dump(base))
result = load_config(str(path))
assert result == base
def test_load_config_with_overlay(tmp_path):
"""Load config with overlay should merge."""
from config_overlay import load_config
base = {"model": "base", "agent": {"max_turns": 10, "verbose": False}}
overlay = {"model": "override", "agent": {"verbose": True}}
(tmp_path / "config.yaml").write_text(yaml.dump(base))
(tmp_path / "config.dev.yaml").write_text(yaml.dump(overlay))
result = load_config(str(tmp_path / "config.yaml"), env="dev")
assert result["model"] == "override"
assert result["agent"]["max_turns"] == 10
assert result["agent"]["verbose"] is True
def test_load_config_missing_overlay(tmp_path):
"""Missing overlay should silently return base."""
from config_overlay import load_config
base = {"model": "base"}
(tmp_path / "config.yaml").write_text(yaml.dump(base))
result = load_config(str(tmp_path / "config.yaml"), env="nonexistent")
assert result == base
def test_find_config(tmp_path):
"""find_config should locate base and overlay."""
from config_overlay import find_config
base = tmp_path / "config.yaml"
base.write_text("a: 1")
overlay = tmp_path / "config.prod.yaml"
overlay.write_text("a: 2")
b, o = find_config(str(base), "prod")
assert b == base
assert o == overlay
def test_list_overlays(tmp_path):
"""list_overlays should find all overlay files."""
from config_overlay import list_overlays
(tmp_path / "config.yaml").write_text("a: 1")
(tmp_path / "config.dev.yaml").write_text("a: 2")
(tmp_path / "config.prod.yaml").write_text("a: 3")
overlays = list_overlays(str(tmp_path / "config.yaml"))
envs = [o['env'] for o in overlays]
assert 'dev' in envs
assert 'prod' in envs
def test_detect_env_from_var(tmp_path, monkeypatch):
"""detect_env should check TIMMY_ENV first."""
from config_overlay import detect_env
monkeypatch.setenv("TIMMY_ENV", "prod")
assert detect_env() == "prod"
def test_detect_env_fallback(tmp_path, monkeypatch):
"""detect_env should fall back through vars."""
from config_overlay import detect_env
monkeypatch.delenv("TIMMY_ENV", raising=False)
monkeypatch.delenv("HERMES_ENV", raising=False)
monkeypatch.setenv("ENVIRONMENT", "cron")
assert detect_env() == "cron"
def test_real_config_overlay():
"""Test against actual config files in the repo."""
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from config_overlay import load_config
base_path = os.path.join(os.path.dirname(__file__), '..', 'config.yaml')
if os.path.exists(base_path):
config = load_config(base_path, env='dev')
assert config['model']['default'] == 'qwen3:30b' # dev overrides
assert config['agent']['max_turns'] == 50 # dev overrides
assert 'terminal' in config # base keys preserved