"""Tests for create_pr.py. Every test mocks `get_credentials` and `urllib.request.urlopen` so no real network calls or keychain access are performed. """ import io import json import sys import unittest from unittest.mock import MagicMock, patch sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent)) import create_pr # noqa: E402 FAKE_CREDS = ("testuser", "testpass") def _mock_urlopen(body=None): if body is None: body = {"number": 99, "html_url": "https://gitea.example.com/pulls/99"} resp = MagicMock() resp.read.return_value = json.dumps(body).encode("utf-8") resp.__enter__ = lambda s: s resp.__exit__ = MagicMock(return_value=False) return resp # --------------------------------------------------------------------------- # Argument parsing # --------------------------------------------------------------------------- class TestArgParsing(unittest.TestCase): @patch("create_pr.urllib.request.urlopen", return_value=_mock_urlopen()) @patch("create_pr.get_credentials", return_value=FAKE_CREDS) def test_minimal_required_args(self, _cred, _url): rc = create_pr.main(["--title", "PR Title", "--head", "feat/branch"]) self.assertEqual(rc, 0) def test_missing_title_exits(self): with self.assertRaises(SystemExit): create_pr.main(["--head", "feat/branch"]) def test_missing_head_exits(self): with self.assertRaises(SystemExit): create_pr.main(["--title", "PR Title"]) def test_invalid_remote_exits(self): with self.assertRaises(SystemExit): create_pr.main(["--remote", "nope", "--title", "T", "--head", "h"]) # --------------------------------------------------------------------------- # Remote resolution # --------------------------------------------------------------------------- class TestRemoteResolution(unittest.TestCase): @patch("create_pr.urllib.request.urlopen", return_value=_mock_urlopen()) @patch("create_pr.get_credentials", return_value=FAKE_CREDS) def test_default_dadeschools(self, mock_cred, _url): create_pr.main(["--title", "T", "--head", "h"]) mock_cred.assert_called_with("gitea.dadeschools.net") @patch("create_pr.urllib.request.urlopen", return_value=_mock_urlopen()) @patch("create_pr.get_credentials", return_value=FAKE_CREDS) def test_prgs_remote(self, mock_cred, _url): create_pr.main(["--remote", "prgs", "--title", "T", "--head", "h"]) mock_cred.assert_called_with("gitea.prgs.cc") # --------------------------------------------------------------------------- # API payload # --------------------------------------------------------------------------- class TestAPIPayload(unittest.TestCase): @patch("create_pr.get_credentials", return_value=FAKE_CREDS) def test_payload_fields(self, _cred): with patch("create_pr.urllib.request.Request") as MockReq: MockReq.return_value = MagicMock() with patch("create_pr.urllib.request.urlopen", return_value=_mock_urlopen()): create_pr.main([ "--title", "My PR", "--head", "feat/x", "--base", "develop", "--body", "Closes #1", ]) data = json.loads(MockReq.call_args[1]["data"].decode("utf-8")) self.assertEqual(data["title"], "My PR") self.assertEqual(data["head"], "feat/x") self.assertEqual(data["base"], "develop") self.assertEqual(data["body"], "Closes #1") @patch("create_pr.get_credentials", return_value=FAKE_CREDS) def test_default_base_is_main(self, _cred): with patch("create_pr.urllib.request.Request") as MockReq: MockReq.return_value = MagicMock() with patch("create_pr.urllib.request.urlopen", return_value=_mock_urlopen()): create_pr.main(["--title", "T", "--head", "h"]) data = json.loads(MockReq.call_args[1]["data"].decode("utf-8")) self.assertEqual(data["base"], "main") @patch("create_pr.get_credentials", return_value=FAKE_CREDS) def test_url_prgs(self, _cred): with patch("create_pr.urllib.request.Request") as MockReq: MockReq.return_value = MagicMock() with patch("create_pr.urllib.request.urlopen", return_value=_mock_urlopen()): create_pr.main(["--remote", "prgs", "--title", "T", "--head", "h"]) url = MockReq.call_args[0][0] self.assertEqual( url, "https://gitea.prgs.cc/api/v1/repos/Scaled-Tech-Consulting/Timesheet/pulls", ) # --------------------------------------------------------------------------- # Auth failure # --------------------------------------------------------------------------- class TestAuthFailure(unittest.TestCase): @patch("create_pr.get_credentials", return_value=("", "")) def test_no_credentials_returns_1(self, _cred): rc = create_pr.main(["--title", "T", "--head", "h"]) self.assertEqual(rc, 1) # --------------------------------------------------------------------------- # HTTP error # --------------------------------------------------------------------------- class TestHTTPError(unittest.TestCase): @patch("create_pr.get_credentials", return_value=FAKE_CREDS) def test_422_returns_1(self, _cred): import urllib.error err = urllib.error.HTTPError( url="https://example.com", code=422, msg="Unprocessable", hdrs=None, fp=io.BytesIO(b'{"message":"branch not found"}'), ) with patch("create_pr.urllib.request.urlopen", side_effect=err): rc = create_pr.main(["--title", "T", "--head", "h"]) self.assertEqual(rc, 1) if __name__ == "__main__": unittest.main()