gabriel / muse public
test_plumbing_ls_files.py python
234 lines 8.5 KB
99746394 feat(tests+docs): supercharge plumbing test suite and update reference doc Gabriel Cardona <gabriel@tellurstori.com> 2d ago
1 """Tests for ``muse plumbing ls-files``.
2
3 Covers: default HEAD listing, explicit ``--commit`` flag, text-format output,
4 empty manifest, commit-not-found and snapshot-not-found error paths, sorted
5 output, and a stress case with a 500-file manifest.
6 """
7
8 from __future__ import annotations
9
10 import datetime
11 import hashlib
12 import json
13 import pathlib
14
15 from typer.testing import CliRunner
16
17 from muse.cli.app import cli
18 from muse.core.errors import ExitCode
19 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
20
21 runner = CliRunner()
22
23
24 # ---------------------------------------------------------------------------
25 # Helpers
26 # ---------------------------------------------------------------------------
27
28
29 def _sha(tag: str) -> str:
30 return hashlib.sha256(tag.encode()).hexdigest()
31
32
33 def _init_repo(path: pathlib.Path) -> pathlib.Path:
34 muse = path / ".muse"
35 (muse / "commits").mkdir(parents=True)
36 (muse / "snapshots").mkdir(parents=True)
37 (muse / "objects").mkdir(parents=True)
38 (muse / "refs" / "heads").mkdir(parents=True)
39 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
40 (muse / "repo.json").write_text(
41 json.dumps({"repo_id": "test-repo", "domain": "midi"}), encoding="utf-8"
42 )
43 return path
44
45
46 def _env(repo: pathlib.Path) -> dict[str, str]:
47 return {"MUSE_REPO_ROOT": str(repo)}
48
49
50 def _snap(repo: pathlib.Path, manifest: dict[str, str], tag: str = "s") -> str:
51 sid = _sha(f"snap-{tag}-{json.dumps(sorted(manifest.items()))}")
52 write_snapshot(
53 repo,
54 SnapshotRecord(
55 snapshot_id=sid,
56 manifest=manifest,
57 created_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
58 ),
59 )
60 return sid
61
62
63 def _commit(
64 repo: pathlib.Path, tag: str, sid: str, branch: str = "main", parent: str | None = None
65 ) -> str:
66 cid = _sha(tag)
67 write_commit(
68 repo,
69 CommitRecord(
70 commit_id=cid,
71 repo_id="test-repo",
72 branch=branch,
73 snapshot_id=sid,
74 message=tag,
75 committed_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
76 author="tester",
77 parent_commit_id=parent,
78 ),
79 )
80 ref = repo / ".muse" / "refs" / "heads" / branch
81 ref.parent.mkdir(parents=True, exist_ok=True)
82 ref.write_text(cid, encoding="utf-8")
83 return cid
84
85
86 # ---------------------------------------------------------------------------
87 # Integration: default HEAD
88 # ---------------------------------------------------------------------------
89
90
91 class TestLsFilesHead:
92 def test_default_lists_head_manifest(self, tmp_path: pathlib.Path) -> None:
93 repo = _init_repo(tmp_path)
94 manifest = {"tracks/drums.mid": _sha("drums"), "tracks/bass.mid": _sha("bass")}
95 sid = _snap(repo, manifest)
96 cid = _commit(repo, "c1", sid)
97 result = runner.invoke(cli, ["plumbing", "ls-files"], env=_env(repo))
98 assert result.exit_code == 0, result.output
99 data = json.loads(result.stdout)
100 assert data["commit_id"] == cid
101 assert data["snapshot_id"] == sid
102 assert data["file_count"] == 2
103 paths = {f["path"] for f in data["files"]}
104 assert paths == set(manifest.keys())
105
106 def test_object_ids_match_manifest(self, tmp_path: pathlib.Path) -> None:
107 repo = _init_repo(tmp_path)
108 oid = _sha("drums-content")
109 sid = _snap(repo, {"drums.mid": oid})
110 _commit(repo, "c2", sid)
111 result = runner.invoke(cli, ["plumbing", "ls-files"], env=_env(repo))
112 assert result.exit_code == 0
113 data = json.loads(result.stdout)
114 assert data["files"][0]["object_id"] == oid
115
116 def test_empty_manifest_reports_zero_files(self, tmp_path: pathlib.Path) -> None:
117 repo = _init_repo(tmp_path)
118 sid = _snap(repo, {})
119 _commit(repo, "empty", sid)
120 result = runner.invoke(cli, ["plumbing", "ls-files"], env=_env(repo))
121 assert result.exit_code == 0
122 data = json.loads(result.stdout)
123 assert data["file_count"] == 0
124 assert data["files"] == []
125
126 def test_no_commits_exits_user_error(self, tmp_path: pathlib.Path) -> None:
127 repo = _init_repo(tmp_path)
128 result = runner.invoke(cli, ["plumbing", "ls-files"], env=_env(repo))
129 assert result.exit_code == ExitCode.USER_ERROR
130
131
132 # ---------------------------------------------------------------------------
133 # Integration: --commit flag
134 # ---------------------------------------------------------------------------
135
136
137 class TestLsFilesCommitFlag:
138 def test_explicit_commit_lists_that_commits_manifest(self, tmp_path: pathlib.Path) -> None:
139 repo = _init_repo(tmp_path)
140 oid_a = _sha("a")
141 oid_b = _sha("b")
142 sid1 = _snap(repo, {"a.mid": oid_a}, "s1")
143 sid2 = _snap(repo, {"b.mid": oid_b}, "s2")
144 cid1 = _commit(repo, "c1", sid1)
145 cid2 = _commit(repo, "c2", sid2, parent=cid1)
146 result = runner.invoke(
147 cli, ["plumbing", "ls-files", "--commit", cid1], env=_env(repo)
148 )
149 assert result.exit_code == 0
150 data = json.loads(result.stdout)
151 assert data["commit_id"] == cid1
152 assert {f["path"] for f in data["files"]} == {"a.mid"}
153
154 def test_short_commit_flag(self, tmp_path: pathlib.Path) -> None:
155 repo = _init_repo(tmp_path)
156 sid = _snap(repo, {"x.mid": _sha("x")})
157 cid = _commit(repo, "cx", sid)
158 result = runner.invoke(cli, ["plumbing", "ls-files", "-c", cid], env=_env(repo))
159 assert result.exit_code == 0
160 assert json.loads(result.stdout)["commit_id"] == cid
161
162 def test_nonexistent_commit_exits_user_error(self, tmp_path: pathlib.Path) -> None:
163 repo = _init_repo(tmp_path)
164 ghost = _sha("ghost")
165 result = runner.invoke(cli, ["plumbing", "ls-files", "--commit", ghost], env=_env(repo))
166 assert result.exit_code == ExitCode.USER_ERROR
167 assert "error" in json.loads(result.stdout)
168
169
170 # ---------------------------------------------------------------------------
171 # Integration: output formats
172 # ---------------------------------------------------------------------------
173
174
175 class TestLsFilesFormats:
176 def test_text_format_shows_tab_separated_lines(self, tmp_path: pathlib.Path) -> None:
177 repo = _init_repo(tmp_path)
178 oid = _sha("t")
179 sid = _snap(repo, {"track.mid": oid})
180 _commit(repo, "tf", sid)
181 result = runner.invoke(
182 cli, ["plumbing", "ls-files", "--format", "text"], env=_env(repo)
183 )
184 assert result.exit_code == 0
185 assert "\t" in result.stdout
186 assert "track.mid" in result.stdout
187
188 def test_text_format_short_flag(self, tmp_path: pathlib.Path) -> None:
189 repo = _init_repo(tmp_path)
190 sid = _snap(repo, {"f.mid": _sha("f")})
191 _commit(repo, "ftf", sid)
192 result = runner.invoke(cli, ["plumbing", "ls-files", "-f", "text"], env=_env(repo))
193 assert result.exit_code == 0
194
195 def test_bad_format_exits_user_error(self, tmp_path: pathlib.Path) -> None:
196 repo = _init_repo(tmp_path)
197 sid = _snap(repo, {})
198 _commit(repo, "bfmt", sid)
199 result = runner.invoke(
200 cli, ["plumbing", "ls-files", "--format", "csv"], env=_env(repo)
201 )
202 assert result.exit_code == ExitCode.USER_ERROR
203
204 def test_files_sorted_lexicographically(self, tmp_path: pathlib.Path) -> None:
205 repo = _init_repo(tmp_path)
206 manifest = {
207 "zzz/z.mid": _sha("z"),
208 "aaa/a.mid": _sha("a"),
209 "mmm/m.mid": _sha("m"),
210 }
211 sid = _snap(repo, manifest)
212 _commit(repo, "sorted", sid)
213 result = runner.invoke(cli, ["plumbing", "ls-files"], env=_env(repo))
214 assert result.exit_code == 0
215 paths = [f["path"] for f in json.loads(result.stdout)["files"]]
216 assert paths == sorted(paths)
217
218
219 # ---------------------------------------------------------------------------
220 # Stress: 500-file manifest
221 # ---------------------------------------------------------------------------
222
223
224 class TestLsFilesStress:
225 def test_500_file_manifest_all_reported(self, tmp_path: pathlib.Path) -> None:
226 repo = _init_repo(tmp_path)
227 manifest = {f"track_{i:04d}.mid": _sha(f"oid-{i}") for i in range(500)}
228 sid = _snap(repo, manifest)
229 _commit(repo, "big-manifest", sid)
230 result = runner.invoke(cli, ["plumbing", "ls-files"], env=_env(repo))
231 assert result.exit_code == 0
232 data = json.loads(result.stdout)
233 assert data["file_count"] == 500
234 assert len(data["files"]) == 500