"""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) @patch("review_pr.get_auth_header", return_value=FAKE_CREDS) def test_missing_pr_number_exits(self, _auth): 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) @patch("review_pr.api_request") @patch("review_pr.get_auth_header", return_value=FAKE_CREDS) def test_payload_fields_and_workflow(self, _auth, mock_api): # Setup mock api_request to return PR details, then review response mock_api.side_effect = [FAKE_PR_DATA, {}] rc = review_pr.main([ "--pr-number", "81", "--event", "APPROVE", "--body", "Approved and ready to merge", ]) self.assertEqual(rc, 0) self.assertEqual(mock_api.call_count, 2) # Verify first call: GET PR first_call_args = mock_api.call_args_list[0] self.assertEqual(first_call_args[0][0], "GET") self.assertEqual(first_call_args[0][1], "https://gitea.dadeschools.net/api/v1/repos/Contractor/Timesheet/pulls/81") # Verify second call: POST review second_call_args = mock_api.call_args_list[1] self.assertEqual(second_call_args[0][0], "POST") self.assertEqual(second_call_args[0][1], "https://gitea.dadeschools.net/api/v1/repos/Contractor/Timesheet/pulls/81/reviews") payload = second_call_args[0][3] self.assertEqual(payload["event"], "APPROVE") self.assertEqual(payload["body"], "Approved and ready to merge") self.assertEqual(payload["commit_id"], "abcdef1234567890") @patch("review_pr.api_request") @patch("review_pr.get_auth_header", return_value=FAKE_CREDS) def test_merge_flag_fails_closed_without_api_call(self, _auth, mock_api): # --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) self.assertEqual(mock_api.call_count, 0) def test_merge_flag_message_points_to_gated_workflow(self): from _pytest.monkeypatch import MonkeyPatch import io with patch("review_pr.get_auth_header", return_value=FAKE_CREDS), \ patch("review_pr.api_request") as mock_api: buf = io.StringIO() monkeypatch = MonkeyPatch() monkeypatch.setattr(sys, "stderr", buf) try: rc = review_pr.main([ "--pr-number", "81", "--event", "APPROVE", "--merge", ]) finally: monkeypatch.undo() self.assertEqual(rc, 2) self.assertEqual(mock_api.call_count, 0) msg = buf.getvalue().lower() self.assertIn("disabled", msg) self.assertIn("gitea_merge_pr", msg) class TestMutationAuthorityLock(unittest.TestCase): """Issue #194: verify that the CLI tool rejects profile overrides when mismatched with lock.""" @patch("review_pr.get_profile") def test_cli_blocked_on_profile_mismatch(self, mock_get_profile): mock_get_profile.return_value = {"profile_name": "prgs-reviewer"} original_exists = os.path.exists def conditional_exists(path): if "gitea_mutation_authority.lock" in str(path): return True return original_exists(path) original_open = open def conditional_open(file, *args, **kwargs): if "gitea_mutation_authority.lock" in str(file): return io.StringIO('{"current_profile": "prgs-author"}') return original_open(file, *args, **kwargs) from _pytest.monkeypatch import MonkeyPatch import io buf = io.StringIO() monkeypatch = MonkeyPatch() monkeypatch.setattr(sys, "stderr", buf) with patch("os.path.exists", side_effect=conditional_exists), \ patch("builtins.open", side_effect=conditional_open): try: rc = review_pr.main([ "--pr-number", "81", "--event", "APPROVE", ]) finally: monkeypatch.undo() self.assertEqual(rc, 3) msg = buf.getvalue().lower() self.assertIn("cli override rejected", msg) @patch("review_pr.get_profile") @patch("review_pr.api_request") @patch("review_pr.get_auth_header", return_value=FAKE_CREDS) def test_cli_allowed_on_profile_match(self, _auth, mock_api, mock_get_profile): mock_get_profile.return_value = {"profile_name": "prgs-reviewer"} mock_api.side_effect = [FAKE_PR_DATA, {}] original_exists = os.path.exists def conditional_exists(path): if "gitea_mutation_authority.lock" in str(path): return True return original_exists(path) original_open = open def conditional_open(file, *args, **kwargs): if "gitea_mutation_authority.lock" in str(file): return io.StringIO('{"current_profile": "prgs-reviewer"}') return original_open(file, *args, **kwargs) with patch("os.path.exists", side_effect=conditional_exists), \ patch("builtins.open", side_effect=conditional_open): rc = review_pr.main([ "--pr-number", "81", "--event", "APPROVE", ]) self.assertEqual(rc, 0) if __name__ == "__main__": unittest.main()