import sys import unittest from unittest.mock import patch sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent)) import branch_cleanup_guard as guard # noqa: E402 import mcp_server # noqa: E402 import task_capability_map # noqa: E402 from final_report_validator import assess_final_report_validator # noqa: E402 from mcp_server import gitea_cleanup_merged_pr_branch # noqa: E402 FAKE_AUTH = "token fake" class TestRawBranchDeleteGuard(unittest.TestCase): def test_detects_local_and_remote_raw_git_delete_commands(self): text = """ Cleanup mutations: - git branch -d feat/issue-485-lease-comments-non-list-guard - git push prgs --delete feat/issue-485-lease-comments-non-list-guard """ commands = guard.raw_branch_delete_commands(text) self.assertEqual(len(commands), 2) self.assertIn("git branch -d", commands[0]) self.assertIn("git push prgs --delete", commands[1]) def test_final_report_blocks_raw_delete_as_cleanup_proof(self): report = """ ## Controller Handoff - Task: review_pr - Cleanup mutations: git push prgs --delete feat/branch """ result = assess_final_report_validator(report, "review_pr") self.assertTrue(result["blocked"]) self.assertTrue( any("shared.raw_branch_delete_bypass" in r for r in result["reasons"]) ) def test_cleanup_task_requires_branch_delete_capability(self): self.assertEqual( task_capability_map.required_permission("cleanup_merged_pr_branch"), "gitea.branch.delete", ) self.assertEqual( task_capability_map.required_role("cleanup_merged_pr_branch"), "reconciler", ) class TestMergedPrBranchCleanupTool(unittest.TestCase): def setUp(self): self._remotes = patch.dict( mcp_server.REMOTES, { "prgs": { "host": "gitea.example.com", "org": "Example-Org", "repo": "Example-Repo", } }, ) self._remotes.start() patch("gitea_audit.audit_enabled", return_value=False).start() self.mock_api = patch("mcp_server.api_request").start() self.mock_all = patch("mcp_server.api_get_all", return_value=[]).start() patch("mcp_server.get_auth_header", return_value=FAKE_AUTH).start() patch( "mcp_server.merged_cleanup_reconcile.is_head_ancestor_of_ref", return_value=True, ).start() def tearDown(self): patch.stopall() def test_reviewer_without_delete_authority_fails_closed(self): patch( "mcp_server.get_profile", return_value={ "profile_name": "reviewer", "allowed_operations": ["gitea.read", "gitea.pr.review"], "forbidden_operations": ["gitea.branch.delete"], }, ).start() res = gitea_cleanup_merged_pr_branch( pr_number=487, confirmation="CLEANUP MERGED PR 487 BRANCH feat/branch", branch="feat/branch", remote="prgs", worktree_path="/tmp/repo/branches/cleanup", ) self.assertFalse(res["performed"]) self.assertEqual(res["required_permission"], "gitea.branch.delete") self.mock_api.assert_not_called() def test_root_checkout_cleanup_fails_closed(self): patch( "mcp_server.get_profile", return_value={ "profile_name": "branch-cleanup", "allowed_operations": ["gitea.read", "gitea.branch.delete"], "forbidden_operations": [], }, ).start() res = gitea_cleanup_merged_pr_branch( pr_number=487, confirmation="CLEANUP MERGED PR 487 BRANCH feat/branch", branch="feat/branch", remote="prgs", worktree_path="/tmp/repo/Gitea-Tools", ) self.assertFalse(res["performed"]) self.assertIn("root checkout", " ".join(res["reasons"])) self.mock_api.assert_not_called() def test_authorized_cleanup_deletes_through_api_path(self): branch = "feat/issue-485-lease-comments-non-list-guard" patch( "mcp_server.get_profile", return_value={ "profile_name": "branch-cleanup", "allowed_operations": ["gitea.read", "gitea.branch.delete"], "forbidden_operations": [], }, ).start() self.mock_api.side_effect = [ { "number": 487, "merged": True, "merged_at": "2026-07-08T01:00:00Z", "head": {"ref": branch, "sha": "a" * 40}, "base": {"ref": "master"}, }, {}, {}, ] res = gitea_cleanup_merged_pr_branch( pr_number=487, confirmation=f"CLEANUP MERGED PR 487 BRANCH {branch}", branch=branch, remote="prgs", worktree_path="/tmp/repo/branches/cleanup", ) self.assertTrue(res["performed"]) delete_calls = [ call for call in self.mock_api.call_args_list if call.args[0] == "DELETE" ] self.assertEqual(len(delete_calls), 1) self.assertIn("branches/feat%2Fissue-485", delete_calls[0].args[1]) def test_confirmation_required_before_delete(self): branch = "feat/issue-485-lease-comments-non-list-guard" patch( "mcp_server.get_profile", return_value={ "profile_name": "branch-cleanup", "allowed_operations": ["gitea.read", "gitea.branch.delete"], "forbidden_operations": [], }, ).start() self.mock_api.side_effect = [ { "number": 487, "merged": True, "merged_at": "2026-07-08T01:00:00Z", "head": {"ref": branch, "sha": "a" * 40}, "base": {"ref": "master"}, }, {}, ] res = gitea_cleanup_merged_pr_branch( pr_number=487, confirmation="wrong", branch=branch, remote="prgs", worktree_path="/tmp/repo/branches/cleanup", ) self.assertFalse(res["performed"]) self.assertIn("confirmation", " ".join(res["reasons"])) delete_calls = [ call for call in self.mock_api.call_args_list if call.args[0] == "DELETE" ] self.assertFalse(delete_calls) if __name__ == "__main__": unittest.main()