orgstate / tests /test_config.py
Legal-i's picture
Initial OrgState deploy via Stage 150 free-tier stack
d2d1903 verified
"""
Tests for core.config — the per-entity-type configuration schema (Stage 1).
"""
import pytest
from core.config import (
HIGHER_IS_WORSE,
LOWER_IS_WORSE,
EntityTypeConfig,
MetricConfig,
VerticalConfig,
)
from core.drift import DEFAULT_DRIFT_WEIGHTS, DEFAULT_SEVERITY_THRESHOLDS
# --- MetricConfig ---------------------------------------------------------
def test_metric_config_accepts_canonical_directions():
assert MetricConfig("backlog", HIGHER_IS_WORSE).direction == HIGHER_IS_WORSE
assert MetricConfig("uptime", LOWER_IS_WORSE).direction == LOWER_IS_WORSE
def test_metric_config_translates_legacy_direction_aliases():
# the legacy data contracts say higher_is_better / lower_is_better
assert MetricConfig("uptime", "higher_is_better").direction == LOWER_IS_WORSE
assert MetricConfig("backlog", "lower_is_better").direction == HIGHER_IS_WORSE
def test_metric_config_rejects_unknown_direction():
with pytest.raises(ValueError):
MetricConfig("x", "sideways")
def test_metric_config_rejects_negative_weight():
with pytest.raises(ValueError):
MetricConfig("x", HIGHER_IS_WORSE, weight=-0.1)
def test_metric_config_round_trips_through_dict():
m = MetricConfig("response_time", HIGHER_IS_WORSE, weight=0.3,
feeds_stability=True, latency_target=25.0)
restored = MetricConfig.from_dict("response_time", m.to_dict())
assert restored == m
# --- EntityTypeConfig -----------------------------------------------------
def _branch_dict():
return {
"entity_type": "branch",
"metrics": {
"complaints": {"direction": "higher_is_worse", "weight": 0.5},
"response_time": {"direction": "higher_is_worse", "weight": 0.5,
"latency_target": 25.0, "feeds_stability": True},
},
}
def test_entity_type_config_from_dict_applies_defaults():
et = EntityTypeConfig.from_dict(_branch_dict())
assert et.entity_type == "branch"
assert et.baseline_window == 14
assert et.baseline_lag == 7
# weights/thresholds default to the documented core defaults
assert et.signal_weights == DEFAULT_DRIFT_WEIGHTS
assert et.severity_thresholds == DEFAULT_SEVERITY_THRESHOLDS
def test_entity_type_config_partial_overrides_are_merged():
d = _branch_dict()
d["signal_weights"] = {"delta": 0.4} # override one key only
d["severity_thresholds"] = {"high": 0.6}
et = EntityTypeConfig.from_dict(d)
assert et.signal_weights["delta"] == 0.4
assert et.signal_weights["xi"] == DEFAULT_DRIFT_WEIGHTS["xi"] # untouched
assert et.severity_thresholds["high"] == 0.6
assert et.severity_thresholds["critical"] == DEFAULT_SEVERITY_THRESHOLDS["critical"]
def test_entity_type_config_rejects_no_metrics():
with pytest.raises(ValueError):
EntityTypeConfig(entity_type="empty", metrics=[])
def test_entity_type_config_rejects_bad_baseline_lag():
with pytest.raises(ValueError):
EntityTypeConfig(
entity_type="branch",
metrics=[MetricConfig("x", HIGHER_IS_WORSE)],
baseline_window=10,
baseline_lag=10, # must be strictly less than window
)
def test_entity_type_config_rejects_duplicate_metric_names():
with pytest.raises(ValueError):
EntityTypeConfig(
entity_type="branch",
metrics=[MetricConfig("x", HIGHER_IS_WORSE),
MetricConfig("x", LOWER_IS_WORSE)],
)
def test_entity_type_config_metric_lookup():
et = EntityTypeConfig.from_dict(_branch_dict())
assert et.metric("complaints").weight == 0.5
assert set(et.metric_names) == {"complaints", "response_time"}
with pytest.raises(KeyError):
et.metric("does_not_exist")
def test_entity_type_config_round_trips_through_dict():
et = EntityTypeConfig.from_dict(_branch_dict())
restored = EntityTypeConfig.from_dict(restored_input := et.to_dict())
assert restored.to_dict() == restored_input
# --- VerticalConfig -------------------------------------------------------
def test_vertical_config_round_trips_through_dict():
raw = {
"name": "logistics",
"entity_types": {
"branch": {k: v for k, v in _branch_dict().items() if k != "entity_type"},
},
}
vc = VerticalConfig.from_dict(raw)
assert vc.name == "logistics"
assert vc.entity_type("branch").entity_type == "branch"
# to_dict() yields the *normalised* form (defaults filled in), so it won't
# equal the sparse input — but it must be a stable fixed point: loading it
# back and dumping again changes nothing.
dumped = vc.to_dict()
assert VerticalConfig.from_dict(dumped).to_dict() == dumped