gabriel / muse public
test_plumbing_verify_pack.py python
180 lines 6.6 KB
86000da9 fix: replace typer CliRunner with argparse-compatible test helper Gabriel Cardona <gabriel@tellurstori.com> 1d ago
1 """Tests for muse plumbing verify-pack."""
2
3 from __future__ import annotations
4
5 import base64
6 import datetime
7 import hashlib
8 import json
9 import pathlib
10
11 import pytest
12 from tests.cli_test_helper import CliRunner
13
14 cli = None # argparse migration — CliRunner ignores this arg
15 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
16
17 runner = CliRunner()
18
19
20 def _sha(tag: str) -> str:
21 return hashlib.sha256(tag.encode()).hexdigest()
22
23
24 def _init_repo(path: pathlib.Path) -> pathlib.Path:
25 muse = path / ".muse"
26 (muse / "commits").mkdir(parents=True)
27 (muse / "snapshots").mkdir(parents=True)
28 (muse / "objects").mkdir(parents=True)
29 (muse / "refs" / "heads").mkdir(parents=True)
30 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
31 (muse / "repo.json").write_text(
32 json.dumps({"repo_id": "test-repo", "domain": "midi"}), encoding="utf-8"
33 )
34 return path
35
36
37 def _env(repo: pathlib.Path) -> dict[str, str]:
38 return {"MUSE_REPO_ROOT": str(repo)}
39
40
41 def _make_bundle(objects: list[dict[str, str]] | None = None) -> str:
42 """Build a minimal PackBundle JSON string for testing."""
43 bundle: dict[str, list[dict[str, str]]] = {
44 "objects": objects or [],
45 "commits": [],
46 "snapshots": [],
47 }
48 return json.dumps(bundle)
49
50
51 def _good_object() -> dict[str, str]:
52 """Return an ObjectPayload dict with a valid hash."""
53 data = b"hello world"
54 oid = hashlib.sha256(data).hexdigest()
55 return {"object_id": oid, "content_b64": base64.b64encode(data).decode()}
56
57
58 def _bad_hash_object() -> dict[str, str]:
59 """Return an ObjectPayload dict where the hash does NOT match the content."""
60 data = b"hello world"
61 wrong_oid = "a" * 64
62 return {"object_id": wrong_oid, "content_b64": base64.b64encode(data).decode()}
63
64
65 class TestVerifyPack:
66 def test_empty_bundle_passes(self, tmp_path: pathlib.Path) -> None:
67 _init_repo(tmp_path)
68 bundle_file = tmp_path / "bundle.json"
69 bundle_file.write_text(_make_bundle(), encoding="utf-8")
70 result = runner.invoke(
71 cli, ["plumbing", "verify-pack", "--file", str(bundle_file)], env=_env(tmp_path)
72 )
73 assert result.exit_code == 0
74 data = json.loads(result.output)
75 assert data["all_ok"] is True
76 assert data["failures"] == []
77
78 def test_good_objects_pass(self, tmp_path: pathlib.Path) -> None:
79 _init_repo(tmp_path)
80 bundle_file = tmp_path / "bundle.json"
81 bundle_file.write_text(_make_bundle([_good_object()]), encoding="utf-8")
82 result = runner.invoke(
83 cli,
84 ["plumbing", "verify-pack", "--file", str(bundle_file), "--no-local"],
85 env=_env(tmp_path),
86 )
87 assert result.exit_code == 0
88 data = json.loads(result.output)
89 assert data["all_ok"] is True
90 assert data["objects_checked"] == 1
91
92 def test_bad_hash_detected(self, tmp_path: pathlib.Path) -> None:
93 _init_repo(tmp_path)
94 bundle_file = tmp_path / "bundle.json"
95 bundle_file.write_text(_make_bundle([_bad_hash_object()]), encoding="utf-8")
96 result = runner.invoke(
97 cli,
98 ["plumbing", "verify-pack", "--file", str(bundle_file), "--no-local"],
99 env=_env(tmp_path),
100 )
101 assert result.exit_code != 0
102 data = json.loads(result.output)
103 assert data["all_ok"] is False
104 assert len(data["failures"]) == 1
105 assert data["failures"][0]["kind"] == "object"
106 assert "hash mismatch" in data["failures"][0]["error"]
107
108 def test_text_format_clean(self, tmp_path: pathlib.Path) -> None:
109 _init_repo(tmp_path)
110 bundle_file = tmp_path / "bundle.json"
111 bundle_file.write_text(_make_bundle(), encoding="utf-8")
112 result = runner.invoke(
113 cli,
114 ["plumbing", "verify-pack", "--file", str(bundle_file), "--format", "text", "--no-local"],
115 env=_env(tmp_path),
116 )
117 assert result.exit_code == 0
118 assert "all_ok=True" in result.output
119
120 def test_text_format_failure(self, tmp_path: pathlib.Path) -> None:
121 _init_repo(tmp_path)
122 bundle_file = tmp_path / "bundle.json"
123 bundle_file.write_text(_make_bundle([_bad_hash_object()]), encoding="utf-8")
124 result = runner.invoke(
125 cli,
126 ["plumbing", "verify-pack", "--file", str(bundle_file), "--format", "text", "--no-local"],
127 env=_env(tmp_path),
128 )
129 assert result.exit_code != 0
130 assert "FAIL" in result.output
131
132 def test_quiet_mode_clean_exits_zero(self, tmp_path: pathlib.Path) -> None:
133 _init_repo(tmp_path)
134 bundle_file = tmp_path / "bundle.json"
135 bundle_file.write_text(_make_bundle(), encoding="utf-8")
136 result = runner.invoke(
137 cli,
138 ["plumbing", "verify-pack", "--file", str(bundle_file), "--quiet", "--no-local"],
139 env=_env(tmp_path),
140 )
141 assert result.exit_code == 0
142 assert result.output.strip() == ""
143
144 def test_quiet_mode_failure_exits_nonzero(self, tmp_path: pathlib.Path) -> None:
145 _init_repo(tmp_path)
146 bundle_file = tmp_path / "bundle.json"
147 bundle_file.write_text(_make_bundle([_bad_hash_object()]), encoding="utf-8")
148 result = runner.invoke(
149 cli,
150 ["plumbing", "verify-pack", "--file", str(bundle_file), "-q", "--no-local"],
151 env=_env(tmp_path),
152 )
153 assert result.exit_code != 0
154 assert result.output.strip() == ""
155
156 def test_malformed_json_errors(self, tmp_path: pathlib.Path) -> None:
157 _init_repo(tmp_path)
158 bundle_file = tmp_path / "bundle.json"
159 bundle_file.write_text("not valid json{{", encoding="utf-8")
160 result = runner.invoke(
161 cli, ["plumbing", "verify-pack", "--file", str(bundle_file)], env=_env(tmp_path)
162 )
163 assert result.exit_code != 0
164
165 def test_multiple_objects_one_bad(self, tmp_path: pathlib.Path) -> None:
166 """Mix of good and bad objects — all_ok should be False."""
167 _init_repo(tmp_path)
168 objs = [_good_object(), _bad_hash_object()]
169 bundle_file = tmp_path / "bundle.json"
170 bundle_file.write_text(_make_bundle(objs), encoding="utf-8")
171 result = runner.invoke(
172 cli,
173 ["plumbing", "verify-pack", "--file", str(bundle_file), "--no-local"],
174 env=_env(tmp_path),
175 )
176 assert result.exit_code != 0
177 data = json.loads(result.output)
178 assert data["all_ok"] is False
179 assert data["objects_checked"] == 2
180 assert len(data["failures"]) == 1