Files
impakt/tests/test_config.py
2026-04-11 15:20:27 -04:00

228 lines
7.4 KiB
Python

"""Tests for the layered configuration system."""
from pathlib import Path
import pytest
import yaml
from impakt.config import Config
from impakt.config.model import (
CriteriaConfig,
PlotConfig,
ProtocolConfig,
SessionConfig,
TransformConfig,
WebConfig,
_deep_merge,
)
class TestDeepMerge:
def test_simple_override(self):
base = {"a": 1, "b": 2}
override = {"b": 3, "c": 4}
result = _deep_merge(base, override)
assert result == {"a": 1, "b": 3, "c": 4}
def test_nested_merge(self):
base = {"plot": {"colors": ["red"], "width": 1}}
override = {"plot": {"width": 2}}
result = _deep_merge(base, override)
assert result["plot"]["colors"] == ["red"]
assert result["plot"]["width"] == 2
def test_list_replacement(self):
"""Lists are replaced entirely, not merged."""
base = {"items": [1, 2, 3]}
override = {"items": [4, 5]}
result = _deep_merge(base, override)
assert result["items"] == [4, 5]
def test_deep_nested(self):
base = {"a": {"b": {"c": 1, "d": 2}}}
override = {"a": {"b": {"c": 99}}}
result = _deep_merge(base, override)
assert result["a"]["b"]["c"] == 99
assert result["a"]["b"]["d"] == 2
class TestConfigLoad:
def test_from_defaults(self):
config = Config.from_defaults()
assert config.plot.line_width == 1.5
assert len(config.plot.colors) == 10
assert config.transforms.default_cfc is None
assert config.protocols.default == "euro_ncap"
def test_load_without_session(self):
config = Config.load()
assert isinstance(config.plot, PlotConfig)
assert isinstance(config.transforms, TransformConfig)
assert isinstance(config.criteria, CriteriaConfig)
assert isinstance(config.protocols, ProtocolConfig)
assert isinstance(config.session, SessionConfig)
assert isinstance(config.web, WebConfig)
def test_load_with_nonexistent_session(self, tmp_path):
config = Config.load(session_path=tmp_path / "nonexistent")
# Should still load package defaults
assert config.plot.line_width == 1.5
def test_session_override(self, tmp_path):
# Create a session config with overrides
session_dir = tmp_path / ".impakt"
session_dir.mkdir()
session_config = session_dir / "config.yaml"
session_config.write_text(
yaml.dump(
{
"plot": {"line_width": 3.0},
"transforms": {"default_cfc": 600},
}
),
encoding="utf-8",
)
config = Config.load(session_path=tmp_path)
assert config.plot.line_width == 3.0
assert config.transforms.default_cfc == 600
# Non-overridden values still come from defaults
assert len(config.plot.colors) == 10
def test_to_dict(self):
config = Config.from_defaults()
d = config.to_dict()
assert "plot" in d
assert "transforms" in d
assert "criteria" in d
assert "protocols" in d
assert d["plot"]["line_width"] == 1.5
def test_to_yaml(self):
config = Config.from_defaults()
yaml_str = config.to_yaml()
assert "line_width" in yaml_str
assert "default_cfc" in yaml_str
# Should be parseable
parsed = yaml.safe_load(yaml_str)
assert parsed["plot"]["line_width"] == 1.5
def test_repr(self):
config = Config.from_defaults()
r = repr(config)
assert "Config(" in r
assert "cfc=" in r
class TestConfigSave:
def test_save_session(self, tmp_path):
config = Config.from_defaults()
config.plot.line_width = 4.0
config.transforms.default_cfc = 1000
path = config.save_session(tmp_path)
assert path.exists()
assert (tmp_path / ".impakt" / "config.yaml").exists()
# Reload and verify
reloaded = Config.load(session_path=tmp_path)
assert reloaded.plot.line_width == 4.0
assert reloaded.transforms.default_cfc == 1000
def test_save_copies_protocols(self, tmp_path):
config = Config.from_defaults()
config.save_session(tmp_path)
proto_dir = tmp_path / ".impakt" / "protocols"
assert proto_dir.exists()
# Should have at least euro_ncap and iihs
yaml_files = list(proto_dir.glob("*.yaml"))
assert len(yaml_files) >= 2
def test_save_user(self, tmp_path, monkeypatch):
# Redirect user dir to tmp
import impakt.config.model as cm
monkeypatch.setattr(cm, "_USER_DIR", tmp_path)
monkeypatch.setattr(cm, "_USER_CONFIG", tmp_path / "config.yaml")
config = Config.from_defaults()
config.plot.line_width = 5.0
path = config.save_user()
assert path.exists()
# Read it back
text = path.read_text()
assert "5.0" in text
def test_init_user_dir(self, tmp_path, monkeypatch):
import impakt.config.model as cm
monkeypatch.setattr(cm, "_USER_DIR", tmp_path / "test_impakt")
monkeypatch.setattr(cm, "_USER_CONFIG", tmp_path / "test_impakt" / "config.yaml")
user_dir = Config.init_user_dir()
assert user_dir.exists()
assert (user_dir / "config.yaml").exists()
assert (user_dir / "templates").exists()
assert (user_dir / "corridors").exists()
assert (user_dir / "protocols").exists()
class TestConfigRoundTrip:
"""Verify that save -> load preserves all values."""
def test_full_round_trip(self, tmp_path):
# Create config with non-default values
config = Config.from_defaults()
config.plot.line_width = 2.5
config.plot.focus_color = "#ff0000"
config.transforms.default_cfc = 180
config.transforms.default_y_align = True
config.criteria.chest_deflection_max_peak_mm = 200.0
config.protocols.default = "iihs"
config.web.cursor_poll_interval_ms = 50
# Save
config.save_session(tmp_path)
# Reload
reloaded = Config.load(session_path=tmp_path)
assert reloaded.plot.line_width == 2.5
assert reloaded.plot.focus_color == "#ff0000"
assert reloaded.transforms.default_cfc == 180
assert reloaded.transforms.default_y_align is True
assert reloaded.criteria.chest_deflection_max_peak_mm == 200.0
assert reloaded.protocols.default == "iihs"
assert reloaded.web.cursor_poll_interval_ms == 50
class TestConfigInSession:
"""Verify Config integrates with Session."""
def test_session_has_config(self):
from impakt import Session
s = Session.open("tests/fixtures/sample_mme")
assert s.config is not None
assert s.config.plot.line_width == 1.5
def test_session_save_config(self, tmp_path):
import shutil
# Copy fixture to tmp so we can write .impakt/
test_dir = tmp_path / "test_session"
shutil.copytree("tests/fixtures/sample_mme", test_dir)
from impakt import Session
s = Session.open(test_dir)
s.config.transforms.default_cfc = 600
result = s.save_config()
assert result is not None
assert (test_dir / ".impakt" / "config.yaml").exists()
# Reopen and verify
s2 = Session.open(test_dir)
assert s2.config.transforms.default_cfc == 600