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