#!/usr/bin/env python3 """Tests for config_template.py — issue #696""" import os import sys import tempfile import pytest from pathlib import Path # Add parent dir for import sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts")) from config_template import ConfigTemplate, _deep_merge, _get_dotted, _diff_dicts, _validate_config @pytest.fixture def tmp_config_dir(tmp_path): """Create a temp config dir with base + overlay files.""" import yaml base = { "model": {"name": "base-model", "temperature": 0.7}, "cron": {"enabled": False, "interval": 300}, "display": {"colors": True}, } overlay = { "model": {"temperature": 0.9}, "cron": {"enabled": True}, "logging": {"level": "DEBUG"}, } with open(tmp_path / "base.yaml", "w") as f: yaml.dump(base, f) with open(tmp_path / "test.overlay.yaml", "w") as f: yaml.dump(overlay, f) return tmp_path class TestDeepMerge: def test_overlay_wins(self): base = {"a": 1, "b": 2} overlay = {"b": 99} result = _deep_merge(base, overlay) assert result == {"a": 1, "b": 99} def test_deep_merge_nested(self): base = {"model": {"name": "x", "temp": 0.7}} overlay = {"model": {"temp": 0.9}} result = _deep_merge(base, overlay) assert result["model"]["name"] == "x" assert result["model"]["temp"] == 0.9 def test_new_keys_added(self): base = {"a": 1} overlay = {"b": 2} result = _deep_merge(base, overlay) assert result == {"a": 1, "b": 2} def test_originals_unchanged(self): base = {"a": {"inner": 1}} overlay = {"a": {"inner": 99}} _deep_merge(base, overlay) assert base["a"]["inner"] == 1 class TestDottedAccess: def test_simple_key(self): assert _get_dotted({"a": 1}, "a") == 1 def test_nested_key(self): assert _get_dotted({"a": {"b": {"c": 42}}}, "a.b.c") == 42 def test_missing_key_returns_default(self): assert _get_dotted({"a": 1}, "x", "fallback") == "fallback" def test_partial_path(self): assert _get_dotted({"a": 1}, "a.b.c", None) is None class TestDiff: def test_no_diff(self): assert _diff_dicts({"a": 1}, {"a": 1}) == [] def test_changed_value(self): diffs = _diff_dicts({"a": 1}, {"a": 2}) assert len(diffs) == 1 assert diffs[0]["type"] == "changed" def test_added_key(self): diffs = _diff_dicts({"a": 1}, {"a": 1, "b": 2}) added = [d for d in diffs if d["type"] == "added_in_overlay"] assert len(added) == 1 assert added[0]["key"] == "b" class TestValidation: def test_valid_config(self): config = {"model": {"name": "x"}, "provider": {"name": "y"}} assert _validate_config(config) == [] def test_missing_model(self): warnings = _validate_config({"provider": {}}) assert any("model" in w for w in warnings) class TestConfigTemplate: def test_load(self, tmp_config_dir): t = ConfigTemplate(str(tmp_config_dir)) merged = t.load("test") assert merged["model"]["name"] == "base-model" assert merged["model"]["temperature"] == 0.9 assert merged["cron"]["enabled"] is True assert merged["logging"]["level"] == "DEBUG" def test_get_dotted(self, tmp_config_dir): t = ConfigTemplate(str(tmp_config_dir)) t.load("test") assert t.get("model.temperature") == 0.9 assert t.get("nonexistent", "default") == "default" def test_diff(self, tmp_config_dir): t = ConfigTemplate(str(tmp_config_dir)) t.load("test") diffs = t.diff() assert len(diffs) > 0 def test_unknown_env_raises(self, tmp_config_dir): t = ConfigTemplate(str(tmp_config_dir)) with pytest.raises(ValueError, match="Unknown environment"): t.load("nonexistent") def test_list_environments(self): envs = ConfigTemplate.list_environments() assert "dev" in envs assert "prod" in envs assert "cron" in envs assert "gateway" in envs