gabriel / muse public
test_plumbing_read_commit.py python
213 lines 7.9 KB
86000da9 fix: replace typer CliRunner with argparse-compatible test helper Gabriel Cardona <gabriel@tellurstori.com> 1d 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 tests.cli_test_helper import CliRunner
16
17 cli = None # argparse migration — CliRunner ignores this arg
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