| 1 | """Tests for :mod:`dlm_sway.core.result`.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from dataclasses import FrozenInstanceError |
| 6 | |
| 7 | import pytest |
| 8 | |
| 9 | from dlm_sway.core.result import ( |
| 10 | DEFAULT_COMPONENT_WEIGHTS, |
| 11 | ProbeResult, |
| 12 | SuiteResult, |
| 13 | SwayScore, |
| 14 | Verdict, |
| 15 | utcnow, |
| 16 | ) |
| 17 | |
| 18 | |
| 19 | class TestVerdict: |
| 20 | def test_is_str_enum(self) -> None: |
| 21 | assert Verdict.PASS.value == "pass" |
| 22 | assert str(Verdict.WARN.value) == "warn" |
| 23 | |
| 24 | def test_all_expected_members(self) -> None: |
| 25 | assert {v.value for v in Verdict} == { |
| 26 | "pass", |
| 27 | "fail", |
| 28 | "warn", |
| 29 | "skip", |
| 30 | "error", |
| 31 | } |
| 32 | |
| 33 | |
| 34 | class TestProbeResult: |
| 35 | def test_minimum_construction(self) -> None: |
| 36 | r = ProbeResult(name="t", kind="delta_kl", verdict=Verdict.PASS, score=0.82) |
| 37 | assert r.raw is None |
| 38 | assert r.evidence == {} |
| 39 | assert r.message == "" |
| 40 | assert r.duration_s == 0.0 |
| 41 | |
| 42 | def test_frozen(self) -> None: |
| 43 | r = ProbeResult(name="t", kind="t", verdict=Verdict.PASS, score=0.5) |
| 44 | with pytest.raises(FrozenInstanceError): |
| 45 | r.score = 0.6 # type: ignore[misc] |
| 46 | |
| 47 | |
| 48 | class TestSuiteResult: |
| 49 | def test_wall_seconds(self) -> None: |
| 50 | from datetime import timedelta |
| 51 | |
| 52 | started = utcnow() |
| 53 | finished = started + timedelta(seconds=2, milliseconds=500) |
| 54 | result = SuiteResult( |
| 55 | spec_path="sway.yaml", |
| 56 | started_at=started, |
| 57 | finished_at=finished, |
| 58 | base_model_id="b", |
| 59 | adapter_id="a", |
| 60 | sway_version="0.1.0.dev0", |
| 61 | ) |
| 62 | assert result.wall_seconds == pytest.approx(2.5, abs=1e-6) |
| 63 | |
| 64 | |
| 65 | class TestSwayScore: |
| 66 | def test_default_weights_sum_to_one(self) -> None: |
| 67 | assert abs(sum(DEFAULT_COMPONENT_WEIGHTS.values()) - 1.0) < 1e-9 |
| 68 | |
| 69 | def test_band_boundaries(self) -> None: |
| 70 | assert SwayScore.band_for(0.0) == "noise" |
| 71 | assert SwayScore.band_for(0.29) == "noise" |
| 72 | assert SwayScore.band_for(0.30) == "partial" |
| 73 | assert SwayScore.band_for(0.59) == "partial" |
| 74 | assert SwayScore.band_for(0.60) == "healthy" |
| 75 | assert SwayScore.band_for(0.85) == "healthy" |
| 76 | assert SwayScore.band_for(0.851) == "suspicious" |
| 77 | assert SwayScore.band_for(0.99) == "suspicious" |
| 78 | |
| 79 | |
| 80 | def test_utcnow_is_tz_aware() -> None: |
| 81 | now = utcnow() |
| 82 | assert now.tzinfo is not None |