forked from hyphen-2025/cyber-pilot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_ralphex_modes.py
More file actions
322 lines (282 loc) · 12.9 KB
/
test_ralphex_modes.py
File metadata and controls
322 lines (282 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
"""
Tests for ralphex delegation mode selection and orchestration.
Covers:
- resolve_plans_dir(): config precedence for plans directory resolution
- build_delegation_command(): CLI command assembly with mode flags
- check_review_precondition(): committed changes verification on feature branch
- Worktree constraint enforcement: --worktree only valid for full/tasks-only
"""
import os
import subprocess
import sys
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import patch, MagicMock
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "cypilot" / "scripts"))
from cypilot.ralphex_export import (
resolve_plans_dir,
build_delegation_command,
check_review_precondition,
)
class TestResolvePlansDir:
"""Tests for resolve_plans_dir() — config precedence for plans directory."""
def test_local_ralphex_config_takes_priority(self):
"""plans_dir from .ralphex/config overrides default."""
with TemporaryDirectory() as tmp:
ralphex_dir = Path(tmp) / ".ralphex"
ralphex_dir.mkdir()
(ralphex_dir / "config").write_text(
'plans_dir = "custom/plans"\n', encoding="utf-8"
)
result = resolve_plans_dir(tmp)
assert result == os.path.join(tmp, "custom/plans")
def test_global_config_used_when_no_local(self):
"""plans_dir from ~/.config/ralphex/config used when no .ralphex/ exists."""
with TemporaryDirectory() as tmp:
global_dir = Path(tmp) / "global_config" / "ralphex"
global_dir.mkdir(parents=True)
(global_dir / "config").write_text(
'plans_dir = "global/plans"\n', encoding="utf-8"
)
with patch.dict(os.environ, {"XDG_CONFIG_HOME": str(Path(tmp) / "global_config")}):
result = resolve_plans_dir(tmp)
assert result == os.path.join(tmp, "global/plans")
def test_default_docs_plans_when_no_config(self):
"""Falls back to docs/plans/ when no ralphex config exists."""
with TemporaryDirectory() as tmp:
with patch.dict(os.environ, {"XDG_CONFIG_HOME": str(Path(tmp) / "no_config")}):
result = resolve_plans_dir(tmp)
assert result == os.path.join(tmp, "docs/plans")
def test_default_dir_used_when_no_config(self):
"""Caller-provided default_dir is used when no ralphex config exists."""
with TemporaryDirectory() as tmp:
with patch.dict(os.environ, {"XDG_CONFIG_HOME": str(Path(tmp) / "no_config")}):
result = resolve_plans_dir(tmp, default_dir=".bootstrap/.plans/my-plan")
assert result == os.path.join(tmp, ".bootstrap/.plans/my-plan")
def test_local_config_overrides_global(self):
"""Local .ralphex/config takes precedence over global config."""
with TemporaryDirectory() as tmp:
# Local config
ralphex_dir = Path(tmp) / ".ralphex"
ralphex_dir.mkdir()
(ralphex_dir / "config").write_text(
'plans_dir = "local/plans"\n', encoding="utf-8"
)
# Global config
global_dir = Path(tmp) / "global_config" / "ralphex"
global_dir.mkdir(parents=True)
(global_dir / "config").write_text(
'plans_dir = "global/plans"\n', encoding="utf-8"
)
with patch.dict(os.environ, {"XDG_CONFIG_HOME": str(Path(tmp) / "global_config")}):
result = resolve_plans_dir(tmp)
assert result == os.path.join(tmp, "local/plans")
def test_absolute_plans_dir_in_config(self):
"""Absolute plans_dir in config is returned as-is."""
with TemporaryDirectory() as tmp:
ralphex_dir = Path(tmp) / ".ralphex"
ralphex_dir.mkdir()
(ralphex_dir / "config").write_text(
f'plans_dir = "{tmp}/absolute/plans"\n', encoding="utf-8"
)
result = resolve_plans_dir(tmp)
assert result == os.path.join(tmp, "absolute/plans")
def test_empty_config_falls_back_to_default(self):
"""Empty .ralphex/config falls back to docs/plans/."""
with TemporaryDirectory() as tmp:
ralphex_dir = Path(tmp) / ".ralphex"
ralphex_dir.mkdir()
(ralphex_dir / "config").write_text("", encoding="utf-8")
with patch.dict(os.environ, {"XDG_CONFIG_HOME": str(Path(tmp) / "no_config")}):
result = resolve_plans_dir(tmp)
assert result == os.path.join(tmp, "docs/plans")
def test_explicit_override_takes_highest_priority(self):
"""Explicit override beats local .ralphex/config."""
with TemporaryDirectory() as tmp:
ralphex_dir = Path(tmp) / ".ralphex"
ralphex_dir.mkdir()
(ralphex_dir / "config").write_text(
'plans_dir = "local/plans"\n', encoding="utf-8"
)
result = resolve_plans_dir(tmp, override="explicit/plans")
assert result == os.path.join(tmp, "explicit/plans")
def test_explicit_override_absolute_path(self):
"""Absolute explicit override returned as-is."""
with TemporaryDirectory() as tmp:
result = resolve_plans_dir(tmp, override="/absolute/plans")
assert result == "/absolute/plans"
def test_explicit_override_relative_path(self):
"""Relative explicit override resolved against repo_root."""
with TemporaryDirectory() as tmp:
result = resolve_plans_dir(tmp, override="my/plans")
assert result == os.path.join(tmp, "my/plans")
class TestBuildDelegationCommand:
"""Tests for build_delegation_command() — CLI command assembly."""
def test_full_execute_mode(self):
"""Full execute mode: ralphex <plan.md>."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file="/repo/docs/plans/task.md",
mode="execute",
)
assert cmd == ["/usr/local/bin/ralphex", "/repo/docs/plans/task.md"]
def test_tasks_only_mode(self):
"""Tasks-only mode appends --tasks-only flag."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file="/repo/docs/plans/task.md",
mode="tasks-only",
)
assert cmd == [
"/usr/local/bin/ralphex",
"/repo/docs/plans/task.md",
"--tasks-only",
]
def test_review_mode_without_plan(self):
"""Review mode: ralphex --review (no plan file required)."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file=None,
mode="review",
)
assert cmd == ["/usr/local/bin/ralphex", "--review"]
def test_review_mode_with_plan_as_context(self):
"""Review mode with optional plan file for context."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file="/repo/docs/plans/task.md",
mode="review",
)
assert cmd == [
"/usr/local/bin/ralphex",
"--review",
"/repo/docs/plans/task.md",
]
def test_worktree_flag_for_execute_mode(self):
"""--worktree is appended for full execute mode."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file="/repo/docs/plans/task.md",
mode="execute",
worktree=True,
)
assert "--worktree" in cmd
def test_worktree_flag_for_tasks_only_mode(self):
"""--worktree is appended for tasks-only mode."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file="/repo/docs/plans/task.md",
mode="tasks-only",
worktree=True,
)
assert "--worktree" in cmd
assert "--tasks-only" in cmd
def test_worktree_flag_not_appended_for_review(self):
"""--worktree is NOT appended for review mode (constraint enforced)."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file=None,
mode="review",
worktree=True,
)
assert "--worktree" not in cmd
def test_serve_flag_appended(self):
"""--serve flag is appended when requested."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file="/repo/docs/plans/task.md",
mode="execute",
serve=True,
)
assert "--serve" in cmd
def test_all_flags_combined(self):
"""All flags combine correctly for full execute + worktree + serve."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file="/repo/docs/plans/task.md",
mode="execute",
worktree=True,
serve=True,
)
assert cmd == [
"/usr/local/bin/ralphex",
"/repo/docs/plans/task.md",
"--worktree",
"--serve",
]
def test_serve_flag_not_appended_when_false(self):
"""--serve is not in command when serve=False."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file="/repo/docs/plans/task.md",
mode="execute",
serve=False,
)
assert "--serve" not in cmd
def test_serve_flag_not_appended_for_review(self):
"""--serve is NOT appended for review mode even when requested."""
cmd = build_delegation_command(
ralphex_path="/usr/local/bin/ralphex",
plan_file=None,
mode="review",
serve=True,
)
assert "--serve" not in cmd
class TestCheckReviewPrecondition:
"""Tests for check_review_precondition() — committed changes verification."""
def test_passes_when_branch_has_commits_ahead(self):
"""Precondition passes when feature branch has commits ahead of default."""
proc = MagicMock(returncode=0, stdout="abc1234\ndef5678\n", stderr="")
with patch("cypilot.ralphex_export.subprocess.run", return_value=proc):
result = check_review_precondition(default_branch="main")
assert result["ok"] is True
assert result["commit_count"] == 2
def test_fails_when_no_commits_ahead(self):
"""Precondition fails when feature branch has no commits ahead."""
proc = MagicMock(returncode=0, stdout="", stderr="")
with patch("cypilot.ralphex_export.subprocess.run", return_value=proc):
result = check_review_precondition(default_branch="main")
assert result["ok"] is False
assert "no committed changes" in result["message"].lower()
def test_fails_when_on_default_branch(self):
"""Precondition fails when HEAD is on the default branch itself."""
# git rev-list returns empty (no commits ahead of self)
proc = MagicMock(returncode=0, stdout="", stderr="")
with patch("cypilot.ralphex_export.subprocess.run", return_value=proc):
result = check_review_precondition(default_branch="main")
assert result["ok"] is False
def test_fails_when_git_command_errors(self):
"""Precondition fails gracefully when git command errors."""
proc = MagicMock(returncode=128, stdout="", stderr="fatal: bad revision")
with patch("cypilot.ralphex_export.subprocess.run", return_value=proc):
result = check_review_precondition(default_branch="main")
assert result["ok"] is False
assert "error" in result["message"].lower() or "failed" in result["message"].lower()
def test_uses_default_branch_parameter(self):
"""Git command references the specified default branch."""
proc = MagicMock(returncode=0, stdout="abc1234\n", stderr="")
with patch("cypilot.ralphex_export.subprocess.run", return_value=proc) as mock_run:
check_review_precondition(default_branch="master")
call_args = mock_run.call_args[0][0]
assert any("master" in str(arg) for arg in call_args)
class TestWorktreeConstraintEnforcement:
"""Integration-level tests confirming worktree is only valid for execute/tasks-only."""
def test_worktree_valid_modes(self):
"""--worktree appears only in commands for valid modes."""
for mode in ("execute", "tasks-only"):
cmd = build_delegation_command(
ralphex_path="ralphex",
plan_file="plan.md",
mode=mode,
worktree=True,
)
assert "--worktree" in cmd, f"--worktree should be in {mode} mode"
def test_worktree_invalid_mode(self):
"""--worktree does NOT appear for review mode even when requested."""
cmd = build_delegation_command(
ralphex_path="ralphex",
plan_file=None,
mode="review",
worktree=True,
)
assert "--worktree" not in cmd