|
1 | 1 | """Unit tests for policy.py — Cedar policy engine.""" |
2 | 2 |
|
| 3 | +from unittest.mock import patch |
| 4 | + |
3 | 5 | import pytest |
4 | 6 |
|
5 | 7 | cedarpy = pytest.importorskip("cedarpy") |
6 | 8 |
|
7 | | -from policy import PolicyDecision, PolicyEngine |
| 9 | +import policy |
| 10 | +from policy import PolicyDecision, PolicyEngine, _validate_constants |
8 | 11 |
|
9 | 12 |
|
10 | 13 | class TestPolicyDecision: |
@@ -231,3 +234,66 @@ def test_task_type_property(self): |
231 | 234 | def test_task_type_pr_review(self): |
232 | 235 | engine = PolicyEngine(task_type="pr_review", repo="owner/repo") |
233 | 236 | assert engine.task_type == "pr_review" |
| 237 | + |
| 238 | + |
| 239 | +class TestConstantsSemanticValidation: |
| 240 | + """Verify _validate_constants rejects invariant violations.""" |
| 241 | + |
| 242 | + def test_rejects_approval_timeout_min_zero(self): |
| 243 | + with ( |
| 244 | + patch.object(policy, "FLOOR_TIMEOUT_S", 0), |
| 245 | + pytest.raises(ValueError, match=r"approval_timeout_s\.min must be > 0"), |
| 246 | + ): |
| 247 | + _validate_constants() |
| 248 | + |
| 249 | + def test_rejects_approval_timeout_min_negative(self): |
| 250 | + with ( |
| 251 | + patch.object(policy, "FLOOR_TIMEOUT_S", -1), |
| 252 | + pytest.raises(ValueError, match=r"approval_timeout_s\.min must be > 0"), |
| 253 | + ): |
| 254 | + _validate_constants() |
| 255 | + |
| 256 | + def test_rejects_approval_timeout_default_below_min(self): |
| 257 | + with ( |
| 258 | + patch.object(policy, "FLOOR_TIMEOUT_S", 60), |
| 259 | + patch.object(policy, "DEFAULT_TASK_TIMEOUT_S", 30), |
| 260 | + pytest.raises(ValueError, match=r"approval_timeout_s\.default .* must be >= min"), |
| 261 | + ): |
| 262 | + _validate_constants() |
| 263 | + |
| 264 | + def test_rejects_approval_timeout_max_below_default(self): |
| 265 | + with ( |
| 266 | + patch.object(policy, "FLOOR_TIMEOUT_S", 30), |
| 267 | + patch.object(policy, "DEFAULT_TASK_TIMEOUT_S", 300), |
| 268 | + patch.object(policy, "_ATS", {"min": 30, "max": 100, "default": 300}), |
| 269 | + pytest.raises(ValueError, match=r"approval_timeout_s\.max .* must be >= default"), |
| 270 | + ): |
| 271 | + _validate_constants() |
| 272 | + |
| 273 | + def test_rejects_approval_gate_cap_min_zero(self): |
| 274 | + with ( |
| 275 | + patch.object(policy, "APPROVAL_GATE_CAP_MIN", 0), |
| 276 | + pytest.raises(ValueError, match=r"approval_gate_cap\.min must be > 0"), |
| 277 | + ): |
| 278 | + _validate_constants() |
| 279 | + |
| 280 | + def test_rejects_approval_gate_cap_default_below_min(self): |
| 281 | + with ( |
| 282 | + patch.object(policy, "APPROVAL_GATE_CAP_MIN", 10), |
| 283 | + patch.object(policy, "DEFAULT_APPROVAL_GATE_CAP", 5), |
| 284 | + pytest.raises(ValueError, match=r"approval_gate_cap\.default .* must be >= min"), |
| 285 | + ): |
| 286 | + _validate_constants() |
| 287 | + |
| 288 | + def test_rejects_approval_gate_cap_max_below_default(self): |
| 289 | + with ( |
| 290 | + patch.object(policy, "APPROVAL_GATE_CAP_MIN", 1), |
| 291 | + patch.object(policy, "DEFAULT_APPROVAL_GATE_CAP", 100), |
| 292 | + patch.object(policy, "APPROVAL_GATE_CAP_MAX", 50), |
| 293 | + pytest.raises(ValueError, match=r"approval_gate_cap\.max .* must be >= default"), |
| 294 | + ): |
| 295 | + _validate_constants() |
| 296 | + |
| 297 | + def test_passes_with_valid_constants(self): |
| 298 | + """Sanity: the real constants pass validation.""" |
| 299 | + _validate_constants() |
0 commit comments