Skip to content

Commit 06b55d9

Browse files
committed
test(worktree): add 4 integration tests for finish idempotency, shared git_root, and upstream
1 parent e67affb commit 06b55d9

1 file changed

Lines changed: 205 additions & 0 deletions

File tree

tests/cli/test_worktree.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,3 +996,208 @@ def test_finish_body_file_dash_empty_stdin_rejected(configured_git_app: Path):
996996
)
997997
assert result.exit_code == 1
998998
assert "empty" in result.output.lower()
999+
1000+
1001+
def test_finish_shared_git_root_creates_one_pr_records_on_all(configured_git_app: Path):
1002+
"""Two repos sharing git_root: one gh pr create call, both get the URL."""
1003+
from mship.cli import container as cli_container
1004+
1005+
# Extend the workspace with a shared-git_root pair.
1006+
cfg_path = configured_git_app / "mothership.yaml"
1007+
cfg_path.write_text(cfg_path.read_text() + """
1008+
infra:
1009+
path: .
1010+
git_root: shared
1011+
type: service
1012+
""")
1013+
1014+
runner.invoke(app, ["spawn", "group prs", "--repos", "shared,infra", "--skip-setup"])
1015+
1016+
create_pr_call_count = 0
1017+
1018+
def mock_run(cmd, cwd, env=None):
1019+
nonlocal create_pr_call_count
1020+
if "gh auth status" in cmd:
1021+
return ShellResult(returncode=0, stdout="Logged in", stderr="")
1022+
if "git push" in cmd:
1023+
return ShellResult(returncode=0, stdout="", stderr="")
1024+
if "rev-parse --abbrev-ref --symbolic-full-name @{u}" in cmd:
1025+
# Pretend upstream is already set after push.
1026+
return ShellResult(returncode=0, stdout="origin/feat/group-prs", stderr="")
1027+
if "gh pr list --head" in cmd:
1028+
# No existing PR on first call.
1029+
return ShellResult(returncode=0, stdout="\n", stderr="")
1030+
if "gh pr create" in cmd:
1031+
create_pr_call_count += 1
1032+
return ShellResult(returncode=0, stdout="https://github.com/org/shared/pull/42\n", stderr="")
1033+
if "gh pr view" in cmd:
1034+
return ShellResult(returncode=0, stdout="body text", stderr="")
1035+
if "gh pr edit" in cmd:
1036+
return ShellResult(returncode=0, stdout="", stderr="")
1037+
if "git log --format=%s" in cmd:
1038+
return ShellResult(returncode=0, stdout="", stderr="")
1039+
return ShellResult(returncode=0, stdout="", stderr="")
1040+
1041+
mock_shell = MagicMock(spec=ShellRunner)
1042+
mock_shell.run.side_effect = mock_run
1043+
mock_shell.run_task.return_value = ShellResult(returncode=0, stdout="ok", stderr="")
1044+
cli_container.shell.override(mock_shell)
1045+
1046+
result = runner.invoke(app, ["finish", "--task", "group-prs"])
1047+
assert result.exit_code == 0, result.output
1048+
assert create_pr_call_count == 1, f"Expected 1 gh pr create call, got {create_pr_call_count}"
1049+
1050+
from mship.core.state import StateManager
1051+
mgr = StateManager(configured_git_app / ".mothership")
1052+
state = mgr.load()
1053+
pr_urls = state.tasks["group-prs"].pr_urls
1054+
assert pr_urls.get("shared") == "https://github.com/org/shared/pull/42"
1055+
assert pr_urls.get("infra") == "https://github.com/org/shared/pull/42"
1056+
1057+
cli_container.shell.reset_override()
1058+
1059+
1060+
def test_finish_harvests_existing_pr_instead_of_creating(configured_git_app: Path):
1061+
"""If a PR for the branch already exists (manual or prior mship run),
1062+
finish harvests it via `gh pr list --head` without calling `gh pr create`."""
1063+
from mship.cli import container as cli_container
1064+
1065+
runner.invoke(app, ["spawn", "reuse pr", "--repos", "shared", "--skip-setup"])
1066+
1067+
create_pr_called = False
1068+
1069+
def mock_run(cmd, cwd, env=None):
1070+
nonlocal create_pr_called
1071+
if "gh auth status" in cmd:
1072+
return ShellResult(returncode=0, stdout="Logged in", stderr="")
1073+
if "git push" in cmd:
1074+
return ShellResult(returncode=0, stdout="", stderr="")
1075+
if "rev-parse --abbrev-ref --symbolic-full-name @{u}" in cmd:
1076+
return ShellResult(returncode=0, stdout="origin/feat/reuse-pr", stderr="")
1077+
if "gh pr list --head" in cmd:
1078+
return ShellResult(returncode=0, stdout="https://github.com/org/shared/pull/88\n", stderr="")
1079+
if "gh pr create" in cmd:
1080+
create_pr_called = True
1081+
return ShellResult(returncode=0, stdout="", stderr="")
1082+
return ShellResult(returncode=0, stdout="", stderr="")
1083+
1084+
mock_shell = MagicMock(spec=ShellRunner)
1085+
mock_shell.run.side_effect = mock_run
1086+
mock_shell.run_task.return_value = ShellResult(returncode=0, stdout="ok", stderr="")
1087+
cli_container.shell.override(mock_shell)
1088+
1089+
result = runner.invoke(app, ["finish", "--task", "reuse-pr"])
1090+
assert result.exit_code == 0, result.output
1091+
assert create_pr_called is False, "gh pr create should not be called when PR already exists"
1092+
1093+
from mship.core.state import StateManager
1094+
mgr = StateManager(configured_git_app / ".mothership")
1095+
state = mgr.load()
1096+
assert state.tasks["reuse-pr"].pr_urls.get("shared") == "https://github.com/org/shared/pull/88"
1097+
1098+
cli_container.shell.reset_override()
1099+
1100+
1101+
def test_finish_harvests_on_create_pr_duplicate_stderr(configured_git_app: Path):
1102+
"""When `gh pr list` returns empty but `gh pr create` then errors with
1103+
'already exists' (race), finish harvests via a second list call."""
1104+
from mship.cli import container as cli_container
1105+
1106+
runner.invoke(app, ["spawn", "race pr", "--repos", "shared", "--skip-setup"])
1107+
1108+
list_call_count = 0
1109+
1110+
def mock_run(cmd, cwd, env=None):
1111+
nonlocal list_call_count
1112+
if "gh auth status" in cmd:
1113+
return ShellResult(returncode=0, stdout="Logged in", stderr="")
1114+
if "git push" in cmd:
1115+
return ShellResult(returncode=0, stdout="", stderr="")
1116+
if "rev-parse --abbrev-ref --symbolic-full-name @{u}" in cmd:
1117+
return ShellResult(returncode=0, stdout="origin/feat/race-pr", stderr="")
1118+
if "gh pr list --head" in cmd:
1119+
list_call_count += 1
1120+
if list_call_count == 1:
1121+
# First call (pre-create check): no PR yet.
1122+
return ShellResult(returncode=0, stdout="\n", stderr="")
1123+
else:
1124+
# Second call (fallback after create failed): PR exists now.
1125+
return ShellResult(
1126+
returncode=0,
1127+
stdout="https://github.com/org/shared/pull/99\n",
1128+
stderr="",
1129+
)
1130+
if "gh pr create" in cmd:
1131+
return ShellResult(
1132+
returncode=1, stdout="",
1133+
stderr="a pull request for branch \"feat/race-pr\" into branch \"main\" already exists",
1134+
)
1135+
return ShellResult(returncode=0, stdout="", stderr="")
1136+
1137+
mock_shell = MagicMock(spec=ShellRunner)
1138+
mock_shell.run.side_effect = mock_run
1139+
mock_shell.run_task.return_value = ShellResult(returncode=0, stdout="ok", stderr="")
1140+
cli_container.shell.override(mock_shell)
1141+
1142+
result = runner.invoke(app, ["finish", "--task", "race-pr"])
1143+
assert result.exit_code == 0, result.output
1144+
assert list_call_count == 2, "Expected pre-check + fallback list calls"
1145+
1146+
from mship.core.state import StateManager
1147+
mgr = StateManager(configured_git_app / ".mothership")
1148+
state = mgr.load()
1149+
assert state.tasks["race-pr"].pr_urls.get("shared") == "https://github.com/org/shared/pull/99"
1150+
1151+
cli_container.shell.reset_override()
1152+
1153+
1154+
def test_finish_calls_ensure_upstream_after_push(configured_git_app: Path):
1155+
"""ensure_upstream fires after push; if @{u} fails, set-upstream-to runs."""
1156+
from mship.cli import container as cli_container
1157+
1158+
runner.invoke(app, ["spawn", "upstream check", "--repos", "shared", "--skip-setup"])
1159+
1160+
set_upstream_called = False
1161+
ensure_upstream_probe_count = 0
1162+
1163+
def mock_run(cmd, cwd, env=None):
1164+
nonlocal set_upstream_called, ensure_upstream_probe_count
1165+
if "gh auth status" in cmd:
1166+
return ShellResult(returncode=0, stdout="Logged in", stderr="")
1167+
if "symbolic-ref" in cmd and "HEAD" in cmd:
1168+
return ShellResult(returncode=0, stdout="main\n", stderr="")
1169+
if "fetch" in cmd:
1170+
return ShellResult(returncode=0, stdout="", stderr="")
1171+
if "rev-list --count" in cmd:
1172+
return ShellResult(returncode=0, stdout="0\n", stderr="")
1173+
if "status --porcelain" in cmd:
1174+
return ShellResult(returncode=0, stdout="", stderr="")
1175+
if "git push" in cmd:
1176+
return ShellResult(returncode=0, stdout="", stderr="")
1177+
if "rev-parse --abbrev-ref --symbolic-full-name @{u}" in cmd:
1178+
ensure_upstream_probe_count += 1
1179+
# First call: audit checks upstream (before push) - pass
1180+
# Subsequent calls: ensure_upstream checks after push - fail to trigger fallback
1181+
if ensure_upstream_probe_count == 1:
1182+
return ShellResult(returncode=0, stdout="origin/main\n", stderr="")
1183+
else:
1184+
return ShellResult(returncode=1, stdout="", stderr="fatal: no upstream")
1185+
if "--set-upstream-to=origin/" in cmd:
1186+
set_upstream_called = True
1187+
return ShellResult(returncode=0, stdout="", stderr="")
1188+
if "gh pr list --head" in cmd:
1189+
return ShellResult(returncode=0, stdout="\n", stderr="")
1190+
if "gh pr create" in cmd:
1191+
return ShellResult(returncode=0, stdout="https://github.com/org/shared/pull/1\n", stderr="")
1192+
return ShellResult(returncode=0, stdout="", stderr="")
1193+
1194+
mock_shell = MagicMock(spec=ShellRunner)
1195+
mock_shell.run.side_effect = mock_run
1196+
mock_shell.run_task.return_value = ShellResult(returncode=0, stdout="ok", stderr="")
1197+
cli_container.shell.override(mock_shell)
1198+
1199+
result = runner.invoke(app, ["finish", "--task", "upstream-check"])
1200+
assert result.exit_code == 0, result.output
1201+
assert set_upstream_called, "ensure_upstream should have run set-upstream-to"
1202+
1203+
cli_container.shell.reset_override()

0 commit comments

Comments
 (0)