gabriel / muse public
test_core_store.py python
315 lines 13.1 KB
Raw
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 135 days ago
1 """Tests for muse.core.store — file-based commit and snapshot storage."""
2
3 import datetime
4 import json
5 import pathlib
6
7 import pytest
8
9 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
10
11 from muse.core._types import Manifest, fake_id
12 from muse.core.store import (
13 CommitDict,
14 CommitRecord,
15 SnapshotRecord,
16 TagRecord,
17 _resolve_branch_commit_id,
18 find_commits_by_prefix,
19 get_all_commits,
20 get_all_tags,
21 get_commits_for_branch,
22 get_head_commit_id,
23 get_head_snapshot_id,
24 get_head_snapshot_manifest,
25 get_tags_for_commit,
26 read_commit,
27 read_snapshot,
28 update_commit_metadata,
29 write_commit,
30 write_snapshot,
31 write_tag,
32 )
33
34
35 @pytest.fixture
36 def repo(tmp_path: pathlib.Path) -> pathlib.Path:
37 """Create a minimal .muse/ directory structure."""
38 muse_dir = tmp_path / ".muse"
39 (muse_dir / "commits").mkdir(parents=True)
40 (muse_dir / "snapshots").mkdir(parents=True)
41 (muse_dir / "refs" / "heads").mkdir(parents=True)
42 (muse_dir / "repo.json").write_text(json.dumps({"repo_id": "test-repo"}))
43 (muse_dir / "HEAD").write_text("ref: refs/heads/main\n")
44 (muse_dir / "refs" / "heads" / "main").write_text("")
45 return tmp_path
46
47
48 def _make_commit(
49 root: pathlib.Path,
50 commit_id: str,
51 snapshot_id: str,
52 message: str,
53 parent: str | None = None,
54 ) -> CommitRecord:
55 """Write a commit whose ID is computed from its content fields.
56
57 The ``commit_id`` parameter is kept for call-site readability but is
58 ignored — the real ID is derived by :func:`~muse.core.snapshot.compute_commit_id`
59 so every stored commit satisfies the I-10 content-hash verification.
60 """
61 now = datetime.datetime.now(datetime.timezone.utc)
62 parents: list[str] = [parent] if parent else []
63 real_id = compute_commit_id(
64 repo_id="test-repo",
65 parent_ids=parents,
66 snapshot_id=snapshot_id,
67 message=message,
68 committed_at_iso=now.isoformat(),
69 )
70 c = CommitRecord(
71 commit_id=real_id,
72 repo_id="test-repo",
73 created_on_branch="main",
74 snapshot_id=snapshot_id,
75 message=message,
76 committed_at=now,
77 parent_commit_id=parent,
78 )
79 write_commit(root, c)
80 return c
81
82
83 def _make_snapshot(root: pathlib.Path, snapshot_id: str, manifest: Manifest) -> SnapshotRecord:
84 """Write a snapshot whose ID is computed from its manifest.
85
86 The ``snapshot_id`` parameter is kept for call-site readability but is
87 ignored — the real ID is derived by
88 :func:`~muse.core.snapshot.compute_snapshot_id` so every stored snapshot
89 satisfies the I-10 content-hash verification.
90 """
91 real_id = compute_snapshot_id(manifest)
92 s = SnapshotRecord(snapshot_id=real_id, manifest=manifest)
93 write_snapshot(root, s)
94 return s
95
96
97 class TestWriteReadCommit:
98 def test_roundtrip(self, repo: pathlib.Path) -> None:
99 c = _make_commit(repo, "ignored", fake_id("snap"), "Initial commit")
100 loaded = read_commit(repo, c.commit_id)
101 assert loaded is not None
102 assert loaded.commit_id == c.commit_id
103 assert loaded.message == "Initial commit"
104 assert loaded.repo_id == "test-repo"
105
106 def test_read_missing_returns_none(self, repo: pathlib.Path) -> None:
107 assert read_commit(repo, fake_id("nonexistent-commit")) is None
108
109 def test_idempotent_write(self, repo: pathlib.Path) -> None:
110 c = _make_commit(repo, "ignored", fake_id("snap"), "First")
111 _make_commit(repo, "ignored", fake_id("snap"), "Second") # different timestamp → different ID; should write
112 loaded = read_commit(repo, c.commit_id)
113 assert loaded is not None
114 assert loaded.message == "First"
115
116 def test_metadata_preserved(self, repo: pathlib.Path) -> None:
117 now = datetime.datetime.now(datetime.timezone.utc)
118 snap_id = fake_id("snap")
119 cid = compute_commit_id(repo_id="test-repo", parent_ids=[], snapshot_id=snap_id, message="With metadata", committed_at_iso=now.isoformat())
120 c = CommitRecord(
121 commit_id=cid,
122 repo_id="test-repo",
123 created_on_branch="main",
124 snapshot_id=snap_id,
125 message="With metadata",
126 committed_at=now,
127 metadata={"section": "chorus", "emotion": "joyful"},
128 )
129 write_commit(repo, c)
130 loaded = read_commit(repo, cid)
131 assert loaded is not None
132 assert loaded.metadata["section"] == "chorus"
133 assert loaded.metadata["emotion"] == "joyful"
134
135
136 class TestUpdateCommitMetadata:
137 def test_set_key(self, repo: pathlib.Path) -> None:
138 c = _make_commit(repo, "ignored", fake_id("snap"), "msg")
139 result = update_commit_metadata(repo, c.commit_id, "tempo_bpm", "120.0")
140 assert result is True
141 loaded = read_commit(repo, c.commit_id)
142 assert loaded is not None
143 assert loaded.metadata["tempo_bpm"] == "120.0"
144
145 def test_missing_commit_returns_false(self, repo: pathlib.Path) -> None:
146 assert update_commit_metadata(repo, fake_id("nonexistent-commit"), "k", "v") is False
147
148
149 class TestWriteReadSnapshot:
150 def test_roundtrip(self, repo: pathlib.Path) -> None:
151 _drum_id = fake_id("deadbeef")
152 s = _make_snapshot(repo, "ignored", {"tracks/drums.mid": _drum_id})
153 loaded = read_snapshot(repo, s.snapshot_id)
154 assert loaded is not None
155 assert loaded.manifest == {"tracks/drums.mid": _drum_id}
156
157 def test_read_missing_returns_none(self, repo: pathlib.Path) -> None:
158 assert read_snapshot(repo, fake_id("nonexistent-snapshot")) is None
159
160
161 class TestHeadQueries:
162 def test_get_head_commit_id_empty_branch(self, repo: pathlib.Path) -> None:
163 assert get_head_commit_id(repo, "main") is None
164
165 def test_get_head_commit_id(self, repo: pathlib.Path) -> None:
166 c = _make_commit(repo, "ignored", fake_id("snap"), "msg")
167 (repo / ".muse" / "refs" / "heads" / "main").write_text(c.commit_id)
168 assert get_head_commit_id(repo, "main") == c.commit_id
169
170 def test_get_head_snapshot_id(self, repo: pathlib.Path) -> None:
171 _f_id = fake_id("f.mid-content")
172 snap = _make_snapshot(repo, "ignored", {"f.mid": _f_id})
173 c = _make_commit(repo, "ignored", snap.snapshot_id, "msg")
174 (repo / ".muse" / "refs" / "heads" / "main").write_text(c.commit_id)
175 assert get_head_snapshot_id(repo, "test-repo", "main") == snap.snapshot_id
176
177 def test_get_head_snapshot_manifest(self, repo: pathlib.Path) -> None:
178 _f_id = fake_id("f.mid-content")
179 snap = _make_snapshot(repo, "ignored", {"f.mid": _f_id})
180 c = _make_commit(repo, "ignored", snap.snapshot_id, "msg")
181 (repo / ".muse" / "refs" / "heads" / "main").write_text(c.commit_id)
182 manifest = get_head_snapshot_manifest(repo, "test-repo", "main")
183 assert manifest == {"f.mid": _f_id}
184
185
186 class TestResolveRemoteBranchCommitId:
187 """_resolve_branch_commit_id handles local branches and remote tracking refs."""
188
189 def test_local_branch_resolved(self, repo: pathlib.Path) -> None:
190 c = _make_commit(repo, "ignored", fake_id("snap"), "msg")
191 (repo / ".muse" / "refs" / "heads" / "main").write_text(c.commit_id)
192 assert _resolve_branch_commit_id(repo, "main") == c.commit_id
193
194 def test_empty_local_branch_returns_none(self, repo: pathlib.Path) -> None:
195 assert _resolve_branch_commit_id(repo, "main") is None
196
197 def test_remote_tracking_ref_resolved(self, repo: pathlib.Path) -> None:
198 remote_dir = repo / ".muse" / "remotes" / "origin"
199 remote_dir.mkdir(parents=True)
200 (remote_dir / "dev").write_text("a" * 64)
201 assert _resolve_branch_commit_id(repo, "origin/dev") == "a" * 64
202
203 def test_remote_tracking_ref_missing_returns_none(self, repo: pathlib.Path) -> None:
204 (repo / ".muse" / "remotes" / "origin").mkdir(parents=True)
205 assert _resolve_branch_commit_id(repo, "origin/nonexistent") is None
206
207 def test_remote_tracking_ref_no_remotes_dir_returns_none(self, repo: pathlib.Path) -> None:
208 assert _resolve_branch_commit_id(repo, "origin/dev") is None
209
210 def test_ref_without_slash_only_checks_local(self, repo: pathlib.Path) -> None:
211 # "dev" has no slash — never checks .muse/remotes/
212 assert _resolve_branch_commit_id(repo, "dev") is None
213
214 def test_local_branch_takes_priority_over_remote(self, repo: pathlib.Path) -> None:
215 local_id = "b" * 64
216 remote_id = "c" * 64
217 # Both a local branch named "origin/dev" (pathological) and a remote ref exist.
218 feat_dir = repo / ".muse" / "refs" / "heads" / "origin"
219 feat_dir.mkdir(parents=True)
220 (feat_dir / "dev").write_text(local_id)
221 remote_dir = repo / ".muse" / "remotes" / "origin"
222 remote_dir.mkdir(parents=True)
223 (remote_dir / "dev").write_text(remote_id)
224 # Local branch wins.
225 assert _resolve_branch_commit_id(repo, "origin/dev") == local_id
226
227
228 class TestGetCommitsForBranch:
229 def test_chain(self, repo: pathlib.Path) -> None:
230 root = _make_commit(repo, "ignored", fake_id("snap-root"), "Root")
231 child = _make_commit(repo, "ignored", fake_id("snap-child"), "Child", parent=root.commit_id)
232 grandchild = _make_commit(repo, "ignored", fake_id("snap-grandchild"), "Grandchild", parent=child.commit_id)
233 (repo / ".muse" / "refs" / "heads" / "main").write_text(grandchild.commit_id)
234
235 commits = get_commits_for_branch(repo, "test-repo", "main")
236 assert [c.commit_id for c in commits] == [
237 grandchild.commit_id, child.commit_id, root.commit_id
238 ]
239
240 def test_empty_branch(self, repo: pathlib.Path) -> None:
241 assert get_commits_for_branch(repo, "test-repo", "main") == []
242
243 def test_remote_tracking_ref(self, repo: pathlib.Path) -> None:
244 """get_commits_for_branch resolves 'origin/dev' via .muse/remotes/."""
245 c = _make_commit(repo, "ignored", fake_id("snap"), "Remote commit")
246 remote_dir = repo / ".muse" / "remotes" / "origin"
247 remote_dir.mkdir(parents=True)
248 (remote_dir / "dev").write_text(c.commit_id)
249
250 commits = get_commits_for_branch(repo, "test-repo", "origin/dev")
251 assert len(commits) == 1
252 assert commits[0].commit_id == c.commit_id
253
254 def test_remote_tracking_ref_chain(self, repo: pathlib.Path) -> None:
255 root = _make_commit(repo, "ignored", fake_id("snap-root"), "Root")
256 tip = _make_commit(repo, "ignored", fake_id("snap-tip"), "Tip", parent=root.commit_id)
257 remote_dir = repo / ".muse" / "remotes" / "upstream"
258 remote_dir.mkdir(parents=True)
259 (remote_dir / "main").write_text(tip.commit_id)
260
261 commits = get_commits_for_branch(repo, "test-repo", "upstream/main")
262 assert [c.commit_id for c in commits] == [tip.commit_id, root.commit_id]
263
264 def test_remote_tracking_ref_missing_returns_empty(self, repo: pathlib.Path) -> None:
265 assert get_commits_for_branch(repo, "test-repo", "origin/dev") == []
266
267 def test_max_count_with_remote_ref(self, repo: pathlib.Path) -> None:
268 commits_written = [_make_commit(repo, "ignored", fake_id(f"snap-s{i}"), f"Commit {i}") for i in range(5)]
269 for i in range(1, 5):
270 commits_written[i] = _make_commit(
271 repo, "ignored", fake_id(f"snap-t{i}"), f"C{i}", parent=commits_written[i - 1].commit_id
272 )
273 tip = _make_commit(repo, "ignored", fake_id("snap-tip"), "Tip", parent=commits_written[-1].commit_id)
274 remote_dir = repo / ".muse" / "remotes" / "origin"
275 remote_dir.mkdir(parents=True)
276 (remote_dir / "dev").write_text(tip.commit_id)
277
278 commits = get_commits_for_branch(repo, "test-repo", "origin/dev", max_count=2)
279 assert len(commits) == 2
280 assert commits[0].commit_id == tip.commit_id
281
282
283 class TestFindByPrefix:
284 def test_finds_match(self, repo: pathlib.Path) -> None:
285 c = _make_commit(repo, "ignored", fake_id("snap"), "msg")
286 hex_prefix = c.commit_id[len("sha256:"):len("sha256:") + 6]
287 results = find_commits_by_prefix(repo, hex_prefix)
288 assert len(results) == 1
289 assert results[0].commit_id == c.commit_id
290
291 def test_no_match(self, repo: pathlib.Path) -> None:
292 assert find_commits_by_prefix(repo, "zzz") == []
293
294
295 class TestTags:
296 def test_write_and_read(self, repo: pathlib.Path) -> None:
297 c = _make_commit(repo, "ignored", fake_id("snap"), "msg")
298 repo_id = fake_id("test-repo")
299 write_tag(repo, TagRecord(
300 tag_id=fake_id("tag1"),
301 repo_id=repo_id,
302 commit_id=c.commit_id,
303 tag="emotion:joyful",
304 ))
305 tags = get_tags_for_commit(repo, repo_id, c.commit_id)
306 assert len(tags) == 1
307 assert tags[0].tag == "emotion:joyful"
308
309 def test_get_all_tags(self, repo: pathlib.Path) -> None:
310 c = _make_commit(repo, "ignored", fake_id("snap"), "msg")
311 repo_id = fake_id("test-repo")
312 write_tag(repo, TagRecord(tag_id=fake_id("t1"), repo_id=repo_id, commit_id=c.commit_id, tag="stage:rough-mix"))
313 write_tag(repo, TagRecord(tag_id=fake_id("t2"), repo_id=repo_id, commit_id=c.commit_id, tag="key:Am"))
314 all_tags = get_all_tags(repo, repo_id)
315 assert len(all_tags) == 2
File History 3 commits
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 135 days ago
sha256:7f9e2ef5286aedad9c1e6011b4c46ca27f39dbdad6e3409357e36b26e46b3b7c docs: docstring sprint for-each-ref→hotspots — idiomatic ru… Sonnet 4.6 patch 141 days ago
sha256:a09b1b4f6838754495547f200aa0ce88e2f56ffc5b20b900f6f0cff2c3cdede9 fix(cursorignore): remove git-ism (.git/worktrees) Human minor 144 days ago