gabriel / muse public

test_cmd_reflog_exists.py file-level

at sha256:c · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:6 chore(timeline): remove unused RationalRate import in entity.py · · Jul 10, 2026
1 """Phase 6 — RL_28–RL_30: ``muse reflog exists`` subcommand.
2
3 TDD: tests written before implementation.
4
5 Coverage tiers
6 --------------
7 RL_28 muse reflog exists --json returns {"exists": true, "count": N}; exit 0
8 RL_29 muse reflog exists --branch dev --json returns exists:false, count:0; exit 1
9 RL_30 exit code 0 for exists, 1 for does-not-exist — scriptable without --json
10 """
11 from __future__ import annotations
12
13 import json
14 import pathlib
15 import time
16
17 import pytest
18
19 from muse.core.paths import muse_dir, logs_dir
20 from muse.core.types import fake_id
21 from tests.cli_test_helper import CliRunner
22
23 runner = CliRunner()
24 cli = None
25
26 # ---------------------------------------------------------------------------
27 # Helpers
28 # ---------------------------------------------------------------------------
29
30 def _env(root: pathlib.Path) -> dict[str, str]:
31 return {"MUSE_REPO_ROOT": str(root)}
32
33
34 def _make_repo(tmp_path: pathlib.Path, branch: str = "main") -> pathlib.Path:
35 """Minimal .muse/ repo with no commits and an empty config."""
36 from muse._version import __version__
37
38 dot = muse_dir(tmp_path)
39 for sub in (
40 "refs/heads", "objects/sha256", "commits", "snapshots",
41 "logs/refs/heads", "logs",
42 ):
43 (dot / sub).mkdir(parents=True, exist_ok=True)
44 (dot / "repo.json").write_text(
45 json.dumps({"repo_id": "test-repo", "schema_version": __version__, "domain": "code"})
46 )
47 (dot / "HEAD").write_text(f"ref: refs/heads/{branch}\n")
48 (dot / "config.toml").write_text("")
49 return tmp_path
50
51
52 def _head_log(root: pathlib.Path) -> pathlib.Path:
53 return logs_dir(root) / "HEAD"
54
55
56 def _branch_log(root: pathlib.Path, branch: str) -> pathlib.Path:
57 return logs_dir(root) / "refs" / "heads" / branch
58
59
60 def _write_entries(log_path: pathlib.Path, n: int = 3) -> None:
61 """Write n reflog entries to log_path."""
62 log_path.parent.mkdir(parents=True, exist_ok=True)
63 old = fake_id("old")
64 new = fake_id("new")
65 ts = int(time.time())
66 lines = [f"{old} {new} user {ts} +0000\tcommit: entry-{i}\n" for i in range(n)]
67 log_path.write_text("".join(lines), encoding="utf-8")
68
69
70 # ---------------------------------------------------------------------------
71 # RL_28 — muse reflog exists --json: exists=true, count=N, exit 0
72 # ---------------------------------------------------------------------------
73
74 class TestReflogExistsTrue:
75 """RL_28: HEAD log has entries → exists=true, count=N, exit 0."""
76
77 def test_json_exists_true_when_head_log_has_entries(
78 self, tmp_path: pathlib.Path
79 ) -> None:
80 root = _make_repo(tmp_path)
81 _write_entries(_head_log(root), n=5)
82
83 r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root))
84
85 assert r.exit_code == 0, r.output
86 data = json.loads(r.output)
87 assert data["exists"] is True
88 assert data["count"] == 5
89
90 def test_json_schema_has_all_required_fields(
91 self, tmp_path: pathlib.Path
92 ) -> None:
93 root = _make_repo(tmp_path)
94 _write_entries(_head_log(root), n=1)
95
96 r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root))
97
98 data = json.loads(r.output)
99 for field in ("exists", "count", "ref", "exit_code", "duration_ms"):
100 assert field in data, f"missing key {field!r}"
101
102 def test_ref_field_is_head_by_default(self, tmp_path: pathlib.Path) -> None:
103 root = _make_repo(tmp_path)
104 _write_entries(_head_log(root), n=2)
105
106 r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root))
107
108 data = json.loads(r.output)
109 assert data["ref"] == "HEAD"
110
111 def test_count_matches_actual_entry_count(self, tmp_path: pathlib.Path) -> None:
112 root = _make_repo(tmp_path)
113 _write_entries(_head_log(root), n=7)
114
115 r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root))
116
117 data = json.loads(r.output)
118 assert data["count"] == 7
119
120 def test_branch_log_exists_with_entries(self, tmp_path: pathlib.Path) -> None:
121 root = _make_repo(tmp_path)
122 _write_entries(_branch_log(root, "dev"), n=3)
123
124 r = runner.invoke(
125 cli, ["reflog", "exists", "--branch", "dev", "--json"], env=_env(root)
126 )
127
128 assert r.exit_code == 0, r.output
129 data = json.loads(r.output)
130 assert data["exists"] is True
131 assert data["count"] == 3
132
133 def test_branch_log_ref_field_uses_full_path(
134 self, tmp_path: pathlib.Path
135 ) -> None:
136 root = _make_repo(tmp_path)
137 _write_entries(_branch_log(root, "main"), n=1)
138
139 r = runner.invoke(
140 cli, ["reflog", "exists", "--branch", "main", "--json"], env=_env(root)
141 )
142
143 data = json.loads(r.output)
144 assert data["ref"] == "refs/heads/main"
145
146 def test_single_entry_reports_count_one(self, tmp_path: pathlib.Path) -> None:
147 root = _make_repo(tmp_path)
148 _write_entries(_head_log(root), n=1)
149
150 r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root))
151
152 data = json.loads(r.output)
153 assert data["exists"] is True
154 assert data["count"] == 1
155
156
157 # ---------------------------------------------------------------------------
158 # RL_29 — muse reflog exists --branch dev: exists=false, count=0, exit 1
159 # ---------------------------------------------------------------------------
160
161 class TestReflogExistsFalse:
162 """RL_29: no log file → exists=false, count=0, exit 1."""
163
164 def test_json_exists_false_when_no_head_log(
165 self, tmp_path: pathlib.Path
166 ) -> None:
167 root = _make_repo(tmp_path)
168 # No log written
169
170 r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root))
171
172 assert r.exit_code == 1
173 data = json.loads(r.output)
174 assert data["exists"] is False
175 assert data["count"] == 0
176
177 def test_json_exists_false_when_branch_has_no_log(
178 self, tmp_path: pathlib.Path
179 ) -> None:
180 root = _make_repo(tmp_path)
181 # HEAD has entries but dev branch does not
182
183 _write_entries(_head_log(root), n=3)
184
185 r = runner.invoke(
186 cli, ["reflog", "exists", "--branch", "dev", "--json"], env=_env(root)
187 )
188
189 assert r.exit_code == 1
190 data = json.loads(r.output)
191 assert data["exists"] is False
192 assert data["count"] == 0
193
194 def test_json_exists_false_after_all_entries_deleted(
195 self, tmp_path: pathlib.Path
196 ) -> None:
197 root = _make_repo(tmp_path)
198 log = _head_log(root)
199 _write_entries(log, n=2)
200
201 # Delete all entries
202 runner.invoke(cli, ["reflog", "delete", "--all"], env=_env(root))
203
204 r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root))
205
206 assert r.exit_code == 1
207 data = json.loads(r.output)
208 assert data["exists"] is False
209
210 def test_ref_field_is_head_when_no_log(self, tmp_path: pathlib.Path) -> None:
211 root = _make_repo(tmp_path)
212
213 r = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root))
214
215 data = json.loads(r.output)
216 assert data["ref"] == "HEAD"
217
218 def test_branch_ref_field_on_missing_branch_log(
219 self, tmp_path: pathlib.Path
220 ) -> None:
221 root = _make_repo(tmp_path)
222
223 r = runner.invoke(
224 cli, ["reflog", "exists", "--branch", "feat", "--json"], env=_env(root)
225 )
226
227 data = json.loads(r.output)
228 assert data["ref"] == "refs/heads/feat"
229
230
231 # ---------------------------------------------------------------------------
232 # RL_30 — exit codes without --json; scriptable interface
233 # ---------------------------------------------------------------------------
234
235 class TestReflogExistsExitCode:
236 """RL_30: exit 0 when entries exist; exit 1 when none — no --json needed."""
237
238 def test_exit_zero_when_entries_exist(self, tmp_path: pathlib.Path) -> None:
239 root = _make_repo(tmp_path)
240 _write_entries(_head_log(root), n=3)
241
242 r = runner.invoke(cli, ["reflog", "exists"], env=_env(root))
243
244 assert r.exit_code == 0
245
246 def test_exit_one_when_no_entries(self, tmp_path: pathlib.Path) -> None:
247 root = _make_repo(tmp_path)
248
249 r = runner.invoke(cli, ["reflog", "exists"], env=_env(root))
250
251 assert r.exit_code == 1
252
253 def test_exit_zero_branch_exists(self, tmp_path: pathlib.Path) -> None:
254 root = _make_repo(tmp_path)
255 _write_entries(_branch_log(root, "main"), n=1)
256
257 r = runner.invoke(cli, ["reflog", "exists", "--branch", "main"], env=_env(root))
258
259 assert r.exit_code == 0
260
261 def test_exit_one_branch_missing(self, tmp_path: pathlib.Path) -> None:
262 root = _make_repo(tmp_path)
263
264 r = runner.invoke(cli, ["reflog", "exists", "--branch", "missing-branch"], env=_env(root))
265
266 assert r.exit_code == 1
267
268 def test_text_output_reports_count(self, tmp_path: pathlib.Path) -> None:
269 """Human output should mention the count when entries exist."""
270 root = _make_repo(tmp_path)
271 _write_entries(_head_log(root), n=4)
272
273 r = runner.invoke(cli, ["reflog", "exists"], env=_env(root))
274
275 # The count should appear in human output.
276 assert "4" in r.output
277
278 def test_text_output_reports_no_entries(self, tmp_path: pathlib.Path) -> None:
279 """Human output should indicate no entries when the log is absent."""
280 root = _make_repo(tmp_path)
281
282 r = runner.invoke(cli, ["reflog", "exists"], env=_env(root))
283
284 combined = r.output + (r.stderr or "")
285 assert "no" in combined.lower() or "0" in combined
286
287 def test_exit_code_matches_json_exists_field(
288 self, tmp_path: pathlib.Path
289 ) -> None:
290 """JSON exists=true → exit 0; exists=false → exit 1."""
291 root = _make_repo(tmp_path)
292 _write_entries(_head_log(root), n=2)
293
294 r_json = runner.invoke(cli, ["reflog", "exists", "--json"], env=_env(root))
295 r_plain = runner.invoke(cli, ["reflog", "exists"], env=_env(root))
296
297 data = json.loads(r_json.output)
298 assert data["exists"] is True
299 assert r_json.exit_code == 0
300 assert r_plain.exit_code == 0
301
302 def test_empty_repo_always_exits_one(self, tmp_path: pathlib.Path) -> None:
303 root = _make_repo(tmp_path)
304
305 r = runner.invoke(cli, ["reflog", "exists"], env=_env(root))
306
307 assert r.exit_code == 1