gabriel / muse public
test_core_invariants.py python
179 lines 5.6 KB
Raw
sha256:c5131d76c6eada02939111fda4aa8e51b0c1456b9983727cfd6be101916de14e merge: pull local/dev — resolve trivial _EXT_MAP symbol con… Sonnet 4.6 patch 12 days ago
1 """Tests for the generic invariants engine in muse/core/invariants.py."""
2
3 import pathlib
4 import tempfile
5
6 import pytest
7
8 from muse.core.invariants import (
9 BaseReport,
10 BaseViolation,
11 InvariantChecker,
12 InvariantSeverity,
13 format_report,
14 load_rules_toml,
15 make_report,
16 )
17
18
19 # ---------------------------------------------------------------------------
20 # make_report
21 # ---------------------------------------------------------------------------
22
23
24 def _make_violation(
25 rule_name: str = "test_rule",
26 severity: InvariantSeverity = "error",
27 address: str = "src/foo.py",
28 description: str = "test violation",
29 ) -> BaseViolation:
30 return BaseViolation(
31 rule_name=rule_name,
32 severity=severity,
33 address=address,
34 description=description,
35 )
36
37
38 class TestMakeReport:
39 def test_empty_violations(self) -> None:
40 report = make_report("abc123", "code", [], 3)
41 assert report["commit_id"] == "abc123"
42 assert report["domain"] == "code"
43 assert report["violations"] == []
44 assert report["rules_checked"] == 3
45 assert not report["has_errors"]
46 assert not report["has_warnings"]
47
48 def test_error_sets_has_errors(self) -> None:
49 v = _make_violation(severity="error")
50 report = make_report("abc", "code", [v], 1)
51 assert report["has_errors"] is True
52 assert report["has_warnings"] is False
53
54 def test_warning_sets_has_warnings(self) -> None:
55 v = _make_violation(severity="warning")
56 report = make_report("abc", "code", [v], 1)
57 assert report["has_errors"] is False
58 assert report["has_warnings"] is True
59
60 def test_violations_sorted_by_address(self) -> None:
61 v1 = _make_violation(address="z.py")
62 v2 = _make_violation(address="a.py")
63 report = make_report("abc", "code", [v1, v2], 2)
64 assert report["violations"][0]["address"] == "a.py"
65 assert report["violations"][1]["address"] == "z.py"
66
67 def test_info_does_not_set_flags(self) -> None:
68 v = _make_violation(severity="info")
69 report = make_report("abc", "code", [v], 1)
70 assert not report["has_errors"]
71 assert not report["has_warnings"]
72
73
74 # ---------------------------------------------------------------------------
75 # format_report
76 # ---------------------------------------------------------------------------
77
78
79 class TestFormatReport:
80 def test_no_violations_shows_green(self) -> None:
81 report = make_report("abc", "code", [], 5)
82 out = format_report(report)
83 assert "✅" in out
84 assert "5 rules" in out
85
86 def test_error_shows_red_cross(self) -> None:
87 v = _make_violation(severity="error", address="src/foo.py::bar")
88 report = make_report("abc", "code", [v], 1)
89 out = format_report(report)
90 assert "❌" in out
91 assert "src/foo.py::bar" in out
92
93 def test_warning_shows_warning_emoji(self) -> None:
94 v = _make_violation(severity="warning", address="src/baz.py")
95 report = make_report("abc", "code", [v], 1)
96 out = format_report(report)
97 assert "⚠️" in out
98
99 def test_no_color_mode(self) -> None:
100 v = _make_violation(severity="error")
101 report = make_report("abc", "code", [v], 1)
102 out = format_report(report, color=False)
103 assert "[error]" in out
104 assert "❌" not in out
105
106
107 # ---------------------------------------------------------------------------
108 # load_rules_toml
109 # ---------------------------------------------------------------------------
110
111
112 class TestLoadRulesToml:
113 def test_missing_file_returns_empty(self) -> None:
114 path = pathlib.Path("/nonexistent/path/rules.toml")
115 result = load_rules_toml(path)
116 assert result == []
117
118 def test_valid_toml_parsed(self) -> None:
119 toml_content = """
120 [[rule]]
121 name = "my_rule"
122 severity = "error"
123 scope = "file"
124 rule_type = "max_complexity"
125
126 [rule.params]
127 threshold = 10
128 """
129 with tempfile.NamedTemporaryFile(suffix=".toml", mode="w", delete=False) as f:
130 f.write(toml_content)
131 path = pathlib.Path(f.name)
132
133 try:
134 rules = load_rules_toml(path)
135 assert len(rules) == 1
136 assert rules[0]["name"] == "my_rule"
137 assert rules[0]["severity"] == "error"
138 finally:
139 path.unlink(missing_ok=True)
140
141 def test_empty_toml_returns_empty(self) -> None:
142 with tempfile.NamedTemporaryFile(suffix=".toml", mode="w", delete=False) as f:
143 f.write("")
144 path = pathlib.Path(f.name)
145 try:
146 result = load_rules_toml(path)
147 assert result == []
148 finally:
149 path.unlink(missing_ok=True)
150
151
152 # ---------------------------------------------------------------------------
153 # InvariantChecker protocol
154 # ---------------------------------------------------------------------------
155
156
157 class TestInvariantCheckerProtocol:
158 def test_concrete_checker_satisfies_protocol(self) -> None:
159 """A class with a check() method satisfies the InvariantChecker protocol."""
160
161 class MyChecker:
162 def check(
163 self,
164 repo_root: pathlib.Path,
165 commit_id: str,
166 *,
167 rules_file: pathlib.Path | None = None,
168 ) -> BaseReport:
169 return make_report(commit_id, "test", [], 0)
170
171 checker = MyChecker()
172 assert isinstance(checker, InvariantChecker)
173
174 def test_missing_check_method_fails_protocol(self) -> None:
175 class NotAChecker:
176 def run(self) -> None:
177 pass
178
179 assert not isinstance(NotAChecker(), InvariantChecker)
File History 5 commits
sha256:c5131d76c6eada02939111fda4aa8e51b0c1456b9983727cfd6be101916de14e merge: pull local/dev — resolve trivial _EXT_MAP symbol con… Sonnet 4.6 patch 12 days ago
sha256:9c33d61749fff814c5226d5386aa2af7064c2c02788594a25fdd709358132eea fix: _PROPOSAL_PREFIX_RESOLVE_LIMIT 200 → 100 to match hub … Sonnet 4.6 19 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 22 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 28 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 28 days ago