Python · 3436 bytes Raw Blame History
1 """Tests for :mod:`dlm_sway.probes.base`."""
2
3 from __future__ import annotations
4
5 from typing import Literal
6
7 import pytest
8
9 from dlm_sway.core.errors import SpecValidationError
10 from dlm_sway.core.result import ProbeResult, Verdict
11 from dlm_sway.probes.base import (
12 Probe,
13 ProbeSpec,
14 RunContext,
15 build_probe,
16 registry,
17 validate_all_probes,
18 )
19
20
21 class _DummySpec(ProbeSpec):
22 kind: Literal["__test_dummy"] = "__test_dummy"
23 payload: str = "x"
24
25
26 class _DummyProbe(Probe):
27 kind = "__test_dummy"
28 spec_cls = _DummySpec
29 category = "adherence"
30
31 def run(self, spec: ProbeSpec, ctx: RunContext) -> ProbeResult:
32 assert isinstance(spec, _DummySpec)
33 return ProbeResult(
34 name=spec.name,
35 kind=spec.kind,
36 verdict=Verdict.PASS,
37 score=1.0,
38 message=spec.payload,
39 )
40
41
42 class TestRegistry:
43 def test_autoregister(self) -> None:
44 assert "__test_dummy" in registry()
45 assert registry()["__test_dummy"] is _DummyProbe
46
47 def test_duplicate_kind_rejected(self) -> None:
48 with pytest.raises(ValueError, match="duplicate probe kind"):
49
50 class _Clash(Probe):
51 kind = "__test_dummy"
52 spec_cls = _DummySpec
53
54 def run(self, spec: ProbeSpec, ctx: RunContext) -> ProbeResult:
55 raise NotImplementedError
56
57
58 class TestBuildProbe:
59 def test_valid_entry(self) -> None:
60 probe, spec = build_probe({"name": "t", "kind": "__test_dummy", "payload": "hi"})
61 assert isinstance(probe, _DummyProbe)
62 assert isinstance(spec, _DummySpec)
63 assert spec.payload == "hi"
64
65 def test_unknown_kind(self) -> None:
66 with pytest.raises(SpecValidationError, match="unknown probe kind"):
67 build_probe({"name": "t", "kind": "no_such_kind"})
68
69 def test_missing_kind(self) -> None:
70 with pytest.raises(SpecValidationError, match="missing string 'kind'"):
71 build_probe({"name": "t"})
72
73 def test_extra_field_forbidden(self) -> None:
74 with pytest.raises(SpecValidationError) as exc_info:
75 build_probe({"name": "t", "kind": "__test_dummy", "bogus": "y"})
76 assert "bogus" in str(exc_info.value).lower()
77
78
79 class TestValidateAllProbes:
80 """B7: collect every probe-entry error in a single message."""
81
82 def test_clean_suite_passes(self) -> None:
83 validate_all_probes(
84 [
85 {"name": "p1", "kind": "__test_dummy"},
86 {"name": "p2", "kind": "__test_dummy", "payload": "y"},
87 ]
88 )
89
90 def test_collects_multiple_errors(self) -> None:
91 with pytest.raises(SpecValidationError) as exc_info:
92 validate_all_probes(
93 [
94 {"name": "good", "kind": "__test_dummy"},
95 {"name": "typo1", "kind": "no_such_kind"},
96 {"name": "typo2", "kind": "another_typo"},
97 ]
98 )
99 msg = str(exc_info.value)
100 # Both typos surface in one message — the user fixes everything in one pass.
101 assert "typo1" in msg
102 assert "typo2" in msg
103 assert "no_such_kind" in msg
104 assert "another_typo" in msg
105
106 def test_unnamed_entry_uses_index_label(self) -> None:
107 with pytest.raises(SpecValidationError, match="entry #0"):
108 validate_all_probes([{"kind": "no_such_kind"}])