cgcardona / muse public
test_cli_reset_revert.py python
236 lines 7.7 KB
c8984819 test: bring core VCS coverage from 60% to 91% Gabriel Cardona <gabriel@tellurstori.com> 3d ago
1 """Tests for muse reset and muse revert."""
2 from __future__ import annotations
3
4 import pathlib
5
6 import pytest
7 from typer.testing import CliRunner
8
9 from muse.cli.app import cli
10 from muse.core.store import get_head_commit_id
11
12 runner = CliRunner()
13
14
15 @pytest.fixture
16 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
17 monkeypatch.chdir(tmp_path)
18 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
19 result = runner.invoke(cli, ["init"])
20 assert result.exit_code == 0, result.output
21 return tmp_path
22
23
24 def _write(repo: pathlib.Path, filename: str, content: str = "data") -> None:
25 (repo / "muse-work" / filename).write_text(content)
26
27
28 def _commit(msg: str = "initial") -> None:
29 result = runner.invoke(cli, ["commit", "-m", msg])
30 assert result.exit_code == 0, result.output
31
32
33 def _head_id(repo: pathlib.Path) -> str | None:
34 return get_head_commit_id(repo, "main")
35
36
37 # ---------------------------------------------------------------------------
38 # reset
39 # ---------------------------------------------------------------------------
40
41
42 class TestResetSoft:
43 def test_moves_branch_pointer(self, repo: pathlib.Path) -> None:
44 _write(repo, "beat.mid", "v1")
45 _commit("first")
46 first_id = _head_id(repo)
47
48 _write(repo, "beat.mid", "v2")
49 _commit("second")
50 assert _head_id(repo) != first_id
51
52 result = runner.invoke(cli, ["reset", first_id])
53 assert result.exit_code == 0, result.output
54 assert _head_id(repo) == first_id
55
56 def test_soft_preserves_workdir(self, repo: pathlib.Path) -> None:
57 _write(repo, "beat.mid", "v1")
58 _commit("first")
59 first_id = _head_id(repo)
60
61 _write(repo, "lead.mid", "new")
62 _commit("second")
63
64 runner.invoke(cli, ["reset", first_id])
65 # workdir still has lead.mid from second commit (soft = no restore)
66 assert (repo / "muse-work" / "lead.mid").exists()
67
68 def test_soft_output_message(self, repo: pathlib.Path) -> None:
69 _write(repo, "beat.mid")
70 _commit("first")
71 first_id = _head_id(repo)
72 _write(repo, "lead.mid")
73 _commit("second")
74
75 result = runner.invoke(cli, ["reset", first_id])
76 assert "Moved" in result.output or first_id[:8] in result.output
77
78 def test_reset_unknown_ref_errors(self, repo: pathlib.Path) -> None:
79 _write(repo, "beat.mid")
80 _commit("only")
81 result = runner.invoke(cli, ["reset", "deadbeef"])
82 assert result.exit_code != 0
83 assert "not found" in result.output.lower() or "deadbeef" in result.output
84
85
86 class TestResetHard:
87 def test_moves_branch_pointer(self, repo: pathlib.Path) -> None:
88 _write(repo, "beat.mid", "v1")
89 _commit("first")
90 first_id = _head_id(repo)
91
92 _write(repo, "beat.mid", "v2")
93 _commit("second")
94
95 result = runner.invoke(cli, ["reset", "--hard", first_id])
96 assert result.exit_code == 0, result.output
97 assert _head_id(repo) == first_id
98
99 def test_restores_workdir(self, repo: pathlib.Path) -> None:
100 _write(repo, "beat.mid", "v1")
101 _commit("first")
102 first_id = _head_id(repo)
103
104 _write(repo, "lead.mid", "new")
105 _commit("second")
106
107 runner.invoke(cli, ["reset", "--hard", first_id])
108 # After hard reset, workdir should reflect first commit (no lead.mid)
109 assert not (repo / "muse-work" / "lead.mid").exists()
110 assert (repo / "muse-work" / "beat.mid").exists()
111
112 def test_restores_file_content(self, repo: pathlib.Path) -> None:
113 _write(repo, "beat.mid", "original")
114 _commit("first")
115 first_id = _head_id(repo)
116
117 _write(repo, "beat.mid", "modified")
118 _commit("second")
119
120 runner.invoke(cli, ["reset", "--hard", first_id])
121 assert (repo / "muse-work" / "beat.mid").read_text() == "original"
122
123 def test_hard_output_shows_commit(self, repo: pathlib.Path) -> None:
124 _write(repo, "beat.mid")
125 _commit("the target")
126 first_id = _head_id(repo)
127 _write(repo, "lead.mid")
128 _commit("second")
129
130 result = runner.invoke(cli, ["reset", "--hard", first_id])
131 assert result.exit_code == 0
132 assert "HEAD is now at" in result.output
133
134
135 # ---------------------------------------------------------------------------
136 # revert
137 # ---------------------------------------------------------------------------
138
139
140 class TestRevert:
141 def test_creates_new_commit(self, repo: pathlib.Path) -> None:
142 _write(repo, "beat.mid")
143 _commit("add beat")
144 before_id = _head_id(repo)
145
146 _write(repo, "lead.mid")
147 _commit("add lead")
148 after_id = _head_id(repo)
149
150 result = runner.invoke(cli, ["revert", after_id])
151 assert result.exit_code == 0, result.output
152 new_id = _head_id(repo)
153 assert new_id not in (before_id, after_id)
154
155 def test_revert_restores_parent_state(self, repo: pathlib.Path) -> None:
156 _write(repo, "beat.mid", "original")
157 _commit("first")
158
159 _write(repo, "beat.mid", "changed")
160 _commit("second")
161 second_id = _head_id(repo)
162
163 runner.invoke(cli, ["revert", second_id])
164 assert (repo / "muse-work" / "beat.mid").read_text() == "original"
165
166 def test_revert_default_message_includes_original(self, repo: pathlib.Path) -> None:
167 # Need a base commit first so "my change" is not the root
168 _write(repo, "base.mid", "base")
169 _commit("base")
170
171 _write(repo, "beat.mid")
172 _commit("my change")
173 commit_id = _head_id(repo)
174
175 _write(repo, "lead.mid")
176 _commit("third")
177
178 result = runner.invoke(cli, ["revert", commit_id])
179 assert result.exit_code == 0
180 assert "my change" in result.output or "Revert" in result.output
181
182 def test_revert_custom_message(self, repo: pathlib.Path) -> None:
183 _write(repo, "base.mid", "base")
184 _commit("base")
185 _write(repo, "beat.mid")
186 _commit("to revert")
187 commit_id = _head_id(repo)
188 _write(repo, "lead.mid")
189 _commit("third")
190
191 result = runner.invoke(cli, ["revert", "-m", "undo that change", commit_id])
192 assert result.exit_code == 0, result.output
193 assert "undo that change" in result.output
194
195 def test_revert_no_commit_flag(self, repo: pathlib.Path) -> None:
196 _write(repo, "base.mid", "base")
197 _commit("base")
198
199 _write(repo, "beat.mid")
200 _commit("second")
201 second_id = _head_id(repo)
202
203 _write(repo, "lead.mid")
204 _commit("third")
205
206 result = runner.invoke(cli, ["revert", "--no-commit", second_id])
207 assert result.exit_code == 0, result.output
208 assert "muse-work" in result.output or "applied" in result.output.lower()
209 # HEAD should not have advanced
210 assert _head_id(repo) != second_id # third is still HEAD
211
212 def test_revert_unknown_ref_errors(self, repo: pathlib.Path) -> None:
213 _write(repo, "beat.mid")
214 _commit("only")
215 result = runner.invoke(cli, ["revert", "deadbeef"])
216 assert result.exit_code != 0
217
218 def test_revert_root_commit_errors(self, repo: pathlib.Path) -> None:
219 _write(repo, "beat.mid")
220 _commit("root")
221 root_id = _head_id(repo)
222
223 result = runner.invoke(cli, ["revert", root_id])
224 assert result.exit_code != 0
225 assert "root" in result.output.lower() or "parent" in result.output.lower()
226
227 def test_revert_removes_added_file(self, repo: pathlib.Path) -> None:
228 _write(repo, "beat.mid", "base")
229 _commit("base")
230
231 _write(repo, "lead.mid", "added")
232 _commit("add lead")
233 lead_commit = _head_id(repo)
234
235 runner.invoke(cli, ["revert", lead_commit])
236 assert not (repo / "muse-work" / "lead.mid").exists()