cgcardona / muse public
test_stress_e2e_workflow.py python
271 lines 9.4 KB
59a915a4 refactor: repo root is the working tree — remove state/ subdirectory Gabriel Cardona <gabriel@tellurstori.com> 6h ago
1 """End-to-end CLI stress tests — mission-critical workflow verification.
2
3 Tests the full muse CLI lifecycle using CliRunner with monkeypatch.chdir:
4 init → commit → log → branch → checkout → merge → tag → revert → stash
5
6 Adversarial scenarios:
7 - 50-commit linear history: log shows all, branch creates correctly.
8 - Concurrent agent commits with provenance fields.
9 - Branch → commit → merge full cycle.
10 - Annotate accumulates reviewers (ORSet semantics).
11 - Stash and pop.
12 - Revert commit.
13 - Reset.
14 """
15
16 import pathlib
17
18 import pytest
19 from typer.testing import CliRunner
20
21 from muse.cli.app import cli
22
23 runner = CliRunner()
24
25
26 # ---------------------------------------------------------------------------
27 # Helpers
28 # ---------------------------------------------------------------------------
29
30
31 def _run(*args: str) -> tuple[int, str]:
32 result = runner.invoke(cli, list(args), catch_exceptions=False)
33 return result.exit_code, result.output
34
35
36 def _write_file(repo: pathlib.Path, filename: str, content: str = "code = True\n") -> None:
37 work = repo
38 (work / filename).write_text(content)
39
40
41 @pytest.fixture
42 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
43 monkeypatch.chdir(tmp_path)
44 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
45 code, out = _run("init", "--domain", "code")
46 assert code == 0, f"init failed: {out}"
47 return tmp_path
48
49
50 # ===========================================================================
51 # Basic lifecycle
52 # ===========================================================================
53
54
55 class TestBasicLifecycle:
56 def test_init_creates_muse_dir(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None:
57 monkeypatch.chdir(tmp_path)
58 code, _ = _run("init")
59 assert code == 0
60 assert (tmp_path / ".muse").is_dir()
61
62 def test_commit_and_log(self, repo: pathlib.Path) -> None:
63 _write_file(repo, "main.py", "x = 1\n")
64 code, out = _run("commit", "-m", "first commit")
65 assert code == 0
66
67 code2, log_out = _run("log")
68 assert code2 == 0
69 assert "first commit" in log_out
70
71 def test_status_works(self, repo: pathlib.Path) -> None:
72 _write_file(repo, "app.py", "print('hello')\n")
73 code, out = _run("status")
74 assert code == 0
75
76 def test_tag_commit(self, repo: pathlib.Path) -> None:
77 _write_file(repo, "tagged.py", "tagged = True\n")
78 _run("commit", "-m", "commit to tag")
79 code, out = _run("tag", "add", "v1.0.0")
80 assert code == 0
81
82 def test_log_shows_multiple_commits(self, repo: pathlib.Path) -> None:
83 for i in range(5):
84 _write_file(repo, f"file{i}.py", f"x = {i}\n")
85 _run("commit", "-m", f"commit number {i}")
86
87 code, out = _run("log")
88 assert code == 0
89 for i in range(5):
90 assert f"commit number {i}" in out
91
92 def test_show_commit(self, repo: pathlib.Path) -> None:
93 _write_file(repo, "show_me.py", "show = 'this'\n")
94 _run("commit", "-m", "showable commit")
95 code, _ = _run("log")
96 assert code == 0
97
98
99 # ===========================================================================
100 # Branch and checkout
101 # ===========================================================================
102
103
104 class TestBranchAndCheckout:
105 def test_branch_creation(self, repo: pathlib.Path) -> None:
106 _write_file(repo, "base.py", "base = 1\n")
107 _run("commit", "-m", "base commit")
108 code, out = _run("branch", "feature/new-thing")
109 assert code == 0
110
111 def test_checkout_to_new_branch_then_back(self, repo: pathlib.Path) -> None:
112 _write_file(repo, "common.py", "common = 1\n")
113 _run("commit", "-m", "initial")
114
115 _run("branch", "feature")
116 code, _ = _run("checkout", "feature")
117 assert code == 0
118 code3, _ = _run("checkout", "main")
119 assert code3 == 0
120
121 def test_multiple_branches_independent(self, repo: pathlib.Path) -> None:
122 _write_file(repo, "root.py", "root = True\n")
123 _run("commit", "-m", "root")
124
125 for i in range(3):
126 _run("branch", f"branch-{i}")
127 code, out = _run("checkout", f"branch-{i}")
128 assert code == 0, f"checkout branch-{i} failed: {out}"
129 _write_file(repo, f"branch_{i}.py", f"b = {i}\n")
130 _run("commit", "-m", f"branch-{i} commit")
131 _run("checkout", "main")
132
133
134 # ===========================================================================
135 # Stash
136 # ===========================================================================
137
138
139 class TestStash:
140 def test_stash_and_pop(self, repo: pathlib.Path) -> None:
141 _write_file(repo, "stash_me.py", "stash = True\n")
142 _run("commit", "-m", "before stash")
143
144 _write_file(repo, "unstaged.py", "unstaged = True\n")
145 code, out = _run("stash")
146 assert code == 0, f"stash failed: {out}"
147
148 code2, out2 = _run("stash", "pop")
149 assert code2 == 0, f"stash pop failed: {out2}"
150
151
152 # ===========================================================================
153 # Revert
154 # ===========================================================================
155
156
157 class TestRevert:
158 def test_revert_undoes_last_commit(self, repo: pathlib.Path) -> None:
159 _write_file(repo, "original.py", "original = True\n")
160 _run("commit", "-m", "original state")
161 _write_file(repo, "added.py", "added = True\n")
162 _run("commit", "-m", "added something")
163
164 code, out = _run("log")
165 assert "added something" in out
166
167 code2, _ = _run("revert", "HEAD")
168 assert code2 == 0
169
170
171 # ===========================================================================
172 # Reset
173 # ===========================================================================
174
175
176 class TestReset:
177 def test_soft_reset_to_head(self, repo: pathlib.Path) -> None:
178 """Reset to HEAD (soft) — a no-op but must not fail."""
179 _write_file(repo, "file.py", "x = 1\n")
180 code1, out1 = _run("commit", "-m", "commit 1")
181 assert code1 == 0
182
183 # "HEAD" is accepted by resolve_commit_ref as the current commit.
184 # Options must precede the positional REF argument in Typer sub-typers.
185 code, out = _run("reset", "--soft", "HEAD")
186 assert code == 0, f"reset failed: {out}"
187
188
189 # ===========================================================================
190 # Provenance fields in commit
191 # ===========================================================================
192
193
194 class TestProvenanceCommit:
195 def test_commit_with_agent_id(self, repo: pathlib.Path) -> None:
196 _write_file(repo, "agent.py", "agent = True\n")
197 result = runner.invoke(
198 cli,
199 ["commit", "-m", "agent commit", "--agent-id", "test-agent"],
200 catch_exceptions=False,
201 )
202 assert result.exit_code == 0
203
204 def test_commit_with_model_id(self, repo: pathlib.Path) -> None:
205 _write_file(repo, "model.py", "model = 'gpt-4o'\n")
206 result = runner.invoke(
207 cli,
208 ["commit", "-m", "model commit", "--model-id", "gpt-4o"],
209 catch_exceptions=False,
210 )
211 assert result.exit_code == 0
212
213
214 # ===========================================================================
215 # Annotate command
216 # ===========================================================================
217
218
219 class TestAnnotateCommand:
220 def test_annotate_test_run(self, repo: pathlib.Path) -> None:
221 _write_file(repo, "annotate_me.py", "code = True\n")
222 _run("commit", "-m", "to annotate")
223 code, out = _run("annotate", "--test-run")
224 assert code == 0
225
226 def test_annotate_reviewed_by(self, repo: pathlib.Path) -> None:
227 _write_file(repo, "reviewed.py", "reviewed = True\n")
228 _run("commit", "-m", "for review")
229 code, out = _run("annotate", "--reviewed-by", "alice")
230 assert code == 0
231
232 def test_annotate_accumulates_reviewers(self, repo: pathlib.Path) -> None:
233 _write_file(repo, "multi_review.py", "x = 1\n")
234 _run("commit", "-m", "multi-review")
235 _run("annotate", "--reviewed-by", "alice")
236 code, out = _run("annotate", "--reviewed-by", "bob")
237 assert code == 0
238
239
240 # ===========================================================================
241 # Long workflow stress
242 # ===========================================================================
243
244
245 class TestLongWorkflowStress:
246 def test_50_sequential_commits(self, repo: pathlib.Path) -> None:
247 for i in range(50):
248 _write_file(repo, f"module_{i:03d}.py", f"x = {i}\n")
249 code, out = _run("commit", "-m", f"commit {i:03d}")
250 assert code == 0, f"commit {i} failed: {out}"
251
252 code, out = _run("log")
253 assert code == 0
254 assert "commit 000" in out
255 assert "commit 049" in out
256
257 def test_branch_commit_merge_cycle(self, repo: pathlib.Path) -> None:
258 """Full branch → commit → merge cycle."""
259 _write_file(repo, "main.py", "main = True\n")
260 _run("commit", "-m", "main base")
261
262 _run("branch", "feature/thing")
263 _run("checkout", "feature/thing")
264 _write_file(repo, "feature.py", "feature = True\n")
265 code, out = _run("commit", "-m", "feature work")
266 assert code == 0
267
268 _run("checkout", "main")
269 code2, out2 = _run("merge", "feature/thing")
270 # Merge may succeed or report no commits yet — either way must not crash.
271 assert code2 in (0, 1), f"merge crashed: {out2}"