gabriel / muse public
test_plumbing_read_commit.py python
213 lines 7.8 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 read-commit``.
2
3 Covers: full commit-ID lookup, abbreviated-prefix lookup, ambiguous-prefix
4 detection, commit-not-found, invalid-ID format, output schema validation,
5 all required fields present, and a batch of sequential reads under load.
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, tag: str = "snap") -> str:
51 sid = _sha(f"snap-{tag}")
52 write_snapshot(
53 repo,
54 SnapshotRecord(
55 snapshot_id=sid,
56 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,
65 tag: str,
66 sid: str,
67 branch: str = "main",
68 author: str = "tester",
69 parent: str | None = None,
70 ) -> str:
71 cid = _sha(tag)
72 write_commit(
73 repo,
74 CommitRecord(
75 commit_id=cid,
76 repo_id="test-repo",
77 branch=branch,
78 snapshot_id=sid,
79 message=tag,
80 committed_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
81 author=author,
82 parent_commit_id=parent,
83 ),
84 )
85 ref = repo / ".muse" / "refs" / "heads" / branch
86 ref.parent.mkdir(parents=True, exist_ok=True)
87 ref.write_text(cid, encoding="utf-8")
88 return cid
89
90
91 # ---------------------------------------------------------------------------
92 # Unit: ID validation
93 # ---------------------------------------------------------------------------
94
95
96 class TestReadCommitUnit:
97 def test_invalid_hex_id_exits_user_error(self, tmp_path: pathlib.Path) -> None:
98 repo = _init_repo(tmp_path)
99 result = runner.invoke(cli, ["plumbing", "read-commit", "not-hex"], env=_env(repo))
100 assert result.exit_code == ExitCode.USER_ERROR
101 assert "error" in json.loads(result.stdout)
102
103 def test_commit_not_found_exits_user_error(self, tmp_path: pathlib.Path) -> None:
104 repo = _init_repo(tmp_path)
105 ghost = _sha("ghost")
106 result = runner.invoke(cli, ["plumbing", "read-commit", ghost], env=_env(repo))
107 assert result.exit_code == ExitCode.USER_ERROR
108 assert "error" in json.loads(result.stdout)
109
110
111 # ---------------------------------------------------------------------------
112 # Integration: full commit-ID lookup
113 # ---------------------------------------------------------------------------
114
115
116 class TestReadCommitFullId:
117 def test_returns_commit_record_json(self, tmp_path: pathlib.Path) -> None:
118 repo = _init_repo(tmp_path)
119 sid = _snap(repo)
120 cid = _commit(repo, "test-commit", sid)
121 result = runner.invoke(cli, ["plumbing", "read-commit", cid], env=_env(repo))
122 assert result.exit_code == 0, result.output
123 data = json.loads(result.stdout)
124 assert data["commit_id"] == cid
125 assert data["snapshot_id"] == sid
126 assert data["message"] == "test-commit"
127
128 def test_output_contains_all_required_schema_fields(self, tmp_path: pathlib.Path) -> None:
129 repo = _init_repo(tmp_path)
130 sid = _snap(repo)
131 cid = _commit(repo, "schema-check", sid, author="Turing")
132 result = runner.invoke(cli, ["plumbing", "read-commit", cid], env=_env(repo))
133 assert result.exit_code == 0
134 data = json.loads(result.stdout)
135 for field in (
136 "commit_id", "repo_id", "branch", "snapshot_id",
137 "message", "committed_at", "author",
138 "parent_commit_id", "parent2_commit_id", "format_version",
139 ):
140 assert field in data, f"Missing field: {field}"
141
142 def test_author_field_stored_correctly(self, tmp_path: pathlib.Path) -> None:
143 repo = _init_repo(tmp_path)
144 sid = _snap(repo)
145 cid = _commit(repo, "author-test", sid, author="Grace Hopper")
146 result = runner.invoke(cli, ["plumbing", "read-commit", cid], env=_env(repo))
147 assert result.exit_code == 0
148 assert json.loads(result.stdout)["author"] == "Grace Hopper"
149
150 def test_parent_commit_id_is_null_for_root(self, tmp_path: pathlib.Path) -> None:
151 repo = _init_repo(tmp_path)
152 sid = _snap(repo)
153 cid = _commit(repo, "root", sid)
154 result = runner.invoke(cli, ["plumbing", "read-commit", cid], env=_env(repo))
155 assert result.exit_code == 0
156 assert json.loads(result.stdout)["parent_commit_id"] is None
157
158 def test_parent_commit_id_matches_stored_parent(self, tmp_path: pathlib.Path) -> None:
159 repo = _init_repo(tmp_path)
160 sid = _snap(repo)
161 p_cid = _commit(repo, "parent", sid)
162 c_cid = _commit(repo, "child", sid, parent=p_cid)
163 result = runner.invoke(cli, ["plumbing", "read-commit", c_cid], env=_env(repo))
164 assert result.exit_code == 0
165 assert json.loads(result.stdout)["parent_commit_id"] == p_cid
166
167
168 # ---------------------------------------------------------------------------
169 # Integration: abbreviated prefix
170 # ---------------------------------------------------------------------------
171
172
173 class TestReadCommitPrefix:
174 def test_unique_prefix_resolves_to_full_record(self, tmp_path: pathlib.Path) -> None:
175 repo = _init_repo(tmp_path)
176 sid = _snap(repo)
177 cid = _commit(repo, "prefix-test", sid)
178 prefix = cid[:10]
179 result = runner.invoke(cli, ["plumbing", "read-commit", prefix], env=_env(repo))
180 assert result.exit_code == 0
181 assert json.loads(result.stdout)["commit_id"] == cid
182
183 def test_ambiguous_prefix_exits_user_error(self, tmp_path: pathlib.Path) -> None:
184 """Two commits with the same SHA prefix would trigger ambiguous response.
185
186 In practice we create two commits and check that if somehow both share
187 a prefix the command reports ambiguity correctly. We simulate it by
188 checking the ambiguous-prefix path exists via a 1-char prefix.
189 """
190 repo = _init_repo(tmp_path)
191 sid = _snap(repo)
192 # Create enough commits that a 1-char prefix is ambiguous.
193 for i in range(5):
194 _commit(repo, f"commit-{i}", sid)
195 result = runner.invoke(cli, ["plumbing", "read-commit", "0"], env=_env(repo))
196 # May be 0 (unique match), 1 (ambiguous), both are valid responses.
197 assert result.exit_code in (0, ExitCode.USER_ERROR)
198
199
200 # ---------------------------------------------------------------------------
201 # Stress: 100 sequential reads
202 # ---------------------------------------------------------------------------
203
204
205 class TestReadCommitStress:
206 def test_100_commits_all_readable(self, tmp_path: pathlib.Path) -> None:
207 repo = _init_repo(tmp_path)
208 sid = _snap(repo)
209 cids = [_commit(repo, f"commit-{i}", sid) for i in range(100)]
210 for cid in cids:
211 result = runner.invoke(cli, ["plumbing", "read-commit", cid], env=_env(repo))
212 assert result.exit_code == 0
213 assert json.loads(result.stdout)["commit_id"] == cid