gabriel / muse public
test_name_rev_supercharge.py python
352 lines 13.1 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 139 days ago
1 """Supercharge tests for ``muse name-rev``.
2
3 Coverage tiers
4 --------------
5 - JSON envelope: exit_code and duration_ms present on all resolution outcomes
6 - Error payload: errors go to stdout as JSON in --json mode, no dual stderr prose
7 - Prefix resolution: bare hex short-prefix matches sha256:-prefixed keys in name_map
8 - TypedDicts: _NameRevJson and _NameRevErrorJson with required annotations
9 - Docstring: module docstring covers exit_code and duration_ms
10 - No-prose pollution: JSON stdout is valid on all non-error paths
11 - Stress: 100-commit chain, all entries have exit_code and duration_ms
12 """
13 from __future__ import annotations
14
15 import datetime
16 import json
17 import pathlib
18 from typing import get_type_hints
19
20 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
21 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
22 from tests.cli_test_helper import CliRunner
23
24 runner = CliRunner()
25
26 _DT = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
27
28
29 # ---------------------------------------------------------------------------
30 # Helpers
31 # ---------------------------------------------------------------------------
32
33
34 def _init_repo(tmp_path: pathlib.Path) -> pathlib.Path:
35 muse = tmp_path / ".muse"
36 for d in ("commits", "snapshots", "objects", "refs/heads"):
37 (muse / d).mkdir(parents=True, exist_ok=True)
38 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
39 (muse / "repo.json").write_text(
40 json.dumps({"repo_id": "nr-supercharge", "domain": "midi"}), encoding="utf-8"
41 )
42 return tmp_path
43
44
45 def _env(root: pathlib.Path) -> dict[str, str]:
46 return {"MUSE_REPO_ROOT": str(root)}
47
48
49 def _commit(
50 root: pathlib.Path,
51 msg: str,
52 branch: str = "main",
53 parent: str | None = None,
54 ) -> str:
55 sid = compute_snapshot_id({})
56 write_snapshot(root, SnapshotRecord(snapshot_id=sid, manifest={}, created_at=_DT))
57 parent_ids = [parent] if parent else []
58 cid = compute_commit_id(parent_ids, sid, msg, _DT.isoformat())
59 write_commit(root, CommitRecord(
60 commit_id=cid, repo_id="nr-supercharge", branch=branch,
61 snapshot_id=sid, message=msg, committed_at=_DT,
62 parent_commit_id=parent,
63 ))
64 ref = root / ".muse" / "refs" / "heads" / branch
65 ref.parent.mkdir(parents=True, exist_ok=True)
66 ref.write_text(cid, encoding="utf-8")
67 return cid
68
69
70 def _nr(root: pathlib.Path, *args: str, stdin: str | None = None):
71 from muse.cli.app import main as cli
72 return runner.invoke(cli, ["name-rev", *args], env=_env(root), input=stdin)
73
74
75 def _hex_prefix(cid: str, n: int = 8) -> str:
76 """Extract n hex chars from a sha256:-prefixed commit ID."""
77 return cid[len("sha256:"):len("sha256:") + n]
78
79
80 # ---------------------------------------------------------------------------
81 # JSON envelope — exit_code
82 # ---------------------------------------------------------------------------
83
84
85 class TestJsonEnvelopeExitCode:
86 def test_found_has_exit_code_zero(self, tmp_path: pathlib.Path) -> None:
87 root = _init_repo(tmp_path)
88 cid = _commit(root, "c1")
89 r = _nr(root, cid)
90 assert r.exit_code == 0
91 d = json.loads(r.output)
92 assert "exit_code" in d, "exit_code missing from found envelope"
93 assert d["exit_code"] == 0
94
95 def test_undefined_result_still_exit_code_zero(self, tmp_path: pathlib.Path) -> None:
96 """undefined entries are a valid outcome — exit_code must still be 0."""
97 root = _init_repo(tmp_path)
98 _commit(root, "c1")
99 fake = "a" * 64
100 r = _nr(root, fake)
101 assert r.exit_code == 0
102 d = json.loads(r.output)
103 assert "exit_code" in d
104 assert d["exit_code"] == 0
105
106 def test_mixed_found_and_undefined_exit_code_zero(self, tmp_path: pathlib.Path) -> None:
107 root = _init_repo(tmp_path)
108 cid = _commit(root, "c1")
109 fake = "b" * 64
110 r = _nr(root, cid, fake)
111 assert r.exit_code == 0
112 d = json.loads(r.output)
113 assert d["exit_code"] == 0
114
115
116 # ---------------------------------------------------------------------------
117 # JSON envelope — duration_ms
118 # ---------------------------------------------------------------------------
119
120
121 class TestJsonEnvelopeDurationMs:
122 def test_found_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
123 root = _init_repo(tmp_path)
124 cid = _commit(root, "c1")
125 r = _nr(root, cid)
126 d = json.loads(r.output)
127 assert "duration_ms" in d, "duration_ms missing from found envelope"
128 assert isinstance(d["duration_ms"], float)
129 assert d["duration_ms"] >= 0.0
130
131 def test_undefined_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
132 root = _init_repo(tmp_path)
133 _commit(root, "c1")
134 r = _nr(root, "a" * 64)
135 d = json.loads(r.output)
136 assert "duration_ms" in d
137
138 def test_branches_filter_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
139 root = _init_repo(tmp_path)
140 cid = _commit(root, "c1", "main")
141 r = _nr(root, cid, "--branches", "main")
142 d = json.loads(r.output)
143 assert "duration_ms" in d
144 assert isinstance(d["duration_ms"], float)
145
146
147 # ---------------------------------------------------------------------------
148 # Error payload — errors route to stdout as JSON in --json mode
149 # ---------------------------------------------------------------------------
150
151
152 class TestErrorPayload:
153 def test_no_inputs_error_goes_to_stdout(self, tmp_path: pathlib.Path) -> None:
154 root = _init_repo(tmp_path)
155 r = _nr(root, "--json")
156 assert r.exit_code != 0
157 assert not r.stderr.strip(), f"unexpected stderr: {r.stderr!r}"
158 d = json.loads(r.output)
159 assert d["status"] == "error"
160
161 def test_invalid_hex_error_goes_to_stdout(self, tmp_path: pathlib.Path) -> None:
162 root = _init_repo(tmp_path)
163 r = _nr(root, "--json", "not-hex!")
164 assert r.exit_code != 0
165 assert not r.stderr.strip(), f"unexpected stderr: {r.stderr!r}"
166 d = json.loads(r.output)
167 assert d["status"] == "error"
168
169 def test_bad_max_walk_error_goes_to_stdout(self, tmp_path: pathlib.Path) -> None:
170 root = _init_repo(tmp_path)
171 cid = _commit(root, "c1")
172 r = _nr(root, "--json", cid, "--max-walk", "0")
173 assert r.exit_code != 0
174 assert not r.stderr.strip(), f"unexpected stderr: {r.stderr!r}"
175 d = json.loads(r.output)
176 assert d["status"] == "error"
177
178 def test_error_payload_has_status_error(self, tmp_path: pathlib.Path) -> None:
179 root = _init_repo(tmp_path)
180 r = _nr(root, "--json", "not-valid!")
181 d = json.loads(r.output)
182 assert d["status"] == "error"
183
184 def test_error_payload_has_exit_code(self, tmp_path: pathlib.Path) -> None:
185 root = _init_repo(tmp_path)
186 r = _nr(root, "--json", "not-valid!")
187 d = json.loads(r.output)
188 assert "exit_code" in d
189 assert d["exit_code"] != 0
190
191 def test_error_payload_has_error_field(self, tmp_path: pathlib.Path) -> None:
192 root = _init_repo(tmp_path)
193 r = _nr(root, "--json")
194 d = json.loads(r.output)
195 assert "error" in d
196 assert d["error"]
197
198 def test_no_emoji_on_stderr_in_json_mode(self, tmp_path: pathlib.Path) -> None:
199 root = _init_repo(tmp_path)
200 r = _nr(root, "--json", "not-valid!")
201 assert "❌" not in r.stderr
202
203
204 # ---------------------------------------------------------------------------
205 # Prefix resolution — bare hex prefix against sha256:-prefixed keys
206 # ---------------------------------------------------------------------------
207
208
209 class TestPrefixResolutionSha256:
210 def test_bare_hex_8char_prefix_resolves(self, tmp_path: pathlib.Path) -> None:
211 """Bare 8-char hex prefix must resolve against sha256:-prefixed name_map keys."""
212 root = _init_repo(tmp_path)
213 cid = _commit(root, "c1")
214 prefix = _hex_prefix(cid, 8) # first 8 hex chars, no sha256: prefix
215 r = _nr(root, prefix)
216 assert r.exit_code == 0
217 entry = json.loads(r.output)["results"][0]
218 assert entry["commit_id"] == cid
219 assert entry["undefined"] is False
220
221 def test_bare_hex_4char_prefix_resolves(self, tmp_path: pathlib.Path) -> None:
222 """4-char bare hex prefix must resolve when unambiguous."""
223 root = _init_repo(tmp_path)
224 cid = _commit(root, "unique-c1-msg")
225 prefix = _hex_prefix(cid, 4)
226 r = _nr(root, prefix)
227 assert r.exit_code == 0
228 entry = json.loads(r.output)["results"][0]
229 # Either resolves to cid or is ambiguous — must not crash or error
230 assert entry["input"] == prefix
231 assert r.exit_code == 0
232
233 def test_sha256_prefixed_short_id_resolves(self, tmp_path: pathlib.Path) -> None:
234 """sha256:-prefixed short IDs (e.g. sha256:abcd1234) must also resolve."""
235 root = _init_repo(tmp_path)
236 cid = _commit(root, "c1")
237 short = cid[:len("sha256:") + 8] # keep sha256: + 8 hex chars
238 r = _nr(root, short)
239 assert r.exit_code == 0
240 entry = json.loads(r.output)["results"][0]
241 assert entry["commit_id"] == cid
242
243 def test_input_field_preserves_bare_hex_prefix(self, tmp_path: pathlib.Path) -> None:
244 """input field echoes the caller's original value, not the resolved full ID."""
245 root = _init_repo(tmp_path)
246 cid = _commit(root, "c1")
247 prefix = _hex_prefix(cid, 8)
248 r = _nr(root, prefix)
249 entry = json.loads(r.output)["results"][0]
250 assert entry["input"] == prefix
251
252
253 # ---------------------------------------------------------------------------
254 # No-prose pollution
255 # ---------------------------------------------------------------------------
256
257
258 class TestNoProsePollution:
259 def test_found_stdout_is_valid_json(self, tmp_path: pathlib.Path) -> None:
260 root = _init_repo(tmp_path)
261 cid = _commit(root, "c1")
262 r = _nr(root, cid)
263 json.loads(r.output) # must not raise
264
265 def test_undefined_stdout_is_valid_json(self, tmp_path: pathlib.Path) -> None:
266 root = _init_repo(tmp_path)
267 _commit(root, "c1")
268 json.loads(_nr(root, "a" * 64).output)
269
270 def test_no_emoji_in_success_json(self, tmp_path: pathlib.Path) -> None:
271 root = _init_repo(tmp_path)
272 cid = _commit(root, "c1")
273 r = _nr(root, cid)
274 assert "✅" not in r.output
275 assert "❌" not in r.output
276
277
278 # ---------------------------------------------------------------------------
279 # TypedDicts
280 # ---------------------------------------------------------------------------
281
282
283 class TestTypedDicts:
284 def test_name_rev_json_typeddict_exists(self) -> None:
285 from muse.cli.commands.name_rev import _NameRevJson
286 assert _NameRevJson is not None
287
288 def test_name_rev_error_json_typeddict_exists(self) -> None:
289 from muse.cli.commands.name_rev import _NameRevErrorJson
290 assert _NameRevErrorJson is not None
291
292 def test_name_rev_json_has_exit_code_annotation(self) -> None:
293 from muse.cli.commands.name_rev import _NameRevJson
294 hints = get_type_hints(_NameRevJson)
295 assert "exit_code" in hints
296
297 def test_name_rev_json_has_duration_ms_annotation(self) -> None:
298 from muse.cli.commands.name_rev import _NameRevJson
299 hints = get_type_hints(_NameRevJson)
300 assert "duration_ms" in hints
301
302 def test_name_rev_error_json_has_required_fields(self) -> None:
303 from muse.cli.commands.name_rev import _NameRevErrorJson
304 hints = get_type_hints(_NameRevErrorJson)
305 for field in ("status", "error", "exit_code"):
306 assert field in hints, f"Missing annotation: {field!r}"
307
308
309 # ---------------------------------------------------------------------------
310 # Docstring coverage
311 # ---------------------------------------------------------------------------
312
313
314 class TestDocstring:
315 def _doc(self) -> str:
316 import muse.cli.commands.name_rev as mod
317 return mod.__doc__ or ""
318
319 def test_docstring_documents_exit_code(self) -> None:
320 assert "exit_code" in self._doc()
321
322 def test_docstring_documents_duration_ms(self) -> None:
323 assert "duration_ms" in self._doc()
324
325
326 # ---------------------------------------------------------------------------
327 # Stress
328 # ---------------------------------------------------------------------------
329
330
331 class TestStress:
332 def test_100_commit_chain_all_have_envelope_fields(self, tmp_path: pathlib.Path) -> None:
333 root = _init_repo(tmp_path)
334 parent: str | None = None
335 commits: list[str] = []
336 for i in range(100):
337 cid = _commit(root, f"c{i:03d}", parent=parent)
338 commits.append(cid)
339 parent = cid
340
341 r = _nr(root, commits[0], commits[49], commits[-1])
342 assert r.exit_code == 0
343 d = json.loads(r.output)
344 assert "exit_code" in d
345 assert "duration_ms" in d
346 assert d["exit_code"] == 0
347 assert isinstance(d["duration_ms"], float)
348 # Tip at distance 0, midpoint at 50, root at 99
349 by_id = {e["commit_id"]: e for e in d["results"]}
350 assert by_id[commits[-1]]["distance"] == 0
351 assert by_id[commits[49]]["distance"] == 50
352 assert by_id[commits[0]]["distance"] == 99
File History 1 commit
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 139 days ago