gabriel / muse public
test_algo_prefix_globs.py python
321 lines 11.6 KB
Raw
1 """Regression tests for algo-prefixed filesystem glob patterns.
2
3 After the algo-prefix sprint every content-addressed store path gained an
4 algorithm directory segment and moved to the unified object store:
5
6 objects/sha256/<shard-2>/<rest> (was: commits/<hex>.msgpack, snapshots/<hex>.msgpack)
7
8 Any glob that scans only the top level of the commits/ or snapshots/ dir
9 silently returns an empty list. These tests catch that class of bug by
10 writing real records through the store, then asserting that every listing
11 and prefix-resolution function returns them.
12
13 Covered functions
14 -----------------
15 muse.core.store
16 get_all_commits
17 _find_commit_by_prefix
18 find_commits_by_prefix
19
20 muse.cli.commands.snapshot_cmd
21 _list_all_snapshots (internal listing helper)
22 _resolve_snapshot (full-id + prefix-scan paths)
23
24 muse.cli.commands.snapshot_diff
25 _resolve_snapshot_prefix (short sha256: prefix resolution)
26 _resolve_to_snapshot_id (end-to-end resolver)
27 """
28
29 from __future__ import annotations
30
31 import datetime
32 import json
33 import pathlib
34
35 import pytest
36
37 from muse.core.types import NULL_COMMIT_ID, fake_id, long_id, short_id
38 from muse.core.ids import hash_commit, hash_snapshot
39 from muse.core.paths import muse_dir
40 from muse.core.commits import (
41 CommitRecord,
42 find_commits_by_prefix,
43 get_all_commits,
44 write_commit,
45 )
46 from muse.core.snapshots import (
47 SnapshotRecord,
48 write_snapshot,
49 )
50 from muse.core.commits import _find_commit_by_prefix
51
52 # ---------------------------------------------------------------------------
53 # Helpers
54 # ---------------------------------------------------------------------------
55
56 _REPO_ID = fake_id("repo")
57 _NOW = datetime.datetime.now(datetime.timezone.utc)
58
59
60 def _init_repo(path: pathlib.Path) -> pathlib.Path:
61 dot_muse = muse_dir(path)
62 (dot_muse / "refs" / "heads").mkdir(parents=True)
63 (dot_muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
64 (dot_muse / "repo.json").write_text(
65 json.dumps({"repo_id": _REPO_ID, "domain": "code"}), encoding="utf-8"
66 )
67 return path
68
69
70 def _make_snapshot(repo: pathlib.Path, seed: str) -> SnapshotRecord:
71 manifest = {f"{seed}.py": fake_id("obj")}
72 sid = hash_snapshot(manifest)
73 snap = SnapshotRecord(
74 snapshot_id=sid,
75 manifest=manifest,
76 directories=[],
77 created_at=_NOW,
78 note=None,
79 )
80 write_snapshot(repo, snap)
81 return snap
82
83
84 def _make_commit(
85 repo: pathlib.Path,
86 snap: SnapshotRecord,
87 msg: str,
88 parent: str | None = None,
89 agent_id: str | None = None,
90 ) -> CommitRecord:
91
92 parents = [parent] if parent else []
93 cid = hash_commit(
94 parent_ids=parents,
95 snapshot_id=snap.snapshot_id,
96 message=msg,
97 committed_at_iso=_NOW.isoformat(),
98 )
99 c = CommitRecord(
100 commit_id=cid,
101 branch="main",
102 snapshot_id=snap.snapshot_id,
103 message=msg,
104 committed_at=_NOW,
105 parent_commit_id=parent,
106 agent_id=agent_id,
107 )
108 write_commit(repo, c)
109 return c
110
111
112 # ---------------------------------------------------------------------------
113 # store.get_all_commits
114 # ---------------------------------------------------------------------------
115
116
117 class TestGetAllCommits:
118 def test_returns_written_commit(self, tmp_path: pathlib.Path) -> None:
119 repo = _init_repo(tmp_path)
120 snap = _make_snapshot(repo, "alpha")
121 c = _make_commit(repo, snap, "first")
122 commits = get_all_commits(repo)
123 ids = [x.commit_id for x in commits]
124 assert c.commit_id in ids
125
126 def test_returns_all_written_commits(self, tmp_path: pathlib.Path) -> None:
127 repo = _init_repo(tmp_path)
128 snap_a = _make_snapshot(repo, "file_a")
129 snap_b = _make_snapshot(repo, "file_b")
130 snap_c = _make_snapshot(repo, "file_c")
131 c1 = _make_commit(repo, snap_a, "one")
132 c2 = _make_commit(repo, snap_b, "two", parent=c1.commit_id)
133 c3 = _make_commit(repo, snap_c, "three", parent=c2.commit_id)
134 commits = get_all_commits(repo)
135 ids = {x.commit_id for x in commits}
136 assert {c1.commit_id, c2.commit_id, c3.commit_id} == ids
137
138 def test_empty_repo_returns_empty_list(self, tmp_path: pathlib.Path) -> None:
139 repo = _init_repo(tmp_path)
140 assert get_all_commits(repo) == []
141
142 def test_missing_commits_dir_returns_empty_list(self, tmp_path: pathlib.Path) -> None:
143 repo = _init_repo(tmp_path)
144 assert get_all_commits(repo) == []
145
146
147 # ---------------------------------------------------------------------------
148 # store._find_commit_by_prefix / find_commits_by_prefix
149 # ---------------------------------------------------------------------------
150
151
152 class TestCommitPrefixScan:
153 def test_find_by_bare_hex_prefix(self, tmp_path: pathlib.Path) -> None:
154 repo = _init_repo(tmp_path)
155 snap = _make_snapshot(repo, "prefix_test")
156 c = _make_commit(repo, snap, "prefix commit")
157 bare_hex = long_id(c.commit_id, strip=True)
158 prefix = bare_hex[:12]
159 found = _find_commit_by_prefix(repo, prefix)
160 assert found is not None
161 assert found.commit_id == c.commit_id
162
163 def test_find_commits_by_prefix_returns_list(self, tmp_path: pathlib.Path) -> None:
164 repo = _init_repo(tmp_path)
165 snap = _make_snapshot(repo, "prefix_multi")
166 c = _make_commit(repo, snap, "multi prefix")
167 bare_hex = long_id(c.commit_id, strip=True)
168 results = find_commits_by_prefix(repo, bare_hex[:8])
169 assert any(x.commit_id == c.commit_id for x in results)
170
171 def test_no_match_returns_none(self, tmp_path: pathlib.Path) -> None:
172 repo = _init_repo(tmp_path)
173 assert _find_commit_by_prefix(repo, NULL_COMMIT_ID) is None
174
175 def test_find_commits_by_prefix_no_match_returns_empty(
176 self, tmp_path: pathlib.Path
177 ) -> None:
178 repo = _init_repo(tmp_path)
179 assert find_commits_by_prefix(repo, NULL_COMMIT_ID) == []
180
181
182 # ---------------------------------------------------------------------------
183 # snapshot_cmd._list_all_snapshots (internal listing helper)
184 # ---------------------------------------------------------------------------
185
186
187 class TestGetAllSnapshots:
188 def test_returns_written_snapshot(self, tmp_path: pathlib.Path) -> None:
189 from muse.cli.commands.snapshot_cmd import _list_all_snapshots
190
191 repo = _init_repo(tmp_path)
192 snap = _make_snapshot(repo, "listed")
193 results = _list_all_snapshots(repo)
194 ids = [s.snapshot_id for s in results]
195 assert snap.snapshot_id in ids
196
197 def test_returns_all_written_snapshots(self, tmp_path: pathlib.Path) -> None:
198 from muse.cli.commands.snapshot_cmd import _list_all_snapshots
199
200 repo = _init_repo(tmp_path)
201 s1 = _make_snapshot(repo, "snap_one")
202 s2 = _make_snapshot(repo, "snap_two")
203 s3 = _make_snapshot(repo, "snap_three")
204 results = _list_all_snapshots(repo)
205 ids = {s.snapshot_id for s in results}
206 assert {s1.snapshot_id, s2.snapshot_id, s3.snapshot_id} == ids
207
208 def test_empty_repo_returns_empty_list(self, tmp_path: pathlib.Path) -> None:
209 from muse.cli.commands.snapshot_cmd import _list_all_snapshots
210
211 repo = _init_repo(tmp_path)
212 assert _list_all_snapshots(repo) == []
213
214
215 # ---------------------------------------------------------------------------
216 # snapshot_cmd._resolve_snapshot — full ID path
217 # ---------------------------------------------------------------------------
218
219
220 class TestResolveSnapshotFullId:
221 def test_full_prefixed_id_resolves(self, tmp_path: pathlib.Path) -> None:
222 from muse.cli.commands.snapshot_cmd import _resolve_snapshot
223
224 repo = _init_repo(tmp_path)
225 snap = _make_snapshot(repo, "full_resolve")
226 result = _resolve_snapshot(repo, snap.snapshot_id)
227 assert result is not None
228 assert result.snapshot_id == snap.snapshot_id
229
230 def test_unknown_id_returns_none(self, tmp_path: pathlib.Path) -> None:
231 from muse.cli.commands.snapshot_cmd import _resolve_snapshot
232
233 repo = _init_repo(tmp_path)
234 _make_snapshot(repo, "some_snap")
235 result = _resolve_snapshot(repo, fake_id("f"))
236 assert result is None
237
238
239 # ---------------------------------------------------------------------------
240 # snapshot_cmd._resolve_snapshot — prefix-scan path (short sha256: prefix)
241 # ---------------------------------------------------------------------------
242
243
244 class TestResolveSnapshotShortPrefix:
245 def test_short_prefixed_id_resolves(self, tmp_path: pathlib.Path) -> None:
246 from muse.cli.commands.snapshot_cmd import _resolve_snapshot
247
248 repo = _init_repo(tmp_path)
249 snap = _make_snapshot(repo, "short_prefix")
250 short = short_id(snap.snapshot_id) # sha256:<12hex>
251 result = _resolve_snapshot(repo, short)
252 assert result is not None, f"prefix {short!r} must resolve; got None"
253 assert result.snapshot_id == snap.snapshot_id
254
255 def test_unrecognized_prefix_returns_none(self, tmp_path: pathlib.Path) -> None:
256 from muse.cli.commands.snapshot_cmd import _resolve_snapshot
257
258 repo = _init_repo(tmp_path)
259 _make_snapshot(repo, "exists")
260 result = _resolve_snapshot(repo, "sha256:000000000000")
261 assert result is None
262
263
264 # ---------------------------------------------------------------------------
265 # snapshot_diff._resolve_snapshot_prefix
266 # ---------------------------------------------------------------------------
267
268
269 class TestResolveDiffSnapshotPrefix:
270 def test_short_prefixed_id_resolves(self, tmp_path: pathlib.Path) -> None:
271 from muse.cli.commands.snapshot_diff import _resolve_snapshot_prefix
272
273 repo = _init_repo(tmp_path)
274 snap = _make_snapshot(repo, "diff_prefix")
275 short = short_id(snap.snapshot_id) # sha256:<12hex>
276 result = _resolve_snapshot_prefix(repo, short)
277 assert result is not None, f"prefix {short!r} must resolve; got None"
278 assert result == snap.snapshot_id
279
280 def test_bare_hex_rejected(self, tmp_path: pathlib.Path) -> None:
281 from muse.cli.commands.snapshot_diff import _resolve_snapshot_prefix
282
283 repo = _init_repo(tmp_path)
284 snap = _make_snapshot(repo, "bare_hex")
285 bare = long_id(snap.snapshot_id, strip=True)[:12]
286 result = _resolve_snapshot_prefix(repo, bare)
287 assert result is None, "bare hex without sha256: prefix must be rejected"
288
289 def test_unknown_prefix_returns_none(self, tmp_path: pathlib.Path) -> None:
290 from muse.cli.commands.snapshot_diff import _resolve_snapshot_prefix
291
292 repo = _init_repo(tmp_path)
293 _make_snapshot(repo, "some_snap")
294 result = _resolve_snapshot_prefix(repo, "sha256:000000000000")
295 assert result is None
296
297
298 # ---------------------------------------------------------------------------
299 # snapshot_diff._resolve_to_snapshot_id — end-to-end via short prefix
300 # ---------------------------------------------------------------------------
301
302
303 class TestResolveToSnapshotId:
304 def test_short_prefixed_snapshot_id_resolves(
305 self, tmp_path: pathlib.Path
306 ) -> None:
307 from muse.cli.commands.snapshot_diff import _resolve_to_snapshot_id
308
309 repo = _init_repo(tmp_path)
310 snap = _make_snapshot(repo, "e2e_short")
311 short = short_id(snap.snapshot_id)
312 result = _resolve_to_snapshot_id(repo, short)
313 assert result == snap.snapshot_id
314
315 def test_full_snapshot_id_resolves(self, tmp_path: pathlib.Path) -> None:
316 from muse.cli.commands.snapshot_diff import _resolve_to_snapshot_id
317
318 repo = _init_repo(tmp_path)
319 snap = _make_snapshot(repo, "e2e_full")
320 result = _resolve_to_snapshot_id(repo, snap.snapshot_id)
321 assert result == snap.snapshot_id
File History 1 commit