"""Tests for the branch-worktree helper scripts (#38/#39). Exercises path generation and refuse-to-overwrite via the scripts' ``--dry-run`` mode, so no real worktrees, branches, network, or deletions are involved. """ import os import subprocess import unittest from pathlib import Path REPO = Path(__file__).resolve().parent.parent SCRIPTS = REPO / "scripts" BRANCHES = REPO / "branches" def run(script, *args): proc = subprocess.run( ["bash", str(SCRIPTS / script), *args], capture_output=True, text=True, cwd=str(REPO), ) return proc.returncode, proc.stdout, proc.stderr class TestWorktreeStart(unittest.TestCase): def test_dry_run_path_generation(self): rc, out, _ = run("worktree-start", "--dry-run", "fix/issue-123-example") self.assertEqual(rc, 0) self.assertIn("branches/fix-issue-123-example", out) self.assertIn("fix/issue-123-example", out) self.assertIn("prgs/master", out) # default start-ref def test_bad_args_exit_2(self): rc, _, _ = run("worktree-start") self.assertEqual(rc, 2) def test_refuses_existing_worktree(self): branch = f"fix/issue-999-refuse-{os.getpid()}" slug = branch.replace("/", "-") target = BRANCHES / slug target.mkdir(parents=True, exist_ok=True) try: rc, _, err = run("worktree-start", "--dry-run", branch) self.assertEqual(rc, 1) self.assertIn("Refusing to reuse", err) finally: target.rmdir() # -- issue-linked branch validation (#48) -------------------------------- def test_accepts_issue_linked_impl_branches(self): for branch in ("fix/issue-123-example", "feat/issue-123-example", "docs/issue-123-example", "chore/issue-123-example"): rc, out, err = run("worktree-start", "--dry-run", branch) self.assertEqual(rc, 0, f"{branch}: {err}") self.assertIn(f"branches/{branch.replace('/', '-')}", out) def test_accepts_review_branch(self): rc, out, _ = run("worktree-start", "--dry-run", "review/pr-456-example") self.assertEqual(rc, 0) self.assertIn("branches/review-pr-456-example", out) def test_rejects_untraceable_branches(self): for branch in ("fix/random-name", "my-branch", "feat/no-issue-here", "fix/issue-abc-x"): rc, _, err = run("worktree-start", "--dry-run", branch) self.assertEqual(rc, 2, branch) self.assertIn("Untraceable branch name", err) def test_allow_unlinked_override(self): rc, out, _ = run("worktree-start", "--dry-run", "--allow-unlinked", "my-branch") self.assertEqual(rc, 0) self.assertIn("branches/my-branch", out) class TestWorktreeReview(unittest.TestCase): def test_dry_run_detached_review_path(self): rc, out, _ = run("worktree-review", "--dry-run", "fix/issue-9-x") self.assertEqual(rc, 0) self.assertIn("branches/review-fix-issue-9-x", out) self.assertIn("--detach", out) self.assertIn("prgs/fix/issue-9-x", out) # default start-ref def test_refuses_existing_review_worktree(self): slug = f"review-zz-refuse-rev-{os.getpid()}" target = BRANCHES / slug target.mkdir(parents=True, exist_ok=True) try: # branch name maps to the same slug: review- rc, _, err = run("worktree-review", "--dry-run", f"zz-refuse-rev-{os.getpid()}") self.assertEqual(rc, 1) self.assertIn("Refusing to reuse", err) finally: target.rmdir() class TestWorktreeClean(unittest.TestCase): def test_missing_worktree_errors(self): rc, _, err = run("worktree-clean", "--dry-run", "does-not-exist-xyz") self.assertEqual(rc, 1) self.assertIn("No such worktree", err) def test_dry_run_does_not_delete(self): slug = f"zz-clean-{os.getpid()}" target = BRANCHES / slug target.mkdir(parents=True, exist_ok=True) try: rc, out, _ = run("worktree-clean", "--dry-run", slug) self.assertEqual(rc, 0) self.assertIn("worktree remove", out) self.assertTrue(target.is_dir()) # nothing removed in dry-run finally: target.rmdir() def test_dry_run_delete_branch_lists_branch_command(self): slug = f"zz-clean-b-{os.getpid()}" target = BRANCHES / slug target.mkdir(parents=True, exist_ok=True) try: rc, out, _ = run("worktree-clean", "--dry-run", "--delete-branch", slug) self.assertEqual(rc, 0) self.assertIn("branch -d", out) finally: target.rmdir() if __name__ == "__main__": unittest.main()