gabriel / muse public
test_cmd_read_snapshot.py python
279 lines 10.1 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 140 days ago
1 """Comprehensive tests for ``muse read-snapshot``.
2
3 Coverage tiers
4 --------------
5 - Unit: schema keys constant
6 - Integration: JSON/text, --no-manifest, --path-prefix, manifest presence
7 - Security: ANSI in snapshot IDs rejected, no traceback on bad input
8 - Stress: 1000-path manifest, 200 sequential reads
9 """
10 from __future__ import annotations
11
12 import datetime
13 import json
14 import pathlib
15
16 from muse.core.errors import ExitCode
17 from muse.core.snapshot import compute_snapshot_id
18 from muse.core.store import SnapshotRecord, write_snapshot
19 from tests.cli_test_helper import CliRunner, InvokeResult
20 from muse.core._types import long_id
21
22 runner = CliRunner()
23
24 _CREATED_AT: datetime.datetime = datetime.datetime(
25 2026, 3, 18, 12, 0, tzinfo=datetime.timezone.utc
26 )
27
28
29 # ---------------------------------------------------------------------------
30 # Helpers
31 # ---------------------------------------------------------------------------
32
33 def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path:
34 repo = tmp_path / "repo"
35 muse = repo / ".muse"
36 for sub in ("objects", "commits", "snapshots", "refs/heads"):
37 (muse / sub).mkdir(parents=True)
38 (muse / "HEAD").write_text("ref: refs/heads/main")
39 (muse / "repo.json").write_text(json.dumps({"repo_id": "test-repo", "domain": "code"}))
40 return repo
41
42
43 def _snap(
44 repo: pathlib.Path,
45 manifest: Manifest | None = None,
46 ) -> str:
47 """Write a snapshot with a real content-addressed ID; return the snapshot_id."""
48 m: Manifest = manifest or {}
49 snap_id = compute_snapshot_id(m)
50 rec = SnapshotRecord(
51 snapshot_id=snap_id,
52 manifest=m,
53 created_at=_CREATED_AT,
54 )
55 write_snapshot(repo, rec)
56 return snap_id
57
58
59 def _rs(repo: pathlib.Path, *args: str) -> InvokeResult:
60 from muse.cli.app import main as cli
61 return runner.invoke(
62 cli,
63 ["read-snapshot", *args],
64 env={"MUSE_REPO_ROOT": str(repo)},
65 )
66
67
68 def _fake_oid(n: int) -> str:
69 return format(n, "064x")
70
71
72 # ---------------------------------------------------------------------------
73 # Integration — JSON format
74 # ---------------------------------------------------------------------------
75
76
77 class TestJsonFormat:
78 def test_full_output_empty_manifest(self, tmp_path: pathlib.Path) -> None:
79 repo = _make_repo(tmp_path)
80 sid = _snap(repo)
81 result = _rs(repo, sid)
82 assert result.exit_code == 0
83 data = json.loads(result.output)
84 assert data["snapshot_id"] == sid
85 assert data["file_count"] == 0
86 assert data["manifest"] == {}
87
88 def test_manifest_paths_present(self, tmp_path: pathlib.Path) -> None:
89 repo = _make_repo(tmp_path)
90 oid = "0" * 64
91 sid = _snap(repo, {"src/main.py": oid, "tests/test_main.py": oid})
92 data = json.loads(_rs(repo, sid).output)
93 assert "src/main.py" in data["manifest"]
94 assert "tests/test_main.py" in data["manifest"]
95 assert data["file_count"] == 2
96
97 def test_json_flag_shorthand(self, tmp_path: pathlib.Path) -> None:
98 repo = _make_repo(tmp_path)
99 sid = _snap(repo, {"a.py": _fake_oid(1)})
100 result = _rs(repo, "--json", sid)
101 assert result.exit_code == 0
102 assert "snapshot_id" in json.loads(result.output)
103
104 def test_created_at_iso8601(self, tmp_path: pathlib.Path) -> None:
105 repo = _make_repo(tmp_path)
106 sid = _snap(repo, {"b.py": _fake_oid(2)})
107 data = json.loads(_rs(repo, sid).output)
108 datetime.datetime.fromisoformat(data["created_at"])
109
110 def test_file_count_reflects_manifest(self, tmp_path: pathlib.Path) -> None:
111 repo = _make_repo(tmp_path)
112 manifest = {f"file{i}.py": _fake_oid(i) for i in range(5)}
113 sid = _snap(repo, manifest)
114 data = json.loads(_rs(repo, sid).output)
115 assert data["file_count"] == 5
116
117
118 # ---------------------------------------------------------------------------
119 # Integration — text format
120 # ---------------------------------------------------------------------------
121
122
123 class TestTextFormat:
124 def test_text_contains_prefix(self, tmp_path: pathlib.Path) -> None:
125 repo = _make_repo(tmp_path)
126 sid = _snap(repo, {"c.py": _fake_oid(3)})
127 result = _rs(repo, "--format", "text", sid)
128 assert result.exit_code == 0
129 assert sid[:12] in result.output
130
131 def test_text_contains_file_count(self, tmp_path: pathlib.Path) -> None:
132 repo = _make_repo(tmp_path)
133 sid = _snap(repo, {"a.py": _fake_oid(1), "b.py": _fake_oid(2)})
134 result = _rs(repo, "--format", "text", sid)
135 assert "2 files" in result.output
136
137 def test_text_single_line(self, tmp_path: pathlib.Path) -> None:
138 repo = _make_repo(tmp_path)
139 sid = _snap(repo)
140 result = _rs(repo, "--format", "text", sid)
141 lines = [l for l in result.output.splitlines() if l.strip()]
142 assert len(lines) == 1
143
144
145 # ---------------------------------------------------------------------------
146 # Integration — --no-manifest
147 # ---------------------------------------------------------------------------
148
149
150 class TestNoManifest:
151 def test_manifest_absent(self, tmp_path: pathlib.Path) -> None:
152 repo = _make_repo(tmp_path)
153 sid = _snap(repo, {"a.py": _fake_oid(1)})
154 data = json.loads(_rs(repo, "--no-manifest", sid).output)
155 assert "manifest" not in data
156 assert data["file_count"] == 1
157
158 def test_snapshot_id_and_created_at_still_present(self, tmp_path: pathlib.Path) -> None:
159 repo = _make_repo(tmp_path)
160 sid = _snap(repo)
161 data = json.loads(_rs(repo, "--no-manifest", sid).output)
162 assert data["snapshot_id"] == sid
163 assert "created_at" in data
164
165 def test_no_manifest_with_text_errors(self, tmp_path: pathlib.Path) -> None:
166 repo = _make_repo(tmp_path)
167 sid = _snap(repo)
168 result = _rs(repo, "--no-manifest", "--format", "text", sid)
169 assert result.exit_code == ExitCode.USER_ERROR
170
171
172 # ---------------------------------------------------------------------------
173 # Integration — --path-prefix
174 # ---------------------------------------------------------------------------
175
176
177 class TestPathPrefix:
178 def test_prefix_filters_manifest(self, tmp_path: pathlib.Path) -> None:
179 repo = _make_repo(tmp_path)
180 sid = _snap(repo, {
181 "src/a.py": _fake_oid(1),
182 "src/b.py": _fake_oid(2),
183 "tests/c.py": _fake_oid(3),
184 })
185 data = json.loads(_rs(repo, "--path-prefix", "src/", sid).output)
186 assert set(data["manifest"].keys()) == {"src/a.py", "src/b.py"}
187 assert data["file_count"] == 2
188
189 def test_prefix_no_match_returns_empty(self, tmp_path: pathlib.Path) -> None:
190 repo = _make_repo(tmp_path)
191 sid = _snap(repo, {"src/a.py": _fake_oid(1)})
192 data = json.loads(_rs(repo, "--path-prefix", "docs/", sid).output)
193 assert data["manifest"] == {}
194 assert data["file_count"] == 0
195
196 def test_prefix_with_text_errors(self, tmp_path: pathlib.Path) -> None:
197 repo = _make_repo(tmp_path)
198 sid = _snap(repo)
199 result = _rs(repo, "--path-prefix", "src/", "--format", "text", sid)
200 assert result.exit_code == ExitCode.USER_ERROR
201
202
203 # ---------------------------------------------------------------------------
204 # Error cases
205 # ---------------------------------------------------------------------------
206
207
208 class TestErrors:
209 def test_missing_snapshot_errors(self, tmp_path: pathlib.Path) -> None:
210 repo = _make_repo(tmp_path)
211 # Valid sha256: format but not present in the store — must get "not found".
212 result = _rs(repo, long_id("dead" + "beef" * 15))
213 assert result.exit_code == ExitCode.USER_ERROR
214 data = json.loads(result.output)
215 assert "not found" in data["error"]
216
217 def test_invalid_snapshot_id_errors(self, tmp_path: pathlib.Path) -> None:
218 repo = _make_repo(tmp_path)
219 result = _rs(repo, "not-valid")
220 assert result.exit_code == ExitCode.USER_ERROR
221
222 def test_unknown_format_errors(self, tmp_path: pathlib.Path) -> None:
223 repo = _make_repo(tmp_path)
224 sid = _snap(repo)
225 result = _rs(repo, "--format", "msgpack", sid)
226 assert result.exit_code == ExitCode.USER_ERROR
227
228
229 # ---------------------------------------------------------------------------
230 # Security
231 # ---------------------------------------------------------------------------
232
233
234 class TestSecurity:
235 def test_ansi_in_snapshot_id_rejected(self, tmp_path: pathlib.Path) -> None:
236 repo = _make_repo(tmp_path)
237 result = _rs(repo, "\x1b[31m" + "a" * 64)
238 assert result.exit_code == ExitCode.USER_ERROR
239
240 def test_no_traceback_on_bad_id(self, tmp_path: pathlib.Path) -> None:
241 repo = _make_repo(tmp_path)
242 result = _rs(repo, "bad-id")
243 assert "Traceback" not in result.output
244
245
246 # ---------------------------------------------------------------------------
247 # Stress
248 # ---------------------------------------------------------------------------
249
250
251 class TestStress:
252 def test_1000_file_manifest(self, tmp_path: pathlib.Path) -> None:
253 repo = _make_repo(tmp_path)
254 manifest = {f"src/module{i:04d}.py": _fake_oid(i) for i in range(1000)}
255 sid = _snap(repo, manifest)
256 result = _rs(repo, sid)
257 assert result.exit_code == 0
258 data = json.loads(result.output)
259 assert data["file_count"] == 1000
260 assert len(data["manifest"]) == 1000
261
262 def test_1000_file_manifest_no_manifest(self, tmp_path: pathlib.Path) -> None:
263 repo = _make_repo(tmp_path)
264 manifest = {f"src/module{i:04d}.py": _fake_oid(i) for i in range(1000)}
265 sid = _snap(repo, manifest)
266 result = _rs(repo, "--no-manifest", sid)
267 assert result.exit_code == 0
268 data = json.loads(result.output)
269 assert data["file_count"] == 1000
270 assert "manifest" not in data
271
272 def test_200_sequential_reads(self, tmp_path: pathlib.Path) -> None:
273 repo = _make_repo(tmp_path)
274 sid = _snap(repo, {"a.py": _fake_oid(0)})
275 for i in range(200):
276 result = _rs(repo, sid)
277 assert result.exit_code == 0, f"failed at iteration {i}"
278 data = json.loads(result.output)
279 assert data["file_count"] == 1
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