gabriel / muse public
test_cli_inspect.py python
742 lines 26.1 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 140 days ago
1 """Tests for all Low-level commands under ``muse …``.
2
3 Each command is tested via the Typer CliRunner so tests exercise the
4 full CLI stack including argument parsing, error handling, and JSON output
5 format. Commands are accessed directly at the top level.
6
7 The ``MUSE_REPO_ROOT`` env-var is used to point repo-discovery at the test
8 fixture without requiring ``os.chdir``.
9 """
10
11 from __future__ import annotations
12
13 import datetime
14 import json
15 import pathlib
16
17 import msgpack
18 import pytest
19 from tests.cli_test_helper import CliRunner
20
21 cli = None # argparse migration — CliRunner ignores this arg
22 from muse.core.errors import ExitCode
23 from muse.core.object_store import write_object
24 from muse.core.pack import build_mpack
25 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
26
27 from muse.core._types import Manifest, long_id
28 from muse.core.store import (
29 CommitRecord,
30 SnapshotRecord,
31 write_commit,
32 write_snapshot,
33 )
34
35 runner = CliRunner()
36
37 # ---------------------------------------------------------------------------
38 # Helpers
39 # ---------------------------------------------------------------------------
40
41
42 def _init_repo(path: pathlib.Path) -> pathlib.Path:
43 """Create a minimal .muse/ directory structure."""
44 muse = path / ".muse"
45 (muse / "commits").mkdir(parents=True)
46 (muse / "snapshots").mkdir(parents=True)
47 (muse / "objects").mkdir(parents=True)
48 (muse / "refs" / "heads").mkdir(parents=True)
49 muse.joinpath("HEAD").write_text("ref: refs/heads/main")
50 muse.joinpath("repo.json").write_text(
51 json.dumps({"repo_id": "test-repo-id", "domain": "generic"})
52 )
53 return path
54
55
56 def _make_object(repo: pathlib.Path, content: bytes) -> str:
57 import hashlib
58
59 oid = long_id(hashlib.sha256(content).hexdigest())
60 write_object(repo, oid, content)
61 return oid
62
63
64 def _make_snapshot(
65 repo: pathlib.Path, manifest: Manifest
66 ) -> SnapshotRecord:
67 """Write a SnapshotRecord whose ID is content-addressed from *manifest*."""
68 snap_id = compute_snapshot_id(manifest)
69 snap = SnapshotRecord(
70 snapshot_id=snap_id,
71 manifest=manifest,
72 created_at=datetime.datetime(2026, 3, 18, tzinfo=datetime.timezone.utc),
73 )
74 write_snapshot(repo, snap)
75 return snap
76
77
78 def _make_commit(
79 repo: pathlib.Path,
80 snapshot_id: str,
81 *,
82 branch: str = "main",
83 parent_commit_id: str | None = None,
84 message: str = "test commit",
85 ) -> CommitRecord:
86 """Write a CommitRecord whose ID is content-addressed from its fields."""
87 committed_at = datetime.datetime(2026, 3, 18, tzinfo=datetime.timezone.utc)
88 parent_ids = [parent_commit_id] if parent_commit_id else []
89 commit_id = compute_commit_id(parent_ids, snapshot_id, message, committed_at.isoformat())
90 rec = CommitRecord(
91 commit_id=commit_id,
92 repo_id="test-repo-id",
93 branch=branch,
94 snapshot_id=snapshot_id,
95 message=message,
96 committed_at=committed_at,
97 author="tester",
98 parent_commit_id=parent_commit_id,
99 )
100 write_commit(repo, rec)
101 return rec
102
103
104 def _set_head(repo: pathlib.Path, branch: str, commit_id: str) -> None:
105 ref = repo / ".muse" / "refs" / "heads" / branch
106 ref.parent.mkdir(parents=True, exist_ok=True)
107 ref.write_text(commit_id)
108
109
110 def _repo_env(repo: pathlib.Path) -> Manifest:
111 """Return env dict that sets MUSE_REPO_ROOT to the given path."""
112 return {"MUSE_REPO_ROOT": str(repo)}
113
114
115 # ---------------------------------------------------------------------------
116 # hash-object
117 # ---------------------------------------------------------------------------
118
119
120 class TestHashObject:
121 def test_hash_file_json_output(self, tmp_path: pathlib.Path) -> None:
122 f = tmp_path / "test.txt"
123 f.write_bytes(b"hello world")
124 result = runner.invoke(cli, ["hash-object", str(f)])
125 assert result.exit_code == 0, result.output
126 data = json.loads(result.stdout)
127 assert "object_id" in data
128 assert data["object_id"].startswith("sha256:")
129 assert len(data["object_id"]) == 71
130 assert data["stored"] is False
131
132 def test_hash_file_text_format(self, tmp_path: pathlib.Path) -> None:
133 f = tmp_path / "data.bin"
134 f.write_bytes(b"test bytes")
135 result = runner.invoke(
136 cli, ["hash-object", "--format", "text", str(f)]
137 )
138 assert result.exit_code == 0, result.output
139 assert result.stdout.strip().startswith("sha256:")
140 assert len(result.stdout.strip()) == 71
141
142 def test_hash_and_write(self, tmp_path: pathlib.Path) -> None:
143 repo = _init_repo(tmp_path / "repo")
144 f = repo / "sample.txt"
145 f.write_bytes(b"write me")
146 result = runner.invoke(
147 cli,
148 ["hash-object", "--write", str(f)],
149 env=_repo_env(repo),
150 catch_exceptions=False,
151 )
152 assert result.exit_code == 0, result.output
153 data = json.loads(result.stdout)
154 assert data["stored"] is True
155
156 def test_missing_file_errors(self, tmp_path: pathlib.Path) -> None:
157 result = runner.invoke(cli, ["hash-object", str(tmp_path / "no.txt")])
158 assert result.exit_code == ExitCode.USER_ERROR
159
160 def test_directory_errors(self, tmp_path: pathlib.Path) -> None:
161 result = runner.invoke(cli, ["hash-object", str(tmp_path)])
162 assert result.exit_code == ExitCode.USER_ERROR
163
164
165 # ---------------------------------------------------------------------------
166 # cat-object
167 # ---------------------------------------------------------------------------
168
169
170 class TestCatObject:
171 def test_cat_raw_bytes(self, tmp_path: pathlib.Path) -> None:
172 repo = _init_repo(tmp_path)
173 content = b"raw content data"
174 oid = _make_object(repo, content)
175 result = runner.invoke(
176 cli, ["cat-object", oid],
177 env=_repo_env(repo),
178 catch_exceptions=False,
179 )
180 assert result.exit_code == 0, result.output
181 assert result.stdout_bytes == content
182
183 def test_cat_info_format(self, tmp_path: pathlib.Path) -> None:
184 repo = _init_repo(tmp_path)
185 content = b"info content"
186 oid = _make_object(repo, content)
187 result = runner.invoke(
188 cli, ["cat-object", "--format", "info", oid],
189 env=_repo_env(repo),
190 )
191 assert result.exit_code == 0, result.output
192 data = json.loads(result.stdout)
193 assert data["object_id"] == oid
194 assert data["present"] is True
195 assert data["size_bytes"] == len(content)
196
197 def test_missing_object_errors(self, tmp_path: pathlib.Path) -> None:
198 repo = _init_repo(tmp_path)
199 result = runner.invoke(
200 cli, ["cat-object", "a" * 64],
201 env=_repo_env(repo),
202 )
203 assert result.exit_code == ExitCode.USER_ERROR
204
205 def test_missing_object_info_format(self, tmp_path: pathlib.Path) -> None:
206 repo = _init_repo(tmp_path)
207 oid = long_id("b" * 64)
208 result = runner.invoke(
209 cli, ["cat-object", "--format", "info", oid],
210 env=_repo_env(repo),
211 )
212 assert result.exit_code == ExitCode.USER_ERROR
213 data = json.loads(result.stdout)
214 assert data["present"] is False
215
216
217 # ---------------------------------------------------------------------------
218 # rev-parse
219 # ---------------------------------------------------------------------------
220
221
222 class TestRevParse:
223 def test_resolve_branch(self, tmp_path: pathlib.Path) -> None:
224 repo = _init_repo(tmp_path)
225 oid = _make_object(repo, b"data")
226 snap = _make_snapshot(repo, {"f": oid})
227 commit = _make_commit(repo, snap.snapshot_id)
228 _set_head(repo, "main", commit.commit_id)
229
230 result = runner.invoke(
231 cli, ["rev-parse", "main"],
232 env=_repo_env(repo),
233 )
234 assert result.exit_code == 0, result.output
235 data = json.loads(result.stdout)
236 assert data["commit_id"] == commit.commit_id
237 assert data["ref"] == "main"
238
239 def test_resolve_head(self, tmp_path: pathlib.Path) -> None:
240 repo = _init_repo(tmp_path)
241 oid = _make_object(repo, b"data")
242 snap = _make_snapshot(repo, {"f": oid})
243 commit = _make_commit(repo, snap.snapshot_id, message="resolve head")
244 _set_head(repo, "main", commit.commit_id)
245
246 result = runner.invoke(
247 cli, ["rev-parse", "HEAD"],
248 env=_repo_env(repo),
249 )
250 assert result.exit_code == 0, result.output
251 data = json.loads(result.stdout)
252 assert data["commit_id"] == commit.commit_id
253
254 def test_resolve_text_format(self, tmp_path: pathlib.Path) -> None:
255 repo = _init_repo(tmp_path)
256 oid = _make_object(repo, b"data")
257 snap = _make_snapshot(repo, {"f": oid})
258 commit = _make_commit(repo, snap.snapshot_id, message="text format")
259 _set_head(repo, "main", commit.commit_id)
260
261 result = runner.invoke(
262 cli, ["rev-parse", "--format", "text", "main"],
263 env=_repo_env(repo),
264 )
265 assert result.exit_code == 0, result.output
266 assert result.stdout.strip() == commit.commit_id
267
268 def test_unknown_ref_errors(self, tmp_path: pathlib.Path) -> None:
269 repo = _init_repo(tmp_path)
270 result = runner.invoke(
271 cli, ["rev-parse", "nonexistent"],
272 env=_repo_env(repo),
273 )
274 assert result.exit_code == ExitCode.USER_ERROR
275 data = json.loads(result.stdout)
276 assert data["commit_id"] is None
277
278
279 # ---------------------------------------------------------------------------
280 # ls-files
281 # ---------------------------------------------------------------------------
282
283
284 class TestLsFiles:
285 def test_lists_files_json(self, tmp_path: pathlib.Path) -> None:
286 repo = _init_repo(tmp_path)
287 oid = _make_object(repo, b"track data")
288 snap = _make_snapshot(repo, {"tracks/drums.mid": oid})
289 commit = _make_commit(repo, snap.snapshot_id)
290 _set_head(repo, "main", commit.commit_id)
291
292 result = runner.invoke(
293 cli, ["ls-files"],
294 env=_repo_env(repo),
295 )
296 assert result.exit_code == 0, result.output
297 data = json.loads(result.stdout)
298 assert data["file_count"] == 1
299 assert data["files"][0]["path"] == "tracks/drums.mid"
300 assert data["files"][0]["object_id"] == oid
301
302 def test_lists_files_text(self, tmp_path: pathlib.Path) -> None:
303 repo = _init_repo(tmp_path)
304 oid = _make_object(repo, b"data")
305 snap = _make_snapshot(repo, {"a.txt": oid})
306 commit = _make_commit(repo, snap.snapshot_id, message="ls files text")
307 _set_head(repo, "main", commit.commit_id)
308
309 result = runner.invoke(
310 cli, ["ls-files", "--format", "text"],
311 env=_repo_env(repo),
312 )
313 assert result.exit_code == 0, result.output
314 assert "a.txt" in result.stdout
315
316 def test_with_explicit_commit(self, tmp_path: pathlib.Path) -> None:
317 repo = _init_repo(tmp_path)
318 oid = _make_object(repo, b"data")
319 snap = _make_snapshot(repo, {"x.mid": oid})
320 commit = _make_commit(repo, snap.snapshot_id, message="explicit commit")
321
322 result = runner.invoke(
323 cli, ["ls-files", "--commit", commit.commit_id],
324 env=_repo_env(repo),
325 )
326 assert result.exit_code == 0, result.output
327 data = json.loads(result.stdout)
328 assert data["commit_id"] == commit.commit_id
329
330 def test_no_commits_errors(self, tmp_path: pathlib.Path) -> None:
331 repo = _init_repo(tmp_path)
332 result = runner.invoke(
333 cli, ["ls-files"],
334 env=_repo_env(repo),
335 )
336 assert result.exit_code == ExitCode.USER_ERROR
337
338
339 # ---------------------------------------------------------------------------
340 # read-commit
341 # ---------------------------------------------------------------------------
342
343
344 class TestReadCommit:
345 def test_reads_commit_json(self, tmp_path: pathlib.Path) -> None:
346 repo = _init_repo(tmp_path)
347 oid = _make_object(repo, b"data")
348 snap = _make_snapshot(repo, {"f": oid})
349 commit = _make_commit(repo, snap.snapshot_id, message="my message")
350
351 result = runner.invoke(
352 cli, ["read-commit", commit.commit_id],
353 env=_repo_env(repo),
354 catch_exceptions=False,
355 )
356 assert result.exit_code == 0, result.output
357 data = json.loads(result.stdout)
358 assert data["commit_id"] == commit.commit_id
359 assert data["message"] == "my message"
360 assert data["snapshot_id"] == snap.snapshot_id
361
362 def test_missing_commit_errors(self, tmp_path: pathlib.Path) -> None:
363 repo = _init_repo(tmp_path)
364 result = runner.invoke(
365 cli, ["read-commit", "z" * 64],
366 env=_repo_env(repo),
367 )
368 assert result.exit_code == ExitCode.USER_ERROR
369 data = json.loads(result.stdout)
370 assert "error" in data
371
372
373 # ---------------------------------------------------------------------------
374 # read-snapshot
375 # ---------------------------------------------------------------------------
376
377
378 class TestReadSnapshot:
379 def test_reads_snapshot_json(self, tmp_path: pathlib.Path) -> None:
380 repo = _init_repo(tmp_path)
381 oid = _make_object(repo, b"snap data")
382 snap = _make_snapshot(repo, {"track.mid": oid})
383
384 result = runner.invoke(
385 cli, ["read-snapshot", snap.snapshot_id],
386 env=_repo_env(repo),
387 catch_exceptions=False,
388 )
389 assert result.exit_code == 0, result.output
390 data = json.loads(result.stdout)
391 assert data["snapshot_id"] == snap.snapshot_id
392 assert data["file_count"] == 1
393 assert "track.mid" in data["manifest"]
394
395 def test_missing_snapshot_errors(self, tmp_path: pathlib.Path) -> None:
396 repo = _init_repo(tmp_path)
397 result = runner.invoke(
398 cli, ["read-snapshot", "nothere"],
399 env=_repo_env(repo),
400 )
401 assert result.exit_code == ExitCode.USER_ERROR
402
403
404 # ---------------------------------------------------------------------------
405 # commit-tree
406 # ---------------------------------------------------------------------------
407
408
409 class TestCommitTree:
410 def test_creates_commit_from_snapshot(self, tmp_path: pathlib.Path) -> None:
411 repo = _init_repo(tmp_path)
412 oid = _make_object(repo, b"content")
413 snap = _make_snapshot(repo, {"file.txt": oid})
414
415 result = runner.invoke(
416 cli,
417 [
418 "commit-tree",
419 "--snapshot", snap.snapshot_id,
420 "--message", "plumbing commit",
421 "--author", "bot",
422 ],
423 env=_repo_env(repo),
424 catch_exceptions=False,
425 )
426 assert result.exit_code == 0, result.output
427 data = json.loads(result.stdout)
428 assert "commit_id" in data
429 assert data["commit_id"].startswith("sha256:")
430 assert len(data["commit_id"]) == 71
431
432 def test_with_parent(self, tmp_path: pathlib.Path) -> None:
433 repo = _init_repo(tmp_path)
434 oid = _make_object(repo, b"data")
435 snap1 = _make_snapshot(repo, {"a": oid})
436 parent = _make_commit(repo, snap1.snapshot_id, message="parent commit")
437
438 snap2 = _make_snapshot(repo, {"b": oid})
439 result = runner.invoke(
440 cli,
441 [
442 "commit-tree",
443 "--snapshot", snap2.snapshot_id,
444 "--parent", parent.commit_id,
445 "--message", "child",
446 ],
447 env=_repo_env(repo),
448 catch_exceptions=False,
449 )
450 assert result.exit_code == 0, result.output
451 data = json.loads(result.stdout)
452 assert "commit_id" in data
453
454 def test_missing_snapshot_errors(self, tmp_path: pathlib.Path) -> None:
455 repo = _init_repo(tmp_path)
456 result = runner.invoke(
457 cli,
458 ["commit-tree", "--snapshot", "nosuch"],
459 env=_repo_env(repo),
460 )
461 assert result.exit_code == ExitCode.USER_ERROR
462
463
464 # ---------------------------------------------------------------------------
465 # update-ref
466 # ---------------------------------------------------------------------------
467
468
469 class TestUpdateRef:
470 def test_creates_branch_ref(self, tmp_path: pathlib.Path) -> None:
471 repo = _init_repo(tmp_path)
472 oid = _make_object(repo, b"x")
473 snap = _make_snapshot(repo, {"x": oid})
474 commit = _make_commit(repo, snap.snapshot_id, message="create ref")
475
476 result = runner.invoke(
477 cli,
478 ["update-ref", "feature", commit.commit_id],
479 env=_repo_env(repo),
480 catch_exceptions=False,
481 )
482 assert result.exit_code == 0, result.output
483 data = json.loads(result.stdout)
484 assert data["branch"] == "feature"
485 assert data["commit_id"] == commit.commit_id
486 ref = repo / ".muse" / "refs" / "heads" / "feature"
487 assert ref.read_text() == commit.commit_id
488
489 def test_updates_existing_ref(self, tmp_path: pathlib.Path) -> None:
490 repo = _init_repo(tmp_path)
491 oid = _make_object(repo, b"y")
492 snap = _make_snapshot(repo, {"y": oid})
493 commit1 = _make_commit(repo, snap.snapshot_id, message="first")
494 commit2 = _make_commit(repo, snap.snapshot_id, message="second", parent_commit_id=commit1.commit_id)
495 _set_head(repo, "main", commit1.commit_id)
496
497 result = runner.invoke(
498 cli,
499 ["update-ref", "main", commit2.commit_id],
500 env=_repo_env(repo),
501 )
502 assert result.exit_code == 0, result.output
503 data = json.loads(result.stdout)
504 assert data["previous"] == commit1.commit_id
505 assert data["commit_id"] == commit2.commit_id
506
507 def test_delete_ref(self, tmp_path: pathlib.Path) -> None:
508 repo = _init_repo(tmp_path)
509 _set_head(repo, "todelete", "x" * 64)
510 result = runner.invoke(
511 cli,
512 ["update-ref", "--delete", "todelete"],
513 env=_repo_env(repo),
514 )
515 assert result.exit_code == 0, result.output
516 data = json.loads(result.stdout)
517 assert data["deleted"] is True
518 ref = repo / ".muse" / "refs" / "heads" / "todelete"
519 assert not ref.exists()
520
521 def test_verify_commit_not_found_errors(self, tmp_path: pathlib.Path) -> None:
522 repo = _init_repo(tmp_path)
523 result = runner.invoke(
524 cli,
525 ["update-ref", "main", "0" * 64],
526 env=_repo_env(repo),
527 )
528 assert result.exit_code == ExitCode.USER_ERROR
529
530 def test_no_verify_skips_commit_check(self, tmp_path: pathlib.Path) -> None:
531 repo = _init_repo(tmp_path)
532 result = runner.invoke(
533 cli,
534 ["update-ref", "--no-verify", "feature", long_id("9" * 64)],
535 env=_repo_env(repo),
536 )
537 assert result.exit_code == 0, result.output
538 ref = repo / ".muse" / "refs" / "heads" / "feature"
539 assert ref.read_text() == long_id("9" * 64)
540
541
542 # ---------------------------------------------------------------------------
543 # commit-graph
544 # ---------------------------------------------------------------------------
545
546
547 class TestCommitGraph:
548 def test_linear_graph(self, tmp_path: pathlib.Path) -> None:
549 repo = _init_repo(tmp_path)
550 oid = _make_object(repo, b"data")
551 snap = _make_snapshot(repo, {"f": oid})
552 c1 = _make_commit(repo, snap.snapshot_id, message="first")
553 c2 = _make_commit(repo, snap.snapshot_id, message="second", parent_commit_id=c1.commit_id)
554 _set_head(repo, "main", c2.commit_id)
555
556 result = runner.invoke(
557 cli, ["commit-graph"],
558 env=_repo_env(repo),
559 catch_exceptions=False,
560 )
561 assert result.exit_code == 0, result.output
562 data = json.loads(result.stdout)
563 assert data["count"] == 2
564 commit_ids = [c["commit_id"] for c in data["commits"]]
565 assert c2.commit_id in commit_ids
566 assert c1.commit_id in commit_ids
567
568 def test_text_format(self, tmp_path: pathlib.Path) -> None:
569 repo = _init_repo(tmp_path)
570 oid = _make_object(repo, b"data")
571 snap = _make_snapshot(repo, {"f": oid})
572 commit = _make_commit(repo, snap.snapshot_id, message="text format commit")
573 _set_head(repo, "main", commit.commit_id)
574
575 result = runner.invoke(
576 cli, ["commit-graph", "--format", "text"],
577 env=_repo_env(repo),
578 )
579 assert result.exit_code == 0, result.output
580 assert commit.commit_id in result.stdout
581
582 def test_explicit_tip(self, tmp_path: pathlib.Path) -> None:
583 repo = _init_repo(tmp_path)
584 oid = _make_object(repo, b"data")
585 snap = _make_snapshot(repo, {"f": oid})
586 commit = _make_commit(repo, snap.snapshot_id, message="explicit tip commit")
587
588 result = runner.invoke(
589 cli, ["commit-graph", "--tip", commit.commit_id],
590 env=_repo_env(repo),
591 )
592 assert result.exit_code == 0, result.output
593 data = json.loads(result.stdout)
594 assert data["tip"] == commit.commit_id
595
596 def test_no_commits_errors(self, tmp_path: pathlib.Path) -> None:
597 repo = _init_repo(tmp_path)
598 result = runner.invoke(
599 cli, ["commit-graph"],
600 env=_repo_env(repo),
601 )
602 assert result.exit_code == ExitCode.USER_ERROR
603
604
605 # ---------------------------------------------------------------------------
606 # pack-objects
607 # ---------------------------------------------------------------------------
608
609
610 class TestPackObjects:
611 def test_packs_head(self, tmp_path: pathlib.Path) -> None:
612 repo = _init_repo(tmp_path)
613 oid = _make_object(repo, b"pack me")
614 snap = _make_snapshot(repo, {"f.mid": oid})
615 commit = _make_commit(repo, snap.snapshot_id, message="pack head")
616 _set_head(repo, "main", commit.commit_id)
617
618 result = runner.invoke(
619 cli, ["pack-objects", "HEAD"],
620 env=_repo_env(repo),
621 catch_exceptions=False,
622 )
623 assert result.exit_code == 0, result.output
624 data = msgpack.unpackb(result.stdout_bytes, raw=False)
625 assert "commits" in data
626 assert len(data["commits"]) >= 1
627
628 def test_packs_explicit_commit(self, tmp_path: pathlib.Path) -> None:
629 repo = _init_repo(tmp_path)
630 oid = _make_object(repo, b"explicit")
631 snap = _make_snapshot(repo, {"g": oid})
632 commit = _make_commit(repo, snap.snapshot_id, message="pack explicit")
633
634 result = runner.invoke(
635 cli, ["pack-objects", commit.commit_id],
636 env=_repo_env(repo),
637 catch_exceptions=False,
638 )
639 assert result.exit_code == 0, result.output
640 data = msgpack.unpackb(result.stdout_bytes, raw=False)
641 commit_ids = [c["commit_id"] for c in data["commits"]]
642 assert commit.commit_id in commit_ids
643
644 def test_no_commits_on_head_errors(self, tmp_path: pathlib.Path) -> None:
645 repo = _init_repo(tmp_path)
646 result = runner.invoke(
647 cli, ["pack-objects", "HEAD"],
648 env=_repo_env(repo),
649 )
650 assert result.exit_code == ExitCode.USER_ERROR
651
652
653 # ---------------------------------------------------------------------------
654 # unpack-objects
655 # ---------------------------------------------------------------------------
656
657
658 class TestUnpackObjects:
659 def test_unpacks_valid_bundle(self, tmp_path: pathlib.Path) -> None:
660 source = _init_repo(tmp_path / "src")
661 dest = _init_repo(tmp_path / "dst")
662
663 oid = _make_object(source, b"unpack me")
664 snap = _make_snapshot(source, {"h.mid": oid})
665 commit = _make_commit(source, snap.snapshot_id, message="unpack bundle")
666
667 bundle = build_mpack(source, [commit.commit_id])
668 bundle_bytes = msgpack.packb(bundle, use_bin_type=True)
669
670 result = runner.invoke(
671 cli,
672 ["unpack-objects"],
673 input=bundle_bytes,
674 env=_repo_env(dest),
675 catch_exceptions=False,
676 )
677 assert result.exit_code == 0, result.output
678 data = json.loads(result.stdout)
679 assert "objects_written" in data
680 assert data["commits_written"] == 1
681 assert data["objects_written"] == 1
682
683 def test_invalid_msgpack_errors(self, tmp_path: pathlib.Path) -> None:
684 repo = _init_repo(tmp_path)
685 result = runner.invoke(
686 cli,
687 ["unpack-objects"],
688 input=b"\xff\xff NOT VALID MSGPACK",
689 env=_repo_env(repo),
690 )
691 assert result.exit_code == ExitCode.USER_ERROR
692
693 def test_idempotent_unpack(self, tmp_path: pathlib.Path) -> None:
694 repo = _init_repo(tmp_path)
695 oid = _make_object(repo, b"idempotent")
696 snap = _make_snapshot(repo, {"i.txt": oid})
697 commit = _make_commit(repo, snap.snapshot_id, message="idempotent unpack")
698
699 bundle = build_mpack(repo, [commit.commit_id])
700 bundle_bytes = msgpack.packb(bundle, use_bin_type=True)
701
702 result1 = runner.invoke(
703 cli, ["unpack-objects"],
704 input=bundle_bytes,
705 env=_repo_env(repo),
706 )
707 assert result1.exit_code == 0, result1.output
708
709 result2 = runner.invoke(
710 cli, ["unpack-objects"],
711 input=bundle_bytes,
712 env=_repo_env(repo),
713 )
714 assert result2.exit_code == 0, result2.output
715 data = json.loads(result2.stdout)
716 assert data["objects_written"] == 0
717 assert data["objects_skipped"] == 1
718
719
720 # ---------------------------------------------------------------------------
721 # ls-remote
722 # ---------------------------------------------------------------------------
723
724
725 class TestLsRemote:
726 def test_bare_url_transport_error(self) -> None:
727 """Bare URL to a non-existent server produces exit code INTERNAL_ERROR."""
728 result = runner.invoke(
729 cli,
730 ["ls-remote", "https://localhost:0/no-such-server"],
731 )
732 assert result.exit_code == ExitCode.INTERNAL_ERROR
733
734 def test_non_url_non_remote_errors(self, tmp_path: pathlib.Path) -> None:
735 """A non-URL, non-configured remote name exits with code USER_ERROR."""
736 repo = _init_repo(tmp_path)
737 result = runner.invoke(
738 cli,
739 ["ls-remote", "not-a-url-or-remote"],
740 env=_repo_env(repo),
741 )
742 assert result.exit_code == ExitCode.USER_ERROR
File History 2 commits
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 140 days ago
sha256:a09b1b4f6838754495547f200aa0ce88e2f56ffc5b20b900f6f0cff2c3cdede9 fix(cursorignore): remove git-ism (.git/worktrees) Human minor 143 days ago