gabriel / muse public
test_cli_coverage_gaps.py python
289 lines 11.3 KB
Raw
1 """Tests targeting specific coverage gaps in checkout, tag, commit, diff, shelf, branch."""
2
3 import pathlib
4
5 import pytest
6 from tests.cli_test_helper import CliRunner
7
8 cli = None # argparse migration — CliRunner ignores this arg
9 from muse.core.refs import get_head_commit_id
10
11 runner = CliRunner()
12
13
14 @pytest.fixture
15 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
16 monkeypatch.chdir(tmp_path)
17 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
18 result = runner.invoke(cli, ["init"])
19 assert result.exit_code == 0, result.output
20 return tmp_path
21
22
23 def _write(repo: pathlib.Path, filename: str, content: str = "data") -> None:
24 (repo / filename).write_text(content)
25
26
27 def _commit(msg: str = "commit") -> str | None:
28 result = runner.invoke(cli, ["commit", "-m", msg])
29 assert result.exit_code == 0, result.output
30 return get_head_commit_id(pathlib.Path("."), "main")
31
32
33 # ---------------------------------------------------------------------------
34 # checkout gaps
35 # ---------------------------------------------------------------------------
36
37
38 class TestCheckoutGaps:
39 def test_create_branch_already_exists_errors(self, repo: pathlib.Path) -> None:
40 result = runner.invoke(cli, ["checkout", "-b", "main"])
41 assert result.exit_code != 0
42 assert "already exists" in result.stderr
43
44 def test_unknown_ref_errors(self, repo: pathlib.Path) -> None:
45 result = runner.invoke(cli, ["checkout", "no-such-branch-or-commit"])
46 assert result.exit_code != 0
47 assert "not a branch" in result.stderr.lower() or "not found" in result.stderr.lower() or "no-such" in result.stderr
48
49 def test_checkout_by_commit_id_detaches_head(self, repo: pathlib.Path) -> None:
50 _write(repo, "beat.mid")
51 result = runner.invoke(cli, ["commit", "-m", "root"])
52 assert result.exit_code == 0
53 commit_id = get_head_commit_id(repo, "main")
54 assert commit_id is not None
55
56 _write(repo, "lead.mid")
57 runner.invoke(cli, ["commit", "-m", "second"])
58
59 result = runner.invoke(cli, ["checkout", commit_id])
60 assert result.exit_code == 0
61 assert "HEAD is now at" in result.output
62
63 def test_checkout_restores_workdir_to_target_snapshot(self, repo: pathlib.Path) -> None:
64 _write(repo, "beat.mid", "v1")
65 result = runner.invoke(cli, ["commit", "-m", "first"])
66 assert result.exit_code == 0
67 first_id = get_head_commit_id(repo, "main")
68 assert first_id is not None
69
70 _write(repo, "lead.mid", "new")
71 runner.invoke(cli, ["commit", "-m", "second"])
72
73 runner.invoke(cli, ["checkout", first_id])
74 assert (repo / "beat.mid").exists()
75 assert not (repo / "lead.mid").exists()
76
77
78 # ---------------------------------------------------------------------------
79 # tag gaps
80 # ---------------------------------------------------------------------------
81
82
83 class TestTagGaps:
84 def test_tag_add_unknown_ref_errors(self, repo: pathlib.Path) -> None:
85 _write(repo, "beat.mid")
86 runner.invoke(cli, ["commit", "-m", "base"])
87 result = runner.invoke(cli, ["tag", "add", "emotion:joyful", "deadbeef"])
88 assert result.exit_code != 0
89
90 def test_tag_list_for_specific_commit(self, repo: pathlib.Path) -> None:
91 _write(repo, "beat.mid")
92 runner.invoke(cli, ["commit", "-m", "base"])
93 commit_id = get_head_commit_id(repo, "main")
94 assert commit_id is not None
95
96 short = commit_id.removeprefix("sha256:")[:8]
97 runner.invoke(cli, ["tag", "add", "emotion:joyful", short])
98 result = runner.invoke(cli, ["tag", "list", short])
99 assert result.exit_code == 0
100 assert "joyful" in result.output
101
102 def test_tag_list_for_unknown_ref_errors(self, repo: pathlib.Path) -> None:
103 _write(repo, "beat.mid")
104 runner.invoke(cli, ["commit", "-m", "base"])
105 result = runner.invoke(cli, ["tag", "list", "deadbeef"])
106 assert result.exit_code != 0
107
108 def test_tag_list_all_shows_all_tags(self, repo: pathlib.Path) -> None:
109 _write(repo, "beat.mid")
110 runner.invoke(cli, ["commit", "-m", "base"])
111 commit_id = get_head_commit_id(repo, "main")
112 assert commit_id is not None
113
114 short = commit_id.removeprefix("sha256:")[:8]
115 runner.invoke(cli, ["tag", "add", "section:verse", short])
116 runner.invoke(cli, ["tag", "add", "emotion:joyful", short])
117 result = runner.invoke(cli, ["tag", "list"])
118 assert result.exit_code == 0
119 assert "section:verse" in result.output
120 assert "emotion:joyful" in result.output
121
122
123 # ---------------------------------------------------------------------------
124 # commit gaps
125 # ---------------------------------------------------------------------------
126
127
128 class TestCommitGaps:
129 def test_no_message_without_allow_empty_errors(self, repo: pathlib.Path) -> None:
130 _write(repo, "beat.mid")
131 result = runner.invoke(cli, ["commit"])
132 assert result.exit_code != 0
133
134 def test_empty_repo_without_allow_empty_errors(self, repo: pathlib.Path) -> None:
135 # muse init creates .museignore / .museattributes which are tracked by default.
136 # Commit that initial state, then verify a second commit on a clean tree
137 # reports nothing to commit.
138 runner.invoke(cli, ["commit", "-m", "init files"])
139 result = runner.invoke(cli, ["commit", "-m", "nothing here"])
140 assert result.exit_code == 0
141 assert "Nothing to commit" in result.output or "nothing" in result.output.lower()
142
143 def test_empty_workdir_without_allow_empty_errors(self, repo: pathlib.Path) -> None:
144 # Same as above: commit init-created dotfiles first, then verify clean tree.
145 runner.invoke(cli, ["commit", "-m", "init files"])
146 result = runner.invoke(cli, ["commit", "-m", "empty"])
147 assert result.exit_code == 0
148 assert "Nothing to commit" in result.output or "nothing" in result.output.lower()
149
150 def test_nothing_to_commit_clean_tree(self, repo: pathlib.Path) -> None:
151 _write(repo, "beat.mid")
152 runner.invoke(cli, ["commit", "-m", "first"])
153 result = runner.invoke(cli, ["commit", "-m", "second"])
154 assert result.exit_code == 0
155 assert "Nothing to commit" in result.output or "clean" in result.output
156
157 def test_commit_with_pending_conflicts_errors(self, repo: pathlib.Path) -> None:
158 _write(repo, "beat.mid", "base")
159 runner.invoke(cli, ["commit", "-m", "base"])
160
161 runner.invoke(cli, ["branch", "feature"])
162 runner.invoke(cli, ["checkout", "feature"])
163 _write(repo, "beat.mid", "feature-v")
164 runner.invoke(cli, ["commit", "-m", "feature changes"])
165
166 runner.invoke(cli, ["checkout", "main"])
167 _write(repo, "beat.mid", "main-v")
168 runner.invoke(cli, ["commit", "-m", "main changes"])
169
170 runner.invoke(cli, ["merge", "feature"])
171
172 # Now try to commit — should fail because of unresolved conflicts
173 _write(repo, "new.mid")
174 result = runner.invoke(cli, ["commit", "-m", "during conflict"])
175 assert result.exit_code != 0
176 assert "conflict" in result.stderr.lower()
177
178
179 # ---------------------------------------------------------------------------
180 # diff gaps
181 # ---------------------------------------------------------------------------
182
183
184 class TestDiffGaps:
185 def test_diff_two_commits(self, repo: pathlib.Path) -> None:
186 _write(repo, "a.mid", "v1")
187 runner.invoke(cli, ["commit", "-m", "first"])
188 first_id = get_head_commit_id(repo, "main")
189
190 _write(repo, "a.mid", "v2")
191 runner.invoke(cli, ["commit", "-m", "second"])
192 second_id = get_head_commit_id(repo, "main")
193
194 assert first_id is not None
195 assert second_id is not None
196 result = runner.invoke(cli, ["diff", first_id, second_id])
197 assert result.exit_code == 0
198 assert "a.mid" in result.output
199
200 def test_diff_commit_vs_head(self, repo: pathlib.Path) -> None:
201 _write(repo, "a.mid", "v1")
202 runner.invoke(cli, ["commit", "-m", "first"])
203 first_id = get_head_commit_id(repo, "main")
204
205 _write(repo, "a.mid", "v2")
206 runner.invoke(cli, ["commit", "-m", "second"])
207 assert first_id is not None
208
209 result = runner.invoke(cli, ["diff", first_id])
210 assert result.exit_code == 0
211 assert "a.mid" in result.output
212
213 def test_diff_stat_flag(self, repo: pathlib.Path) -> None:
214 _write(repo, "a.mid")
215 runner.invoke(cli, ["commit", "-m", "base"])
216 _write(repo, "b.mid")
217 result = runner.invoke(cli, ["diff", "--stat"])
218 assert result.exit_code == 0
219 # --stat prints the structured delta summary line.
220 assert "added" in result.output or "No differences" in result.output
221
222
223 # ---------------------------------------------------------------------------
224 # shelf gaps
225 # ---------------------------------------------------------------------------
226
227
228 class TestShelfGaps:
229 def test_shelf_pop_restores_files(self, repo: pathlib.Path) -> None:
230 _write(repo, "beat.mid")
231 runner.invoke(cli, ["commit", "-m", "base"])
232 _write(repo, "unsaved.mid", "wip")
233
234 runner.invoke(cli, ["shelf", "save"])
235 assert not (repo / "unsaved.mid").exists()
236
237 runner.invoke(cli, ["shelf", "pop"])
238 assert (repo / "unsaved.mid").exists()
239
240 def test_shelf_drop_removes_entry(self, repo: pathlib.Path) -> None:
241 _write(repo, "beat.mid")
242 runner.invoke(cli, ["commit", "-m", "base"])
243 _write(repo, "unsaved.mid", "wip")
244
245 runner.invoke(cli, ["shelf", "save"])
246 result = runner.invoke(cli, ["shelf", "drop"])
247 assert result.exit_code == 0
248
249 result = runner.invoke(cli, ["shelf", "list"])
250 assert "No shelf entries" in result.output
251
252 def test_shelf_pop_empty_errors(self, repo: pathlib.Path) -> None:
253 result = runner.invoke(cli, ["shelf", "pop"])
254 assert result.exit_code != 0
255
256 def test_shelf_drop_empty_errors(self, repo: pathlib.Path) -> None:
257 result = runner.invoke(cli, ["shelf", "drop"])
258 assert result.exit_code != 0
259
260
261
262 # ---------------------------------------------------------------------------
263 # branch gaps
264 # ---------------------------------------------------------------------------
265
266
267 class TestBranchGaps:
268 def test_delete_branch_with_d_flag(self, repo: pathlib.Path) -> None:
269 runner.invoke(cli, ["branch", "to-delete"])
270 result = runner.invoke(cli, ["branch", "-D", "to-delete"])
271 assert result.exit_code == 0
272
273 def test_delete_current_branch_errors(self, repo: pathlib.Path) -> None:
274 result = runner.invoke(cli, ["branch", "-d", "main"])
275 assert result.exit_code != 0
276 assert "current" in result.stderr.lower() or "main" in result.stderr
277
278 def test_delete_nonexistent_branch_errors(self, repo: pathlib.Path) -> None:
279 result = runner.invoke(cli, ["branch", "-d", "no-such-branch"])
280 assert result.exit_code != 0
281
282 def test_create_branch_with_b_flag(self, repo: pathlib.Path) -> None:
283 result = runner.invoke(cli, ["branch", "new-feature"])
284 assert result.exit_code == 0
285
286 def test_branch_with_slash_shown_in_list(self, repo: pathlib.Path) -> None:
287 runner.invoke(cli, ["branch", "feature/my-thing"])
288 result = runner.invoke(cli, ["branch"])
289 assert "feature/my-thing" in result.output
File History 1 commit