"""Integration tests for reconciler merged cleanup (#523).""" import os import sys import unittest from unittest.mock import patch, MagicMock sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent)) import mcp_server import task_capability_map from audit_reconciliation_mode import clear_phase RECONCILER_PROFILE = { "profile_name": "prgs-reconciler", "context": "prgs", "role": "reconciler", "username": "sysadmin", "base_url": "https://gitea.prgs.cc", "allowed_operations": ["gitea.read", "gitea.pr.close", "gitea.pr.comment", "gitea.issue.comment"], "forbidden_operations": [], } class TestReconcilerCleanupIntegration(unittest.TestCase): def setUp(self): clear_phase() mcp_server._preflight_whoami_called = False mcp_server._preflight_capability_called = False mcp_server._preflight_resolved_role = None mcp_server._preflight_resolved_task = None mcp_server._preflight_whoami_violation = False mcp_server._preflight_capability_violation = False mcp_server._preflight_whoami_violation_files = [] mcp_server._preflight_capability_violation_files = [] self.mock_api = patch("gitea_auth.api_request").start() self.mock_auth = patch( "mcp_server.get_auth_header", return_value="token test" ).start() def tearDown(self): patch.stopall() clear_phase() mcp_server._IDENTITY_CACHE.clear() @patch.dict(os.environ, {"GITEA_PROFILE_NAME": "prgs-reconciler"}, clear=True) @patch("mcp_server.get_profile", return_value=RECONCILER_PROFILE) def test_reconciler_can_run_reconcile_merged_cleanups_directly(self, _profile): mcp_server.record_preflight_check("whoami") mcp_server.record_preflight_check("capability", resolved_role="reconciler", resolved_task="reconcile_merged_cleanups") # Mock closed PRs and open PRs returned by API self.mock_api.side_effect = [ [ # closed pulls { "number": 100, "title": "merged feature", "body": "Closes #100", "merged": True, "merged_at": "2026-07-06T12:00:00Z", "merge_commit_sha": "deadbeef", "head": {"ref": "feat/issue-100", "sha": "cafebabe"}, } ], [], # open pulls ] with patch("mcp_server._remote_branch_exists", return_value=True): with patch("mcp_server.merged_cleanup_reconcile.is_head_ancestor_of_ref", return_value=True): result = mcp_server.gitea_reconcile_merged_cleanups( dry_run=True, remote="prgs", ) self.assertTrue(result["success"]) self.assertTrue(result["dry_run"]) self.assertEqual(result["entries"][0]["issue_number"], 100) @patch.dict(os.environ, {"GITEA_PROFILE_NAME": "prgs-reconciler"}, clear=True) @patch("mcp_server.get_profile", return_value=RECONCILER_PROFILE) def test_reconciler_role_bypasses_branches_only_mutation_guard(self, _profile): # We simulate verify_preflight_purity under reconciler role. # It should succeed without raising RuntimeError even if CWD/workspace is the process root. mcp_server.record_preflight_check("whoami") mcp_server.record_preflight_check("capability", resolved_role="reconciler", resolved_task="reconcile_merged_cleanups") # Calling verify_preflight_purity directly try: mcp_server.verify_preflight_purity(remote="prgs", task="reconcile_merged_cleanups") except RuntimeError as e: self.fail(f"verify_preflight_purity raised RuntimeError unexpectedly: {e}") @patch.dict(os.environ, {"GITEA_PROFILE_NAME": "prgs-author", "GITEA_TEST_PORCELAIN": ""}, clear=True) @patch("mcp_server.get_profile", return_value={ "profile_name": "prgs-author", "context": "prgs", "role": "author", "username": "jcwalker3", "base_url": "https://gitea.prgs.cc", "allowed_operations": ["gitea.read", "gitea.pr.create", "gitea.branch.create", "gitea.branch.push", "gitea.repo.commit"], "forbidden_operations": [], }) def test_author_role_remains_blocked_on_root_checkout(self, _profile): mcp_server.record_preflight_check("whoami") mcp_server.record_preflight_check("capability", resolved_role="author", resolved_task="reconcile_merged_cleanups") with patch("mcp_server.PROJECT_ROOT", "/Users/jasonwalker/Development/Gitea-Tools"): with self.assertRaises(RuntimeError) as ctx: mcp_server.verify_preflight_purity(remote="prgs", task="reconcile_merged_cleanups") self.assertIn("author mutation blocked: workspace is the stable control checkout", str(ctx.exception)) if __name__ == "__main__": unittest.main()