test_cost_awareness_unit.py file-level

at sha256:a · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:8 docs: queue board-identity follow-ups so they survive the session Capt… · aaronrene · Sep 5, 2026
1 """Unit tests for Track P / P-cost cost awareness (§PC.9)."""
2
3 from __future__ import annotations
4
5 from pathlib import Path
6
7 import pytest
8 import yaml
9
10 from adapters.config import load_config
11 from adapters.errors import ConfigError
12 from cli.kit_root import kit_root
13 from tests.support import FIXTURES
14 from tools.cost_awareness.derive import COST_CLASS_ORDER, derive_cost_view
15 from tools.cost_awareness.normalize import gate_for_phase, normalize_phase_tier
16 from tools.governance_gates.types import PendingGate
17 from tools.model_routing.labels import (
18 COST_CLASS_VALUES,
19 RoutingPolicyError,
20 load_model_tier_cost_bands,
21 validate_model_tier_entry,
22 )
23
24
25 def test_cost_class_optional_valid_values() -> None:
26 for band in COST_CLASS_VALUES:
27 tier_id = validate_model_tier_entry(
28 {
29 "id": "fast",
30 "display": "Fast",
31 "meaning": "quick",
32 "cost_class": band,
33 },
34 index=0,
35 path="policy/model-labels.yaml",
36 )
37 assert tier_id == "fast"
38
39
40 def test_cost_class_rejects_unknown_value() -> None:
41 with pytest.raises(ConfigError, match="outside frozen vocabulary"):
42 validate_model_tier_entry(
43 {
44 "id": "fast",
45 "display": "Fast",
46 "meaning": "quick",
47 "cost_class": "unknown",
48 },
49 index=0,
50 path="policy/model-labels.yaml",
51 )
52
53
54 def test_cost_class_rejects_non_string() -> None:
55 with pytest.raises(ConfigError, match="must be a string"):
56 validate_model_tier_entry(
57 {
58 "id": "fast",
59 "display": "Fast",
60 "meaning": "quick",
61 "cost_class": 42,
62 },
63 index=0,
64 path="policy/model-labels.yaml",
65 )
66
67
68 def test_cost_class_recognized_key_not_unknown() -> None:
69 validate_model_tier_entry(
70 {
71 "id": "fast",
72 "display": "Fast",
73 "meaning": "quick",
74 "cost_class": "low",
75 "cursor_model_hint": "hint",
76 },
77 index=0,
78 path="policy/model-labels.yaml",
79 )
80
81
82 def test_cost_class_fail_closed_exit_32(tmp_path: Path) -> None:
83 labels = tmp_path / "policy" / "model-labels.yaml"
84 labels.parent.mkdir(parents=True)
85 labels.write_text(
86 yaml.safe_dump(
87 {
88 "model_tiers": [
89 {
90 "id": "fast",
91 "display": "Fast",
92 "meaning": "quick",
93 "cost_class": "pricey",
94 }
95 ]
96 }
97 ),
98 encoding="utf-8",
99 )
100 load_model_tier_cost_bands.cache_clear()
101 with pytest.raises(RoutingPolicyError) as exc:
102 load_model_tier_cost_bands(tmp_path, fail_closed=True)
103 assert exc.value.exit_code == 32
104 load_model_tier_cost_bands.cache_clear()
105
106
107 def test_paid_derivation_all_bands() -> None:
108 bands = {
109 "deep-reasoning": "high",
110 "standard": "moderate",
111 "fast": "low",
112 "local-offline": "free",
113 }
114 assert derive_cost_view("deep-reasoning", bands) == ("high", True)
115 assert derive_cost_view("standard", bands) == ("moderate", True)
116 assert derive_cost_view("fast", bands) == ("low", True)
117 assert derive_cost_view("local-offline", bands) == ("free", False)
118
119
120 def test_paid_derivation_human_unpaid() -> None:
121 assert derive_cost_view("human", {}) == ("free", False)
122
123
124 def test_paid_derivation_absent_band_conservative() -> None:
125 assert derive_cost_view("standard", {"standard": None}) == ("unknown", True)
126
127
128 def test_cost_class_ordinal_order() -> None:
129 assert COST_CLASS_ORDER["free"] < COST_CLASS_ORDER["low"]
130 assert COST_CLASS_ORDER["low"] < COST_CLASS_ORDER["moderate"]
131 assert COST_CLASS_ORDER["moderate"] < COST_CLASS_ORDER["high"]
132
133
134 def test_cost_awareness_config_defaults() -> None:
135 config = load_config(FIXTURES / "config-git-only.yaml")
136 assert config.cost_awareness.enabled is False
137 assert config.cost_awareness.surfaces == frozenset({"status", "governance-sync"})
138
139
140 def test_cost_awareness_unknown_surface_exit_2(tmp_path: Path) -> None:
141 write = FIXTURES / "config-git-only.yaml"
142 cfg = tmp_path / "config.yaml"
143 data = yaml.safe_load(write.read_text(encoding="utf-8"))
144 data["cost_awareness"] = {"enabled": True, "surfaces": ["handover-paste"]}
145 cfg.write_text(yaml.safe_dump(data), encoding="utf-8")
146 with pytest.raises(ConfigError, match="status\\|governance-sync"):
147 load_config(cfg)
148
149
150 def test_phase_model_label_normalization() -> None:
151 label_ids = frozenset({"thinking", "auto"})
152 assert normalize_phase_tier("Thinking", label_ids=label_ids) == "thinking"
153 assert normalize_phase_tier("Auto", label_ids=label_ids) == "auto"
154 assert normalize_phase_tier("Operator + Auto", label_ids=label_ids) is None
155
156
157 def test_pending_gate_mapping() -> None:
158 pending = (
159 PendingGate(
160 gate_id="build_verification",
161 phase_id="Demo Auto",
162 artifact=None,
163 message="msg",
164 invoke="invoke",
165 ),
166 PendingGate(
167 gate_id="freeze_review",
168 phase_id="Demo Thinking",
169 artifact="docs/x.md",
170 message="msg",
171 invoke="invoke",
172 ),
173 )
174 assert gate_for_phase(pending, "Demo Thinking") == "freeze_review"
175 assert gate_for_phase(pending, "Demo Auto") == "build_verification"
176 assert gate_for_phase(pending, "Other") is None
177
178
179 def test_derivation_is_pure_no_io() -> None:
180 bands = {"standard": "moderate"}
181 first = derive_cost_view("standard", bands)
182 second = derive_cost_view("standard", bands)
183 assert first == second == ("moderate", True)