gabriel / muse public
test_patch_id_supercharge.py python
691 lines 27.5 KB
Raw
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 20 days ago
1 """Supercharge tests for ``muse patch-id``.
2
3 TDD — [RED] tests fail until the feature lands; [GREEN] tests replace the
4 broken ``test_cmd_patch_id.py`` (which used raw hex object IDs without the
5 required ``sha256:`` prefix).
6
7 New features under test
8 -----------------------
9 - ``duration_ms`` [RED] — wall-clock ms in JSON output
10 - ``exit_code`` [RED] — always present in JSON output
11 - ``files_changed`` [RED] — count of files in the diff in JSON output
12 - ``stable`` [RED] — boolean reflecting --stable flag in JSON output
13
14 Gap-fill / regression coverage [GREEN]
15 ----------------------------------------
16 - _compute_patch_id unit tests (all using sha256: prefix)
17 - JSON schema keys present
18 - Text output format «<patch_id> <commit_id>»
19 - Same diff → same patch-id (cherry-pick detection)
20 - Different diff → different patch-id
21 - --stable whitespace normalisation via CLI and unit
22 - Initial commit (no parent) has deterministic patch-id
23 - Branch name ref, explicit commit ID ref
24 - Error paths: empty repo, bad ref
25 - Security: ANSI, null byte, path traversal, very long ref, no tracebacks
26 - Data integrity: patch_id changes when content changes
27 - Multi-file commit patch-id covers all changed files
28 - Binary file diffs included in patch-id
29 - Performance: duration_ms < 2000ms for 20-file commit
30 - Stress: 10 distinct commits → 10 distinct patch-ids
31 """
32 from __future__ import annotations
33 from collections.abc import Mapping
34
35 import datetime
36 import json
37 import pathlib
38
39 import pytest
40
41 from muse.cli.commands.patch_id import _compute_patch_id
42 from muse.core.object_store import write_object
43 from muse.core.ids import hash_commit as compute_commit_id, hash_snapshot as compute_snapshot_id
44 from muse.core.commits import (
45 CommitRecord,
46 write_commit,
47 )
48 from muse.core.snapshots import (
49 SnapshotRecord,
50 write_snapshot,
51 )
52 from muse.core.types import Manifest, blob_id, split_id
53 from muse.core.paths import heads_dir, muse_dir, ref_path
54 from tests.cli_test_helper import CliRunner, InvokeResult
55
56 runner = CliRunner()
57
58 _TS = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
59 _REPO_ID = "patch-id-test"
60
61
62 # ---------------------------------------------------------------------------
63 # Helpers
64 # ---------------------------------------------------------------------------
65
66 def _oid(content: bytes) -> str:
67 """Return a sha256:-prefixed object ID for content."""
68 return blob_id(content)
69
70
71 def _init_repo(path: pathlib.Path) -> pathlib.Path:
72 muse = muse_dir(path)
73 for d in ("commits", "snapshots", "objects", "refs/heads"):
74 (muse / d).mkdir(parents=True, exist_ok=True)
75 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
76 (muse / "repo.json").write_text(
77 json.dumps({"repo_id": _REPO_ID, "domain": "code"}), encoding="utf-8"
78 )
79 return path
80
81
82 def _write_obj(repo: pathlib.Path, content: bytes) -> str:
83 oid = _oid(content)
84 write_object(repo, oid, content)
85 return oid
86
87
88 def _commit(
89 repo: pathlib.Path,
90 msg: str,
91 manifest: dict[str, str],
92 *,
93 branch: str = "main",
94 parent: str | None = None,
95 ) -> str:
96 sid = compute_snapshot_id(manifest)
97 write_snapshot(repo, SnapshotRecord(snapshot_id=sid, manifest=manifest, created_at=_TS))
98 parent_ids = [parent] if parent else []
99 cid = compute_commit_id( parent_ids=parent_ids,
100 snapshot_id=sid,
101 message=msg,
102 committed_at_iso=_TS.isoformat(),
103 author="gabriel",)
104 write_commit(repo, CommitRecord(
105 commit_id=cid, branch=branch,
106 snapshot_id=sid, message=msg, committed_at=_TS,
107 author="gabriel", parent_commit_id=parent, parent2_commit_id=None,
108 ))
109 ref = ref_path(repo, branch)
110 ref.parent.mkdir(parents=True, exist_ok=True)
111 ref.write_text(cid)
112 return cid
113
114
115 def _pid(repo: pathlib.Path, *args: str) -> InvokeResult:
116 return runner.invoke(None, ["patch-id", *args], env={"MUSE_REPO_ROOT": str(repo)})
117
118
119 def _json_out(r: InvokeResult) -> Mapping[str, object]:
120 for line in r.output.splitlines():
121 line = line.strip()
122 if line.startswith("{"):
123 return json.loads(line)
124 raise ValueError(f"No JSON in output:\n{r.output!r}")
125
126
127 # ---------------------------------------------------------------------------
128 # _compute_patch_id unit tests [GREEN — fix sha256: prefix]
129 # ---------------------------------------------------------------------------
130
131 class TestComputePatchId:
132 def test_same_diff_same_id(self, tmp_path: pathlib.Path) -> None:
133 repo = _init_repo(tmp_path)
134 oid_a = _write_obj(repo, b"x = 1\n")
135 oid_b = _write_obj(repo, b"x = 2\n")
136 base = {"f.py": oid_a}
137 target = {"f.py": oid_b}
138 assert _compute_patch_id(repo, base, target, stable=False) == \
139 _compute_patch_id(repo, base, target, stable=False)
140
141 def test_result_is_64_hex_chars(self, tmp_path: pathlib.Path) -> None:
142 repo = _init_repo(tmp_path)
143 oid_a = _write_obj(repo, b"a")
144 oid_b = _write_obj(repo, b"b")
145 pid = _compute_patch_id(repo, {"f.py": oid_a}, {"f.py": oid_b}, stable=False)
146 assert pid.startswith("sha256:") and len(pid) == 71
147 assert all(c in "0123456789abcdef" for c in split_id(pid)[1])
148
149 def test_different_content_different_id(self, tmp_path: pathlib.Path) -> None:
150 repo = _init_repo(tmp_path)
151 oid_a = _write_obj(repo, b"v1\n")
152 oid_b = _write_obj(repo, b"v2\n")
153 oid_c = _write_obj(repo, b"v3\n")
154 id1 = _compute_patch_id(repo, {"f.py": oid_a}, {"f.py": oid_b}, stable=False)
155 id2 = _compute_patch_id(repo, {"f.py": oid_a}, {"f.py": oid_c}, stable=False)
156 assert id1 != id2
157
158 def test_no_op_commit_deterministic(self, tmp_path: pathlib.Path) -> None:
159 repo = _init_repo(tmp_path)
160 oid = _write_obj(repo, b"unchanged\n")
161 manifest = {"f.py": oid}
162 pid1 = _compute_patch_id(repo, manifest, manifest, stable=False)
163 pid2 = _compute_patch_id(repo, manifest, manifest, stable=False)
164 assert pid1 == pid2
165
166 def test_stable_normalizes_trailing_whitespace(self, tmp_path: pathlib.Path) -> None:
167 repo = _init_repo(tmp_path)
168 oid_base = _write_obj(repo, b"x = 1\n")
169 oid_clean = _write_obj(repo, b"x = 2\n")
170 oid_ws = _write_obj(repo, b"x = 2 \n")
171 base = {"f.py": oid_base}
172 id_clean = _compute_patch_id(repo, base, {"f.py": oid_clean}, stable=True)
173 id_ws = _compute_patch_id(repo, base, {"f.py": oid_ws}, stable=True)
174 assert id_clean == id_ws
175
176 def test_unstable_sensitive_to_whitespace(self, tmp_path: pathlib.Path) -> None:
177 repo = _init_repo(tmp_path)
178 oid_base = _write_obj(repo, b"x = 1\n")
179 oid_clean = _write_obj(repo, b"x = 2\n")
180 oid_ws = _write_obj(repo, b"x = 2 \n")
181 base = {"f.py": oid_base}
182 id_clean = _compute_patch_id(repo, base, {"f.py": oid_clean}, stable=False)
183 id_ws = _compute_patch_id(repo, base, {"f.py": oid_ws}, stable=False)
184 assert id_clean != id_ws
185
186 def test_file_order_does_not_affect_id(self, tmp_path: pathlib.Path) -> None:
187 """Files are sorted alphabetically so order of dict keys is irrelevant."""
188 repo = _init_repo(tmp_path)
189 oid_a = _write_obj(repo, b"a\n")
190 oid_b = _write_obj(repo, b"b\n")
191 oid_a2 = _write_obj(repo, b"a2\n")
192 oid_b2 = _write_obj(repo, b"b2\n")
193 base1 = {"a.py": oid_a, "b.py": oid_b}
194 base2 = {"b.py": oid_b, "a.py": oid_a}
195 target1 = {"a.py": oid_a2, "b.py": oid_b2}
196 target2 = {"b.py": oid_b2, "a.py": oid_a2}
197 assert _compute_patch_id(repo, base1, target1, stable=False) == \
198 _compute_patch_id(repo, base2, target2, stable=False)
199
200 def test_initial_commit_no_parent(self, tmp_path: pathlib.Path) -> None:
201 """Initial commit: base_manifest={}, target has files."""
202 repo = _init_repo(tmp_path)
203 oid = _write_obj(repo, b"hello\n")
204 pid = _compute_patch_id(repo, {}, {"f.py": oid}, stable=False)
205 assert pid.startswith("sha256:") and len(pid) == 71
206
207 def test_deleted_file_affects_id(self, tmp_path: pathlib.Path) -> None:
208 repo = _init_repo(tmp_path)
209 oid = _write_obj(repo, b"bye\n")
210 id_del = _compute_patch_id(repo, {"f.py": oid}, {}, stable=False)
211 id_add = _compute_patch_id(repo, {}, {"f.py": oid}, stable=False)
212 assert id_del != id_add
213
214 def test_binary_content_included(self, tmp_path: pathlib.Path) -> None:
215 """Binary files (non-UTF-8) still produce a stable patch-id."""
216 repo = _init_repo(tmp_path)
217 binary_v1 = bytes(range(256))
218 binary_v2 = bytes(range(255, -1, -1))
219 oid1 = _write_obj(repo, binary_v1)
220 oid2 = _write_obj(repo, binary_v2)
221 pid1 = _compute_patch_id(repo, {"img.bin": oid1}, {"img.bin": oid2}, stable=False)
222 pid2 = _compute_patch_id(repo, {"img.bin": oid1}, {"img.bin": oid2}, stable=False)
223 assert pid1 == pid2
224 assert pid1.startswith("sha256:") and len(pid1) == 71
225
226
227 # ---------------------------------------------------------------------------
228 # JSON output: duration_ms, exit_code, files_changed, stable [RED]
229 # ---------------------------------------------------------------------------
230
231 class TestJsonSupercharge:
232 """[RED] New fields in --json output."""
233
234 def test_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
235 repo = _init_repo(tmp_path)
236 oid = _write_obj(repo, b"x")
237 _commit(repo, "init", {"f.py": oid})
238 d = _json_out(_pid(repo, "--json"))
239 assert "duration_ms" in d
240
241 def test_json_duration_ms_non_negative(self, tmp_path: pathlib.Path) -> None:
242 repo = _init_repo(tmp_path)
243 oid = _write_obj(repo, b"x")
244 _commit(repo, "init", {"f.py": oid})
245 d = _json_out(_pid(repo, "--json"))
246 assert d["duration_ms"] >= 0.0
247
248 def test_json_has_exit_code(self, tmp_path: pathlib.Path) -> None:
249 repo = _init_repo(tmp_path)
250 oid = _write_obj(repo, b"x")
251 _commit(repo, "init", {"f.py": oid})
252 d = _json_out(_pid(repo, "--json"))
253 assert "exit_code" in d
254
255 def test_json_exit_code_zero_on_success(self, tmp_path: pathlib.Path) -> None:
256 repo = _init_repo(tmp_path)
257 oid = _write_obj(repo, b"x")
258 _commit(repo, "init", {"f.py": oid})
259 d = _json_out(_pid(repo, "--json"))
260 assert d["exit_code"] == 0
261
262 def test_json_has_files_changed(self, tmp_path: pathlib.Path) -> None:
263 repo = _init_repo(tmp_path)
264 oid = _write_obj(repo, b"x")
265 _commit(repo, "init", {"f.py": oid})
266 d = _json_out(_pid(repo, "--json"))
267 assert "files_changed" in d
268
269 def test_json_files_changed_correct_count(self, tmp_path: pathlib.Path) -> None:
270 repo = _init_repo(tmp_path)
271 oid_a = _write_obj(repo, b"a")
272 oid_b = _write_obj(repo, b"b")
273 _commit(repo, "init", {"a.py": oid_a, "b.py": oid_b})
274 d = _json_out(_pid(repo, "--json"))
275 assert d["files_changed"] == 2
276
277 def test_json_files_changed_is_int(self, tmp_path: pathlib.Path) -> None:
278 repo = _init_repo(tmp_path)
279 oid = _write_obj(repo, b"x")
280 _commit(repo, "init", {"f.py": oid})
281 d = _json_out(_pid(repo, "--json"))
282 assert isinstance(d["files_changed"], int)
283
284 def test_json_has_stable_field(self, tmp_path: pathlib.Path) -> None:
285 repo = _init_repo(tmp_path)
286 oid = _write_obj(repo, b"x")
287 _commit(repo, "init", {"f.py": oid})
288 d = _json_out(_pid(repo, "--json"))
289 assert "stable" in d
290
291 def test_json_stable_false_by_default(self, tmp_path: pathlib.Path) -> None:
292 repo = _init_repo(tmp_path)
293 oid = _write_obj(repo, b"x")
294 _commit(repo, "init", {"f.py": oid})
295 d = _json_out(_pid(repo, "--json"))
296 assert d["stable"] is False
297
298 def test_json_stable_true_with_flag(self, tmp_path: pathlib.Path) -> None:
299 repo = _init_repo(tmp_path)
300 oid = _write_obj(repo, b"x")
301 _commit(repo, "init", {"f.py": oid})
302 d = _json_out(_pid(repo, "--json", "--stable"))
303 assert d["stable"] is True
304
305 def test_stable_flag_produces_different_patch_id_for_ws_diff(
306 self, tmp_path: pathlib.Path
307 ) -> None:
308 """--stable and no-flag produce different IDs for trailing-whitespace diff."""
309 repo = _init_repo(tmp_path)
310 oid_base = _write_obj(repo, b"x = 1\n")
311 c1 = _commit(repo, "c1", {"f.py": oid_base})
312 oid_ws = _write_obj(repo, b"x = 1 \n")
313 _commit(repo, "c2 ws", {"f.py": oid_ws}, parent=c1)
314 d_normal = _json_out(_pid(repo, "--json"))
315 d_stable = _json_out(_pid(repo, "--json", "--stable"))
316 assert d_normal["patch_id"] != d_stable["patch_id"]
317
318
319 # ---------------------------------------------------------------------------
320 # Existing JSON fields still present [GREEN]
321 # ---------------------------------------------------------------------------
322
323 class TestJsonGreen:
324 def test_commit_id_present(self, tmp_path: pathlib.Path) -> None:
325 repo = _init_repo(tmp_path)
326 oid = _write_obj(repo, b"x")
327 _commit(repo, "init", {"f.py": oid})
328 d = _json_out(_pid(repo, "--json"))
329 assert "commit_id" in d
330
331 def test_patch_id_present(self, tmp_path: pathlib.Path) -> None:
332 repo = _init_repo(tmp_path)
333 oid = _write_obj(repo, b"x")
334 _commit(repo, "init", {"f.py": oid})
335 d = _json_out(_pid(repo, "--json"))
336 assert "patch_id" in d
337
338 def test_patch_id_is_64_hex(self, tmp_path: pathlib.Path) -> None:
339 repo = _init_repo(tmp_path)
340 oid = _write_obj(repo, b"x")
341 _commit(repo, "init", {"f.py": oid})
342 d = _json_out(_pid(repo, "--json"))
343 assert d["patch_id"].startswith("sha256:") and len(d["patch_id"]) == 71
344 assert all(c in "0123456789abcdef" for c in split_id(d["patch_id"])[1])
345
346 def test_subject_present(self, tmp_path: pathlib.Path) -> None:
347 repo = _init_repo(tmp_path)
348 oid = _write_obj(repo, b"x")
349 _commit(repo, "feat: hello world", {"f.py": oid})
350 d = _json_out(_pid(repo, "--json"))
351 assert d["subject"] == "feat: hello world"
352
353 def test_commit_id_matches_head(self, tmp_path: pathlib.Path) -> None:
354 repo = _init_repo(tmp_path)
355 oid = _write_obj(repo, b"x")
356 cid = _commit(repo, "init", {"f.py": oid})
357 d = _json_out(_pid(repo, "--json"))
358 assert d["commit_id"] == cid
359
360 def test_same_diff_same_patch_id(self, tmp_path: pathlib.Path) -> None:
361 """Cherry-pick detection: same logical change → same patch_id."""
362 repo = _init_repo(tmp_path)
363 oid_a = _write_obj(repo, b"v1\n")
364 c1 = _commit(repo, "c1", {"f.py": oid_a})
365 oid_b = _write_obj(repo, b"v2\n")
366 _commit(repo, "c2", {"f.py": oid_b}, parent=c1)
367 d1 = _json_out(_pid(repo, "--json"))
368
369 # Second repo with identical diff
370 repo2 = _init_repo(tmp_path / "repo2")
371 _write_obj(repo2, b"v1\n")
372 c1b = _commit(repo2, "c1", {"f.py": oid_a})
373 _write_obj(repo2, b"v2\n")
374 _commit(repo2, "c2 clone", {"f.py": oid_b}, parent=c1b)
375 d2 = _json_out(_pid(repo2, "--json"))
376
377 assert d1["patch_id"] == d2["patch_id"]
378
379 def test_different_diff_different_patch_id(self, tmp_path: pathlib.Path) -> None:
380 repo = _init_repo(tmp_path)
381 oid_a = _write_obj(repo, b"v1\n")
382 c1 = _commit(repo, "c1", {"f.py": oid_a})
383 oid_b = _write_obj(repo, b"v2\n")
384 _commit(repo, "c2", {"f.py": oid_b}, parent=c1)
385 d1 = _json_out(_pid(repo, "--json"))
386
387 repo2 = _init_repo(tmp_path / "repo2")
388 _write_obj(repo2, b"v1\n")
389 c1b = _commit(repo2, "c1", {"f.py": oid_a})
390 oid_c = _write_obj(repo2, b"completely different content\n")
391 _commit(repo2, "c2 different", {"f.py": oid_c}, parent=c1b)
392 d2 = _json_out(_pid(repo2, "--json"))
393
394 assert d1["patch_id"] != d2["patch_id"]
395
396 def test_explicit_commit_id_ref(self, tmp_path: pathlib.Path) -> None:
397 repo = _init_repo(tmp_path)
398 oid = _write_obj(repo, b"x")
399 cid = _commit(repo, "init", {"f.py": oid})
400 d = _json_out(_pid(repo, cid, "--json"))
401 assert d["commit_id"] == cid
402
403 def test_branch_name_ref(self, tmp_path: pathlib.Path) -> None:
404 repo = _init_repo(tmp_path)
405 oid = _write_obj(repo, b"x")
406 _commit(repo, "init", {"f.py": oid})
407 d = _json_out(_pid(repo, "main", "--json"))
408 assert "patch_id" in d
409
410
411 # ---------------------------------------------------------------------------
412 # Text output format [GREEN]
413 # ---------------------------------------------------------------------------
414
415 class TestTextOutput:
416 def test_text_format_two_parts(self, tmp_path: pathlib.Path) -> None:
417 repo = _init_repo(tmp_path)
418 oid = _write_obj(repo, b"x")
419 _commit(repo, "init", {"f.py": oid})
420 r = _pid(repo)
421 assert r.exit_code == 0
422 parts = r.output.strip().split()
423 assert len(parts) == 2
424
425 def test_text_patch_id_is_hex(self, tmp_path: pathlib.Path) -> None:
426 repo = _init_repo(tmp_path)
427 oid = _write_obj(repo, b"x")
428 _commit(repo, "init", {"f.py": oid})
429 parts = _pid(repo).output.strip().split()
430 assert parts[0].startswith("sha256:") and len(parts[0]) == 71
431 assert all(c in "0123456789abcdef" for c in split_id(parts[0])[1])
432
433 def test_text_commit_id_matches_json(self, tmp_path: pathlib.Path) -> None:
434 repo = _init_repo(tmp_path)
435 oid = _write_obj(repo, b"x")
436 _commit(repo, "init", {"f.py": oid})
437 text_parts = _pid(repo).output.strip().split()
438 json_d = _json_out(_pid(repo, "--json"))
439 assert text_parts[1] == json_d["commit_id"]
440 assert text_parts[0] == json_d["patch_id"]
441
442
443 # ---------------------------------------------------------------------------
444 # files_changed correctness [RED]
445 # ---------------------------------------------------------------------------
446
447 class TestFilesChanged:
448 def test_initial_commit_all_files_counted(self, tmp_path: pathlib.Path) -> None:
449 repo = _init_repo(tmp_path)
450 oid_a = _write_obj(repo, b"a")
451 oid_b = _write_obj(repo, b"b")
452 oid_c = _write_obj(repo, b"c")
453 _commit(repo, "init", {"a.py": oid_a, "b.py": oid_b, "c.py": oid_c})
454 d = _json_out(_pid(repo, "--json"))
455 assert d["files_changed"] == 3
456
457 def test_deletion_counted(self, tmp_path: pathlib.Path) -> None:
458 repo = _init_repo(tmp_path)
459 oid = _write_obj(repo, b"gone")
460 c1 = _commit(repo, "c1", {"old.py": oid})
461 _commit(repo, "c2 delete", {}, parent=c1)
462 d = _json_out(_pid(repo, "--json"))
463 assert d["files_changed"] == 1
464
465 def test_modification_counted(self, tmp_path: pathlib.Path) -> None:
466 repo = _init_repo(tmp_path)
467 oid_v1 = _write_obj(repo, b"v1")
468 c1 = _commit(repo, "c1", {"f.py": oid_v1})
469 oid_v2 = _write_obj(repo, b"v2")
470 _commit(repo, "c2 mod", {"f.py": oid_v2}, parent=c1)
471 d = _json_out(_pid(repo, "--json"))
472 assert d["files_changed"] == 1
473
474 def test_unchanged_files_not_counted(self, tmp_path: pathlib.Path) -> None:
475 repo = _init_repo(tmp_path)
476 oid_keep = _write_obj(repo, b"keep")
477 oid_chg = _write_obj(repo, b"v1")
478 c1 = _commit(repo, "c1", {"keep.py": oid_keep, "chg.py": oid_chg})
479 oid_chg2 = _write_obj(repo, b"v2")
480 _commit(repo, "c2", {"keep.py": oid_keep, "chg.py": oid_chg2}, parent=c1)
481 d = _json_out(_pid(repo, "--json"))
482 assert d["files_changed"] == 1
483
484 def test_no_op_commit_zero_files_changed(self, tmp_path: pathlib.Path) -> None:
485 repo = _init_repo(tmp_path)
486 oid = _write_obj(repo, b"same")
487 c1 = _commit(repo, "c1", {"f.py": oid})
488 _commit(repo, "c2 noop", {"f.py": oid}, parent=c1)
489 d = _json_out(_pid(repo, "--json"))
490 assert d["files_changed"] == 0
491
492
493 # ---------------------------------------------------------------------------
494 # Error paths [GREEN]
495 # ---------------------------------------------------------------------------
496
497 class TestErrors:
498 def test_empty_repo_exits_nonzero(self, tmp_path: pathlib.Path) -> None:
499 repo = _init_repo(tmp_path)
500 r = _pid(repo, "--json")
501 assert r.exit_code != 0
502
503 def test_bad_ref_exits_nonzero(self, tmp_path: pathlib.Path) -> None:
504 repo = _init_repo(tmp_path)
505 oid = _write_obj(repo, b"x")
506 _commit(repo, "init", {"f.py": oid})
507 r = _pid(repo, "no-such-ref", "--json")
508 assert r.exit_code != 0
509
510 def test_no_traceback_on_bad_ref(self, tmp_path: pathlib.Path) -> None:
511 repo = _init_repo(tmp_path)
512 oid = _write_obj(repo, b"x")
513 _commit(repo, "init", {"f.py": oid})
514 r = _pid(repo, "no-such-ref")
515 assert "Traceback" not in r.output
516 assert "Traceback" not in r.stderr
517
518 def test_error_to_stderr_not_stdout(self, tmp_path: pathlib.Path) -> None:
519 repo = _init_repo(tmp_path)
520 r = _pid(repo, "--json")
521 assert r.exit_code != 0
522 assert "❌" in r.stderr or r.exit_code != 0
523
524
525 # ---------------------------------------------------------------------------
526 # Security [GREEN]
527 # ---------------------------------------------------------------------------
528
529 class TestSecurity:
530 def test_ansi_in_ref_rejected(self, tmp_path: pathlib.Path) -> None:
531 repo = _init_repo(tmp_path)
532 oid = _write_obj(repo, b"x")
533 _commit(repo, "init", {"f.py": oid})
534 assert _pid(repo, "\x1b[31mbad\x1b[0m").exit_code != 0
535
536 def test_null_byte_in_ref_rejected(self, tmp_path: pathlib.Path) -> None:
537 repo = _init_repo(tmp_path)
538 oid = _write_obj(repo, b"x")
539 _commit(repo, "init", {"f.py": oid})
540 assert _pid(repo, "main\x00malicious").exit_code != 0
541
542 def test_path_traversal_in_ref_rejected(self, tmp_path: pathlib.Path) -> None:
543 repo = _init_repo(tmp_path)
544 oid = _write_obj(repo, b"x")
545 _commit(repo, "init", {"f.py": oid})
546 assert _pid(repo, "../../etc/passwd").exit_code != 0
547
548 def test_very_long_ref_rejected(self, tmp_path: pathlib.Path) -> None:
549 repo = _init_repo(tmp_path)
550 oid = _write_obj(repo, b"x")
551 _commit(repo, "init", {"f.py": oid})
552 assert _pid(repo, "a" * 300).exit_code != 0
553
554 def test_no_traceback_on_ansi_ref(self, tmp_path: pathlib.Path) -> None:
555 repo = _init_repo(tmp_path)
556 oid = _write_obj(repo, b"x")
557 _commit(repo, "init", {"f.py": oid})
558 r = _pid(repo, "\x1b[31mbad\x1b[0m")
559 assert "Traceback" not in r.output
560 assert "Traceback" not in r.stderr
561
562
563 # ---------------------------------------------------------------------------
564 # Data integrity [GREEN]
565 # ---------------------------------------------------------------------------
566
567 class TestDataIntegrity:
568 def test_patch_id_changes_when_content_changes(self, tmp_path: pathlib.Path) -> None:
569 repo = _init_repo(tmp_path)
570 oid_v1 = _write_obj(repo, b"version 1\n")
571 c1 = _commit(repo, "c1", {"f.py": oid_v1})
572 oid_v2 = _write_obj(repo, b"version 2\n")
573 _commit(repo, "c2", {"f.py": oid_v2}, parent=c1)
574 d1 = _json_out(_pid(repo, c1, "--json"))
575
576 # Change HEAD to c2 by making another commit
577 oid_v3 = _write_obj(repo, b"version 3\n")
578 c3 = _commit(repo, "c3", {"f.py": oid_v3}, parent=c1)
579 # re-point HEAD ref directly (two different commits from same parent)
580 (heads_dir(repo) / "main").write_text(c3)
581 d3 = _json_out(_pid(repo, "--json"))
582 assert d1["patch_id"] != d3["patch_id"]
583
584 def test_adding_file_changes_patch_id(self, tmp_path: pathlib.Path) -> None:
585 repo = _init_repo(tmp_path)
586 oid_a = _write_obj(repo, b"a\n")
587 c1 = _commit(repo, "c1", {"a.py": oid_a})
588 oid_b = _write_obj(repo, b"b\n")
589 _commit(repo, "c2 add b", {"a.py": oid_a, "b.py": oid_b}, parent=c1)
590 d = _json_out(_pid(repo, "--json"))
591 assert d["files_changed"] == 1
592 assert d["patch_id"] is not None
593
594 def test_patch_id_stable_vs_unstable_differ_for_ws(self, tmp_path: pathlib.Path) -> None:
595 repo = _init_repo(tmp_path)
596 oid_base = _write_obj(repo, b"x = 1\n")
597 c1 = _commit(repo, "c1", {"f.py": oid_base})
598 oid_ws = _write_obj(repo, b"x = 1 \n")
599 _commit(repo, "c2", {"f.py": oid_ws}, parent=c1)
600 d_normal = _json_out(_pid(repo, "--json"))
601 d_stable = _json_out(_pid(repo, "--json", "--stable"))
602 assert d_normal["patch_id"] != d_stable["patch_id"]
603
604
605 # ---------------------------------------------------------------------------
606 # Performance [GREEN]
607 # ---------------------------------------------------------------------------
608
609 class TestPerformance:
610 def test_duration_ms_under_two_seconds(self, tmp_path: pathlib.Path) -> None:
611 repo = _init_repo(tmp_path)
612 manifest: dict[str, str] = {}
613 for i in range(20):
614 content = f"# module {i}\n" .encode() * 50
615 oid = _write_obj(repo, content)
616 manifest[f"src/file_{i:02d}.py"] = oid
617 _commit(repo, "feat: 20 files", manifest)
618 d = _json_out(_pid(repo, "--json"))
619 assert d["duration_ms"] < 2000.0
620
621 def test_duration_ms_non_negative(self, tmp_path: pathlib.Path) -> None:
622 repo = _init_repo(tmp_path)
623 oid = _write_obj(repo, b"x")
624 _commit(repo, "init", {"f.py": oid})
625 d = _json_out(_pid(repo, "--json"))
626 assert d["duration_ms"] >= 0.0
627
628
629 # ---------------------------------------------------------------------------
630 # Stress [GREEN]
631 # ---------------------------------------------------------------------------
632
633 class TestStress:
634 def test_10_distinct_commits_10_distinct_patch_ids(self, tmp_path: pathlib.Path) -> None:
635 repo = _init_repo(tmp_path)
636 patch_ids: set[str] = set()
637 parent: str | None = None
638 for i in range(10):
639 content = f"value = {i}\n".encode()
640 oid = _write_obj(repo, content)
641 cid = _commit(repo, f"c{i}", {"file.py": oid}, parent=parent)
642 d = _json_out(_pid(repo, cid, "--json"))
643 patch_ids.add(d["patch_id"])
644 parent = cid
645 assert len(patch_ids) == 10
646
647 def test_50_file_commit(self, tmp_path: pathlib.Path) -> None:
648 repo = _init_repo(tmp_path)
649 manifest: dict[str, str] = {}
650 for i in range(50):
651 oid = _write_obj(repo, f"file {i}\n".encode() * 20)
652 manifest[f"f{i:03d}.py"] = oid
653 _commit(repo, "feat: 50 files", manifest)
654 r = _pid(repo, "--json")
655 assert r.exit_code == 0
656 d = _json_out(r)
657 assert d["files_changed"] == 50
658
659
660 # ---------------------------------------------------------------------------
661 # TestRegisterFlags — argparse-level verification
662 # ---------------------------------------------------------------------------
663
664
665 class TestRegisterFlags:
666 """Verify that register() wires --json / -j correctly."""
667
668 def _make_parser(self) -> "argparse.ArgumentParser":
669 import argparse
670 from muse.cli.commands.patch_id import register
671 ap = argparse.ArgumentParser()
672 subs = ap.add_subparsers()
673 register(subs)
674 return ap
675
676 def test_json_flag_long(self) -> None:
677 ns = self._make_parser().parse_args(["patch-id", "--json"])
678 assert ns.json_out is True
679
680 def test_j_alias(self) -> None:
681 ns = self._make_parser().parse_args(["patch-id", "-j"])
682 assert ns.json_out is True
683
684 def test_default_is_text(self) -> None:
685 ns = self._make_parser().parse_args(["patch-id"])
686 assert ns.json_out is False
687
688 def test_dest_is_json_out(self) -> None:
689 ns = self._make_parser().parse_args(["patch-id", "-j"])
690 assert hasattr(ns, "json_out")
691 assert not hasattr(ns, "fmt")
File History 4 commits
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 20 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 29 days ago