"""Tests for the MCP server tool functions. Each tool is tested by calling the underlying function directly (not through the MCP protocol) with mocked API responses. """ import os import sys import unittest from unittest.mock import patch, MagicMock sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent)) from mcp_server import ( # noqa: E402 gitea_create_issue, gitea_create_pr, gitea_close_issue, gitea_list_issues, gitea_view_issue, gitea_mark_issue, gitea_mirror_refs, ) FAKE_AUTH = "Basic dGVzdDp0ZXN0" # --------------------------------------------------------------------------- # Create Issue # --------------------------------------------------------------------------- class TestCreateIssue(unittest.TestCase): @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_creates_issue(self, _auth, mock_api): mock_api.return_value = {"number": 1, "html_url": "https://gitea.example.com/issues/1"} result = gitea_create_issue(title="Test issue", body="body text") self.assertEqual(result["number"], 1) self.assertIn("issues/1", result["url"]) mock_api.assert_called_once() # Verify payload call_args = mock_api.call_args self.assertEqual(call_args[0][0], "POST") self.assertEqual(call_args[0][3]["title"], "Test issue") @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_creates_on_prgs(self, _auth, mock_api): mock_api.return_value = {"number": 5, "html_url": "https://gitea.prgs.cc/issues/5"} result = gitea_create_issue(title="Test", remote="prgs") self.assertEqual(result["number"], 5) url = mock_api.call_args[0][1] self.assertIn("gitea.prgs.cc", url) self.assertIn("Scaled-Tech-Consulting", url) # --------------------------------------------------------------------------- # Create PR # --------------------------------------------------------------------------- class TestCreatePR(unittest.TestCase): @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_creates_pr(self, _auth, mock_api): mock_api.return_value = {"number": 3, "html_url": "https://example.com/pulls/3"} result = gitea_create_pr(title="feat: X", head="feat/x", base="main") self.assertEqual(result["number"], 3) payload = mock_api.call_args[0][3] self.assertEqual(payload["head"], "feat/x") self.assertEqual(payload["base"], "main") # --------------------------------------------------------------------------- # Close Issue # --------------------------------------------------------------------------- class TestCloseIssue(unittest.TestCase): @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_closes_issue(self, _auth, mock_api): mock_api.return_value = {"state": "closed"} result = gitea_close_issue(issue_number=42) self.assertTrue(result["success"]) self.assertIn("42", result["message"]) payload = mock_api.call_args[0][3] self.assertEqual(payload["state"], "closed") # --------------------------------------------------------------------------- # List Issues # --------------------------------------------------------------------------- class TestListIssues(unittest.TestCase): @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_returns_formatted_list(self, _auth, mock_api): mock_api.return_value = [ { "number": 1, "title": "Bug", "state": "open", "labels": [{"name": "bug"}], "assignee": {"login": "alice"}, }, { "number": 2, "title": "Feature", "state": "open", "labels": [], "assignee": None, }, ] result = gitea_list_issues() self.assertEqual(len(result), 2) self.assertEqual(result[0]["number"], 1) self.assertEqual(result[0]["labels"], ["bug"]) self.assertEqual(result[0]["assignee"], "alice") self.assertEqual(result[1]["assignee"], "") @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_passes_label_filter(self, _auth, mock_api): mock_api.return_value = [] gitea_list_issues(label="important") url = mock_api.call_args[0][1] self.assertIn("labels=important", url) @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_passes_state_filter(self, _auth, mock_api): mock_api.return_value = [] gitea_list_issues(state="closed") url = mock_api.call_args[0][1] self.assertIn("state=closed", url) # --------------------------------------------------------------------------- # View Issue # --------------------------------------------------------------------------- class TestViewIssue(unittest.TestCase): @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_returns_full_details(self, _auth, mock_api): mock_api.return_value = { "number": 7, "title": "MCP server", "body": "Build it", "state": "open", "labels": [{"name": "important"}], "assignee": {"login": "jason"}, "html_url": "https://gitea.prgs.cc/issues/7", } result = gitea_view_issue(issue_number=7, remote="prgs") self.assertEqual(result["number"], 7) self.assertEqual(result["body"], "Build it") self.assertEqual(result["labels"], ["important"]) self.assertEqual(result["assignee"], "jason") # --------------------------------------------------------------------------- # Mark Issue # --------------------------------------------------------------------------- class TestMarkIssue(unittest.TestCase): @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_start_adds_label(self, _auth, mock_api): # First call: get labels; second call: add label mock_api.side_effect = [ [{"id": 10, "name": "status:in-progress"}], [{"name": "status:in-progress"}], ] result = gitea_mark_issue(issue_number=5, action="start") self.assertTrue(result["success"]) self.assertIn("claimed", result["message"]) @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_done_removes_label(self, _auth, mock_api): mock_api.side_effect = [ [{"id": 10, "name": "status:in-progress"}], None, ] result = gitea_mark_issue(issue_number=5, action="done") self.assertTrue(result["success"]) self.assertIn("released", result["message"]) def test_invalid_action_raises(self): with self.assertRaises(ValueError): gitea_mark_issue(issue_number=5, action="pause") @patch("mcp_server.api_request") @patch("mcp_server.get_auth_header", return_value=FAKE_AUTH) def test_missing_label_raises(self, _auth, mock_api): mock_api.return_value = [] # no labels exist with self.assertRaises(RuntimeError): gitea_mark_issue(issue_number=5, action="start") # --------------------------------------------------------------------------- # Auth errors # --------------------------------------------------------------------------- class TestAuthErrors(unittest.TestCase): @patch("mcp_server.get_auth_header", return_value=None) def test_no_credentials_raises(self, _auth): with self.assertRaises(RuntimeError): gitea_create_issue(title="test") def test_unknown_remote_raises(self): with self.assertRaises(ValueError): gitea_create_issue(title="test", remote="nonexistent") # --------------------------------------------------------------------------- # Mirror Refs # --------------------------------------------------------------------------- class TestMirrorRefs(unittest.TestCase): @patch("mcp_server.subprocess.run") def test_dry_run_default(self, mock_run): mock_run.return_value = MagicMock( stdout="DRY RUN\n", stderr="", returncode=0 ) result = gitea_mirror_refs() self.assertEqual(result["return_code"], 0) # Should NOT have --apply args = mock_run.call_args[0][0] self.assertNotIn("--apply", args) self.assertNotIn("--force", args) @patch("mcp_server.subprocess.run") def test_apply_flag(self, mock_run): mock_run.return_value = MagicMock( stdout="done\n", stderr="", returncode=0 ) result = gitea_mirror_refs(apply=True) args = mock_run.call_args[0][0] self.assertIn("--apply", args) @patch("mcp_server.subprocess.run") def test_force_flag(self, mock_run): mock_run.return_value = MagicMock( stdout="", stderr="", returncode=0 ) gitea_mirror_refs(apply=True, force=True) args = mock_run.call_args[0][0] self.assertIn("--apply", args) self.assertIn("--force", args) if __name__ == "__main__": unittest.main()