import os import sys import unittest from unittest import mock import remote_repo_guard import gitea_mcp_server as server from session_context_binding import _reset_session_context_for_testing, bind_session_context LOCAL_GITEA_TOOLS_URL = "https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git" class TestCrossProjectMutationBoundary(unittest.TestCase): def setUp(self): os.environ["PYTEST_CURRENT_TEST"] = "test" _reset_session_context_for_testing() def tearDown(self): _reset_session_context_for_testing() os.environ.pop("PYTEST_CURRENT_TEST", None) def test_cross_project_codebase_mutation_blocked(self): """Codebase mutations targeting another project must fail closed (#707).""" assessment = remote_repo_guard.assess_remote_repo_match( remote="prgs", resolved_org="Other-Org", resolved_repo="Other-Repo", local_remote_url=LOCAL_GITEA_TOOLS_URL, org_explicit=True, repo_explicit=True, for_mutation=True, ) self.assertTrue(assessment["block"]) self.assertTrue(assessment.get("cross_project_mutation_block")) self.assertEqual(assessment["primary_org"], "Scaled-Tech-Consulting") self.assertEqual(assessment["primary_repo"], "Gitea-Tools") err_msg = remote_repo_guard.format_remote_repo_guard_error(assessment) self.assertIn("Cross-project mutation guard (#707)", err_msg) self.assertIn("Attempted codebase mutation targeting 'Other-Org/Other-Repo'", err_msg) self.assertIn("outside of primary authorized project context 'Scaled-Tech-Consulting/Gitea-Tools'", err_msg) self.assertIn("create an issue in the target repository ('Other-Org/Other-Repo') instead", err_msg) def test_same_project_codebase_mutation_allowed(self): """Codebase mutations targeting the primary project context are allowed.""" assessment = remote_repo_guard.assess_remote_repo_match( remote="prgs", resolved_org="Scaled-Tech-Consulting", resolved_repo="Gitea-Tools", local_remote_url=LOCAL_GITEA_TOOLS_URL, org_explicit=True, repo_explicit=True, for_mutation=True, ) self.assertFalse(assessment["block"]) self.assertFalse(assessment.get("cross_project_mutation_block", False)) def test_cross_project_metadata_operation_allowed(self): """Metadata operations (issue creation, comments) across project boundaries are allowed.""" assessment = remote_repo_guard.assess_remote_repo_match( remote="prgs", resolved_org="Other-Org", resolved_repo="Other-Repo", local_remote_url=LOCAL_GITEA_TOOLS_URL, org_explicit=True, repo_explicit=True, for_mutation=False, ) self.assertTrue(assessment["proven"]) self.assertFalse(assessment["block"]) @mock.patch.dict(os.environ, {"GITEA_FORCE_REMOTE_REPO_CHECK": "1"}) @mock.patch("gitea_mcp_server._local_git_remote_url", return_value=LOCAL_GITEA_TOOLS_URL) def test_mcp_server_resolve_cross_project_mutation_fails_closed(self, mock_remote): """_resolve with for_mutation=True blocks cross-project targets.""" with self.assertRaises(RuntimeError) as ctx: server._resolve("prgs", None, "Other-Org", "Other-Repo", for_mutation=True) err = str(ctx.exception) self.assertIn("Cross-project mutation guard (#707)", err) self.assertIn("create an issue in the target repository", err) @mock.patch.dict(os.environ, {"GITEA_FORCE_REMOTE_REPO_CHECK": "1"}) @mock.patch("gitea_mcp_server._local_git_remote_url", return_value=LOCAL_GITEA_TOOLS_URL) def test_mcp_server_resolve_cross_project_metadata_succeeds(self, mock_remote): """_resolve with for_mutation=False allows explicit cross-project target.""" host, org, repo = server._resolve("prgs", None, "Other-Org", "Other-Repo", for_mutation=False) self.assertEqual(org, "Other-Org") self.assertEqual(repo, "Other-Repo") if __name__ == "__main__": unittest.main()