"""Tests for review_pr.py. Mocks api_request and credentials. """ import io import os import sys import unittest from unittest.mock import patch sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent)) import review_pr # noqa: E402 FAKE_CREDS = "Basic dGVzdHVzZXI6dGVzdHBhc3M=" FAKE_PR_DATA = { "number": 81, "state": "open", "head": { "ref": "feature-branch", "sha": "abcdef1234567890" }, "base": { "ref": "main" }, "html_url": "https://gitea.example.com/pulls/81" } class TestArgParsing(unittest.TestCase): def setUp(self): self.exists_patcher = patch("os.path.exists", return_value=False) self.exists_patcher.start() self.addCleanup(self.exists_patcher.stop) def test_missing_pr_number_exits(self): with self.assertRaises(SystemExit): review_pr.main([]) class TestAPIPayload(unittest.TestCase): def setUp(self): self.exists_patcher = patch("os.path.exists", return_value=False) self.exists_patcher.start() self.addCleanup(self.exists_patcher.stop) def test_review_submission_fails_closed_without_api_call(self): # #211: live review submission must use gated MCP tools, not CLI POST. import io buf = io.StringIO() with patch.object(sys, "stderr", buf): rc = review_pr.main([ "--pr-number", "81", "--event", "APPROVE", "--body", "Approved and ready to merge", ]) self.assertEqual(rc, 2) self.assertIn("disabled", buf.getvalue().lower()) self.assertIn("gitea_submit_pr_review", buf.getvalue()) def test_merge_flag_fails_closed_without_api_call(self): # --merge is an ungated bypass and is disabled (#16). It must fail # closed BEFORE any API call — no review, no merge. rc = review_pr.main([ "--pr-number", "81", "--event", "APPROVE", "--body", "Approved", "--merge", "--merge-method", "squash", ]) self.assertEqual(rc, 2) def test_merge_flag_message_points_to_gated_workflow(self): import io buf = io.StringIO() with patch.object(sys, "stderr", buf): rc = review_pr.main([ "--pr-number", "81", "--event", "APPROVE", "--merge", ]) self.assertEqual(rc, 2) msg = buf.getvalue().lower() self.assertIn("disabled", msg) self.assertIn("gitea_merge_pr", msg) class TestMutationAuthorityLock(unittest.TestCase): """#199 (refs #194): the CLI refuses to run under active MCP sessions.""" def test_cli_disabled_on_session_lock(self): # When running inside an MCP session, direct review submission via CLI is disabled entirely. import io buf = io.StringIO() with patch.dict(os.environ, {"GITEA_SESSION_PROFILE_LOCK": "prgs-author"}), \ patch.object(sys, "stderr", buf): rc = review_pr.main([ "--pr-number", "81", "--event", "APPROVE", ]) self.assertEqual(rc, 2) msg = buf.getvalue().lower() self.assertIn("disabled", msg) self.assertIn("gitea_submit_pr_review", msg) def test_cli_disabled_even_without_session_lock(self): # #211: CLI review submission is fail-closed regardless of session lock. env = {k: v for k, v in os.environ.items() if k != "GITEA_SESSION_PROFILE_LOCK"} with patch.dict(os.environ, env, clear=True): rc = review_pr.main([ "--pr-number", "81", "--event", "APPROVE", ]) self.assertEqual(rc, 2) if __name__ == "__main__": unittest.main()