test: add 106 unit + smoke tests, 93% coverage

- pytest + respx + pytest-cov added as dev dependencies
- tests/smoke/test_registration.py: verify all 66 tools registered and unique
- tests/unit/: 12 files covering every tool — HTTP method, URL, params/body, errors
- tests/config.test.json + conftest.py: zero-network test setup via GITEA_CONFIG
- CLAUDE.md: developer guide with testing philosophy, pyramid, and commands
- server/gitea_mcp.py: get_commit, list_pr_files tools added
- README.md: tool count corrected to 66
This commit is contained in:
2026-06-08 09:09:44 +03:00
parent 50cd8e85e6
commit a6162a855c
23 changed files with 1722 additions and 4 deletions
+149
View File
@@ -0,0 +1,149 @@
import json
import pytest
import httpx
import respx
from server.gitea_mcp import (
list_issues, get_issue, create_issue, update_issue, close_issue, reopen_issue,
list_issue_comments, add_issue_comment, edit_issue_comment, delete_issue_comment,
)
BASE = "https://gitea.test/api/v1"
@respx.mock
def test_list_issues_defaults():
route = respx.get(f"{BASE}/repos/owner/repo/issues").mock(
return_value=httpx.Response(200, json=[])
)
list_issues("owner", "repo")
params = dict(route.calls[0].request.url.params)
assert params["state"] == "open"
assert params["type"] == "issues"
assert params["limit"] == "20"
@respx.mock
def test_list_issues_with_filters():
route = respx.get(f"{BASE}/repos/owner/repo/issues").mock(
return_value=httpx.Response(200, json=[])
)
list_issues("owner", "repo", state="closed", labels="bug", assignee="alice")
params = dict(route.calls[0].request.url.params)
assert params["state"] == "closed"
assert params["labels"] == "bug"
assert params["assignee"] == "alice"
@respx.mock
def test_get_issue():
route = respx.get(f"{BASE}/repos/owner/repo/issues/42").mock(
return_value=httpx.Response(200, json={"number": 42, "title": "Bug"})
)
result = get_issue("owner", "repo", 42)
assert route.called
assert result["number"] == 42
@respx.mock
def test_get_issue_not_found():
respx.get(f"{BASE}/repos/owner/repo/issues/999").mock(
return_value=httpx.Response(404, json={"message": "not found"})
)
with pytest.raises(RuntimeError, match="404"):
get_issue("owner", "repo", 999)
@respx.mock
def test_create_issue():
route = respx.post(f"{BASE}/repos/owner/repo/issues").mock(
return_value=httpx.Response(201, json={"number": 1, "title": "New bug"})
)
result = create_issue("owner", "repo", "New bug", body="description")
assert route.called
body = json.loads(route.calls[0].request.content)
assert body["title"] == "New bug"
assert body["body"] == "description"
@respx.mock
def test_create_issue_with_assignees_and_labels():
route = respx.post(f"{BASE}/repos/owner/repo/issues").mock(
return_value=httpx.Response(201, json={"number": 2})
)
create_issue("owner", "repo", "Task", assignees=["alice"], labels=[1, 2])
body = json.loads(route.calls[0].request.content)
assert body["assignees"] == ["alice"]
assert body["labels"] == [1, 2]
@respx.mock
def test_update_issue():
route = respx.patch(f"{BASE}/repos/owner/repo/issues/5").mock(
return_value=httpx.Response(200, json={"number": 5})
)
update_issue("owner", "repo", 5, title="Updated title")
body = json.loads(route.calls[0].request.content)
assert body["title"] == "Updated title"
assert "body" not in body
@respx.mock
def test_close_issue():
route = respx.patch(f"{BASE}/repos/owner/repo/issues/3").mock(
return_value=httpx.Response(200, json={"state": "closed"})
)
result = close_issue("owner", "repo", 3)
assert route.called
body = json.loads(route.calls[0].request.content)
assert body["state"] == "closed"
@respx.mock
def test_reopen_issue():
route = respx.patch(f"{BASE}/repos/owner/repo/issues/3").mock(
return_value=httpx.Response(200, json={"state": "open"})
)
reopen_issue("owner", "repo", 3)
body = json.loads(route.calls[0].request.content)
assert body["state"] == "open"
@respx.mock
def test_list_issue_comments():
route = respx.get(f"{BASE}/repos/owner/repo/issues/7/comments").mock(
return_value=httpx.Response(200, json=[{"id": 1, "body": "hi"}])
)
result = list_issue_comments("owner", "repo", 7)
assert route.called
assert result[0]["id"] == 1
@respx.mock
def test_add_issue_comment():
route = respx.post(f"{BASE}/repos/owner/repo/issues/7/comments").mock(
return_value=httpx.Response(201, json={"id": 10, "body": "LGTM"})
)
result = add_issue_comment("owner", "repo", 7, "LGTM")
assert route.called
body = json.loads(route.calls[0].request.content)
assert body["body"] == "LGTM"
@respx.mock
def test_edit_issue_comment():
route = respx.patch(f"{BASE}/repos/owner/repo/issues/comments/42").mock(
return_value=httpx.Response(200, json={"id": 42})
)
edit_issue_comment("owner", "repo", 42, "edited body")
body = json.loads(route.calls[0].request.content)
assert body["body"] == "edited body"
@respx.mock
def test_delete_issue_comment():
route = respx.delete(f"{BASE}/repos/owner/repo/issues/comments/42").mock(
return_value=httpx.Response(204)
)
result = delete_issue_comment("owner", "repo", 42)
assert route.called
assert result == {"success": True}