import json import pytest import httpx import respx from gitea_mcp import ( list_pull_requests, get_pull_request, create_pull_request, update_pull_request, merge_pull_request, get_pr_diff, list_pr_files, list_pr_reviews, create_pr_review, ) BASE = "https://gitea.test/api/v1" @respx.mock def test_list_pull_requests(): route = respx.get(f"{BASE}/repos/owner/repo/pulls").mock( return_value=httpx.Response(200, json=[]) ) list_pull_requests("owner", "repo") params = dict(route.calls[0].request.url.params) assert params["state"] == "open" assert params["limit"] == "20" @respx.mock def test_list_pull_requests_closed(): route = respx.get(f"{BASE}/repos/owner/repo/pulls").mock( return_value=httpx.Response(200, json=[]) ) list_pull_requests("owner", "repo", state="closed", limit=5) params = dict(route.calls[0].request.url.params) assert params["state"] == "closed" assert params["limit"] == "5" @respx.mock def test_get_pull_request(): route = respx.get(f"{BASE}/repos/owner/repo/pulls/7").mock( return_value=httpx.Response(200, json={"number": 7, "title": "Add feature"}) ) result = get_pull_request("owner", "repo", 7) assert route.called assert result["number"] == 7 @respx.mock def test_create_pull_request(): route = respx.post(f"{BASE}/repos/owner/repo/pulls").mock( return_value=httpx.Response(201, json={"number": 10}) ) result = create_pull_request("owner", "repo", "Add feature", "feature/x", "main") assert route.called body = json.loads(route.calls[0].request.content) assert body["title"] == "Add feature" assert body["head"] == "feature/x" assert body["base"] == "main" assert "body" not in body @respx.mock def test_create_pull_request_with_body_and_labels(): route = respx.post(f"{BASE}/repos/owner/repo/pulls").mock( return_value=httpx.Response(201, json={"number": 11}) ) create_pull_request( "owner", "repo", "Fix bug", "fix/crash", "main", body="Fixes crash on startup", labels=[3], ) body = json.loads(route.calls[0].request.content) assert body["body"] == "Fixes crash on startup" assert body["labels"] == [3] @respx.mock def test_update_pull_request(): route = respx.patch(f"{BASE}/repos/owner/repo/pulls/5").mock( return_value=httpx.Response(200, json={"number": 5}) ) update_pull_request("owner", "repo", 5, title="New title") body = json.loads(route.calls[0].request.content) assert body["title"] == "New title" assert "body" not in body @respx.mock def test_merge_pull_request_defaults(): route = respx.post(f"{BASE}/repos/owner/repo/pulls/3/merge").mock( return_value=httpx.Response(200, json={"success": True}) ) merge_pull_request("owner", "repo", 3) assert route.called body = json.loads(route.calls[0].request.content) assert body["Do"] == "merge" assert body["delete_branch_after_merge"] is False @respx.mock def test_merge_pull_request_squash_with_delete(): route = respx.post(f"{BASE}/repos/owner/repo/pulls/4/merge").mock( return_value=httpx.Response(200, json={"success": True}) ) merge_pull_request("owner", "repo", 4, merge_style="squash", delete_branch_after_merge=True) body = json.loads(route.calls[0].request.content) assert body["Do"] == "squash" assert body["delete_branch_after_merge"] is True @respx.mock def test_get_pr_diff(): diff_text = "diff --git a/file.py b/file.py\n+added line\n" route = respx.get(f"{BASE}/repos/owner/repo/pulls/2.diff").mock( return_value=httpx.Response(200, text=diff_text) ) result = get_pr_diff("owner", "repo", 2) assert route.called assert result["diff"] == diff_text @respx.mock def test_get_pr_diff_error(): respx.get(f"{BASE}/repos/owner/repo/pulls/99.diff").mock( return_value=httpx.Response(404, text="not found") ) with pytest.raises(RuntimeError, match="404"): get_pr_diff("owner", "repo", 99) @respx.mock def test_list_pr_files(): route = respx.get(f"{BASE}/repos/owner/repo/pulls/5/files").mock( return_value=httpx.Response(200, json=[{"filename": "server/gitea_mcp.py", "status": "modified"}]) ) result = list_pr_files("owner", "repo", 5) assert route.called assert result[0]["filename"] == "server/gitea_mcp.py" params = dict(route.calls[0].request.url.params) assert params["limit"] == "50" assert params["page"] == "1" @respx.mock def test_list_pr_files_with_whitespace(): route = respx.get(f"{BASE}/repos/owner/repo/pulls/6/files").mock( return_value=httpx.Response(200, json=[]) ) list_pr_files("owner", "repo", 6, whitespace="ignore-all") params = dict(route.calls[0].request.url.params) assert params["whitespace"] == "ignore-all" @respx.mock def test_list_pr_reviews(): route = respx.get(f"{BASE}/repos/owner/repo/pulls/8/reviews").mock( return_value=httpx.Response(200, json=[{"id": 1, "state": "APPROVED"}]) ) result = list_pr_reviews("owner", "repo", 8) assert route.called assert result[0]["state"] == "APPROVED" @respx.mock def test_create_pr_review_approved(): route = respx.post(f"{BASE}/repos/owner/repo/pulls/9/reviews").mock( return_value=httpx.Response(200, json={"id": 5, "state": "APPROVED"}) ) result = create_pr_review("owner", "repo", 9, "APPROVED", body="Looks good!") assert route.called body = json.loads(route.calls[0].request.content) assert body["event"] == "APPROVED" assert body["body"] == "Looks good!" @respx.mock def test_create_pr_review_request_changes(): route = respx.post(f"{BASE}/repos/owner/repo/pulls/10/reviews").mock( return_value=httpx.Response(200, json={"id": 6, "state": "REQUEST_CHANGES"}) ) create_pr_review("owner", "repo", 10, "REQUEST_CHANGES") body = json.loads(route.calls[0].request.content) assert body["event"] == "REQUEST_CHANGES" assert "body" not in body