Remove incorrect standalone MCP implementation

This commit removes the incorrectly structured standalone MCP server that was built without understanding the distinction between standalone and HTTP transport modes. These files will be replaced with proper HTTP transport wrapper components.

Removed:
- src/gitea_mcp/ directory (standalone server implementation)
- tests/ directory (tests for standalone implementation)

This clears the way for implementing the correct HTTP-wrapped architecture.

Closes #9

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 16:06:17 -05:00
parent b94dcebfc7
commit cd55d53f1b
15 changed files with 0 additions and 2424 deletions

View File

@@ -1 +0,0 @@
"""Tests for Gitea MCP server."""

View File

@@ -1,103 +0,0 @@
"""Shared pytest fixtures for Gitea MCP tests."""
import pytest
from unittest.mock import AsyncMock, MagicMock
from gitea_mcp.auth import AuthConfig
from gitea_mcp.client import GiteaClient
@pytest.fixture
def mock_config(monkeypatch):
"""Create mock authentication config.
This fixture sets up test environment variables and returns
a configured AuthConfig instance for testing.
"""
monkeypatch.setenv("GITEA_API_URL", "http://gitea.example.com/api/v1")
monkeypatch.setenv("GITEA_API_TOKEN", "test_token_123")
return AuthConfig()
@pytest.fixture
def mock_client(mock_config):
"""Create a mock GiteaClient instance.
Returns a GiteaClient with mocked HTTP methods that don't make
real API calls. Use this for testing tool handlers.
"""
client = GiteaClient(mock_config, timeout=10.0)
# Mock the internal HTTP client methods
client.get = AsyncMock()
client.post = AsyncMock()
client.patch = AsyncMock()
client.delete = AsyncMock()
# Mock context manager
client.__aenter__ = AsyncMock(return_value=client)
client.__aexit__ = AsyncMock(return_value=None)
return client
@pytest.fixture
def sample_issue():
"""Sample issue data for testing.
Returns a dict representing a typical Gitea issue response.
"""
return {
"id": 1,
"number": 42,
"title": "Test Issue",
"body": "This is a test issue",
"state": "open",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T12:00:00Z",
"html_url": "http://gitea.example.com/test/repo/issues/42",
"labels": [
{"id": 1, "name": "bug", "color": "ff0000"},
{"id": 2, "name": "priority-high", "color": "ff9900"},
],
"milestone": {
"id": 10,
"title": "v1.0",
"state": "open",
},
"assignees": [
{"id": 100, "login": "testuser"},
],
}
@pytest.fixture
def sample_label():
"""Sample label data for testing.
Returns a dict representing a typical Gitea label response.
"""
return {
"id": 1,
"name": "bug",
"color": "ff0000",
"description": "Something isn't working",
}
@pytest.fixture
def sample_milestone():
"""Sample milestone data for testing.
Returns a dict representing a typical Gitea milestone response.
"""
return {
"id": 10,
"title": "v1.0",
"description": "First major release",
"state": "open",
"open_issues": 5,
"closed_issues": 15,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-15T12:00:00Z",
"due_on": "2024-12-31T23:59:59Z",
}

View File

@@ -1,56 +0,0 @@
"""Tests for authentication module."""
import os
import pytest
from gitea_mcp.auth import AuthConfig
def test_auth_config_success(monkeypatch):
"""Test successful authentication configuration."""
monkeypatch.setenv("GITEA_API_URL", "http://gitea.example.com/api/v1")
monkeypatch.setenv("GITEA_API_TOKEN", "test_token_123")
config = AuthConfig()
assert config.api_url == "http://gitea.example.com/api/v1"
assert config.api_token == "test_token_123"
def test_auth_config_removes_trailing_slash(monkeypatch):
"""Test that trailing slash is removed from URL."""
monkeypatch.setenv("GITEA_API_URL", "http://gitea.example.com/api/v1/")
monkeypatch.setenv("GITEA_API_TOKEN", "test_token_123")
config = AuthConfig()
assert config.api_url == "http://gitea.example.com/api/v1"
def test_auth_config_missing_url(monkeypatch):
"""Test error when GITEA_API_URL is missing."""
monkeypatch.delenv("GITEA_API_URL", raising=False)
monkeypatch.setenv("GITEA_API_TOKEN", "test_token_123")
with pytest.raises(ValueError, match="GITEA_API_URL"):
AuthConfig()
def test_auth_config_missing_token(monkeypatch):
"""Test error when GITEA_API_TOKEN is missing."""
monkeypatch.setenv("GITEA_API_URL", "http://gitea.example.com/api/v1")
monkeypatch.delenv("GITEA_API_TOKEN", raising=False)
with pytest.raises(ValueError, match="GITEA_API_TOKEN"):
AuthConfig()
def test_get_auth_headers(monkeypatch):
"""Test authentication headers generation."""
monkeypatch.setenv("GITEA_API_URL", "http://gitea.example.com/api/v1")
monkeypatch.setenv("GITEA_API_TOKEN", "test_token_123")
config = AuthConfig()
headers = config.get_auth_headers()
assert headers["Authorization"] == "token test_token_123"
assert headers["Content-Type"] == "application/json"

View File

@@ -1,93 +0,0 @@
"""Tests for Gitea API client."""
import pytest
import httpx
from unittest.mock import AsyncMock, MagicMock
from gitea_mcp.auth import AuthConfig
from gitea_mcp.client import (
GiteaClient,
GiteaClientError,
GiteaAuthError,
GiteaNotFoundError,
GiteaServerError,
)
@pytest.fixture
def mock_config(monkeypatch):
"""Create mock authentication config."""
monkeypatch.setenv("GITEA_API_URL", "http://gitea.example.com/api/v1")
monkeypatch.setenv("GITEA_API_TOKEN", "test_token_123")
return AuthConfig()
@pytest.mark.asyncio
async def test_client_initialization(mock_config):
"""Test client initialization."""
client = GiteaClient(mock_config, timeout=10.0)
assert client.config == mock_config
assert client.timeout == 10.0
@pytest.mark.asyncio
async def test_client_context_manager(mock_config):
"""Test client as async context manager."""
async with GiteaClient(mock_config) as client:
assert client._client is not None
assert isinstance(client._client, httpx.AsyncClient)
@pytest.mark.asyncio
async def test_client_without_context_manager_raises_error(mock_config):
"""Test that using client without context manager raises error."""
client = GiteaClient(mock_config)
with pytest.raises(GiteaClientError, match="not initialized"):
await client.get("/test")
@pytest.mark.asyncio
async def test_handle_error_401(mock_config):
"""Test handling 401 authentication error."""
response = MagicMock()
response.status_code = 401
async with GiteaClient(mock_config) as client:
with pytest.raises(GiteaAuthError, match="Authentication failed"):
client._handle_error(response)
@pytest.mark.asyncio
async def test_handle_error_403(mock_config):
"""Test handling 403 forbidden error."""
response = MagicMock()
response.status_code = 403
async with GiteaClient(mock_config) as client:
with pytest.raises(GiteaAuthError, match="Access forbidden"):
client._handle_error(response)
@pytest.mark.asyncio
async def test_handle_error_404(mock_config):
"""Test handling 404 not found error."""
response = MagicMock()
response.status_code = 404
response.request = MagicMock()
response.request.url = "http://gitea.example.com/api/v1/test"
async with GiteaClient(mock_config) as client:
with pytest.raises(GiteaNotFoundError, match="not found"):
client._handle_error(response)
@pytest.mark.asyncio
async def test_handle_error_500(mock_config):
"""Test handling 500 server error."""
response = MagicMock()
response.status_code = 500
response.text = "Internal Server Error"
async with GiteaClient(mock_config) as client:
with pytest.raises(GiteaServerError, match="server error"):
client._handle_error(response)

View File

@@ -1,372 +0,0 @@
"""Tests for issue operations tools."""
import pytest
from mcp.types import Tool, TextContent
from gitea_mcp.tools.issues import (
get_issue_tools,
handle_issue_tool,
_list_issues,
_get_issue,
_create_issue,
_update_issue,
)
from gitea_mcp.client import GiteaClientError
class TestIssueToolDefinitions:
"""Test issue tool schema definitions."""
def test_get_issue_tools_returns_list(self):
"""Test that get_issue_tools returns a list of Tool objects."""
tools = get_issue_tools()
assert isinstance(tools, list)
assert len(tools) == 4
assert all(isinstance(tool, Tool) for tool in tools)
def test_list_issues_tool_schema(self):
"""Test gitea_list_issues tool has correct schema."""
tools = get_issue_tools()
list_tool = next(t for t in tools if t.name == "gitea_list_issues")
assert list_tool.name == "gitea_list_issues"
assert "list issues" in list_tool.description.lower()
schema = list_tool.inputSchema
assert schema["type"] == "object"
assert set(schema["required"]) == {"owner", "repo"}
assert "owner" in schema["properties"]
assert "repo" in schema["properties"]
assert "state" in schema["properties"]
assert "labels" in schema["properties"]
assert "milestone" in schema["properties"]
def test_get_issue_tool_schema(self):
"""Test gitea_get_issue tool has correct schema."""
tools = get_issue_tools()
get_tool = next(t for t in tools if t.name == "gitea_get_issue")
assert get_tool.name == "gitea_get_issue"
assert "get details" in get_tool.description.lower()
schema = get_tool.inputSchema
assert set(schema["required"]) == {"owner", "repo", "index"}
assert "index" in schema["properties"]
def test_create_issue_tool_schema(self):
"""Test gitea_create_issue tool has correct schema."""
tools = get_issue_tools()
create_tool = next(t for t in tools if t.name == "gitea_create_issue")
assert create_tool.name == "gitea_create_issue"
assert "create" in create_tool.description.lower()
schema = create_tool.inputSchema
assert set(schema["required"]) == {"owner", "repo", "title"}
assert "body" in schema["properties"]
assert "labels" in schema["properties"]
assert "assignees" in schema["properties"]
def test_update_issue_tool_schema(self):
"""Test gitea_update_issue tool has correct schema."""
tools = get_issue_tools()
update_tool = next(t for t in tools if t.name == "gitea_update_issue")
assert update_tool.name == "gitea_update_issue"
assert "update" in update_tool.description.lower()
schema = update_tool.inputSchema
assert set(schema["required"]) == {"owner", "repo", "index"}
assert "title" in schema["properties"]
assert "state" in schema["properties"]
class TestListIssues:
"""Test _list_issues function."""
@pytest.mark.asyncio
async def test_list_issues_success(self, mock_client, sample_issue):
"""Test successful issue listing."""
mock_client.get.return_value = [sample_issue]
arguments = {
"owner": "testowner",
"repo": "testrepo",
"state": "open",
}
result = await _list_issues(arguments, mock_client)
assert isinstance(result, list)
assert len(result) == 1
assert isinstance(result[0], TextContent)
assert "Test Issue" in result[0].text
assert "#42" in result[0].text
assert "bug" in result[0].text
mock_client.get.assert_called_once()
call_args = mock_client.get.call_args
assert "/repos/testowner/testrepo/issues" in call_args[0][0]
@pytest.mark.asyncio
async def test_list_issues_empty(self, mock_client):
"""Test listing when no issues found."""
mock_client.get.return_value = []
arguments = {
"owner": "testowner",
"repo": "testrepo",
"state": "closed",
}
result = await _list_issues(arguments, mock_client)
assert len(result) == 1
assert "No closed issues found" in result[0].text
@pytest.mark.asyncio
async def test_list_issues_with_filters(self, mock_client, sample_issue):
"""Test listing with label and milestone filters."""
mock_client.get.return_value = [sample_issue]
arguments = {
"owner": "testowner",
"repo": "testrepo",
"state": "all",
"labels": "bug,priority-high",
"milestone": "v1.0",
"page": 2,
"limit": 50,
}
result = await _list_issues(arguments, mock_client)
mock_client.get.assert_called_once()
call_args = mock_client.get.call_args
params = call_args[1]["params"]
assert params["labels"] == "bug,priority-high"
assert params["milestone"] == "v1.0"
assert params["page"] == 2
assert params["limit"] == 50
class TestGetIssue:
"""Test _get_issue function."""
@pytest.mark.asyncio
async def test_get_issue_success(self, mock_client, sample_issue):
"""Test successful issue retrieval."""
mock_client.get.return_value = sample_issue
arguments = {
"owner": "testowner",
"repo": "testrepo",
"index": 42,
}
result = await _get_issue(arguments, mock_client)
assert len(result) == 1
assert "Issue #42" in result[0].text
assert "Test Issue" in result[0].text
assert "This is a test issue" in result[0].text
assert "bug" in result[0].text
assert "v1.0" in result[0].text
assert "testuser" in result[0].text
mock_client.get.assert_called_once()
assert "/repos/testowner/testrepo/issues/42" in mock_client.get.call_args[0][0]
class TestCreateIssue:
"""Test _create_issue function."""
@pytest.mark.asyncio
async def test_create_issue_minimal(self, mock_client, sample_issue):
"""Test creating issue with minimal required fields."""
mock_client.post.return_value = sample_issue
arguments = {
"owner": "testowner",
"repo": "testrepo",
"title": "New Issue",
}
result = await _create_issue(arguments, mock_client)
assert len(result) == 1
assert "Created issue #42" in result[0].text
assert "Test Issue" in result[0].text
mock_client.post.assert_called_once()
call_args = mock_client.post.call_args
assert "/repos/testowner/testrepo/issues" in call_args[0][0]
assert call_args[1]["json"]["title"] == "New Issue"
@pytest.mark.asyncio
async def test_create_issue_full(self, mock_client, sample_issue):
"""Test creating issue with all optional fields."""
mock_client.post.return_value = sample_issue
arguments = {
"owner": "testowner",
"repo": "testrepo",
"title": "New Issue",
"body": "Detailed description",
"labels": [1, 2],
"milestone": 10,
"assignees": ["user1", "user2"],
}
result = await _create_issue(arguments, mock_client)
assert len(result) == 1
call_args = mock_client.post.call_args
json_data = call_args[1]["json"]
assert json_data["title"] == "New Issue"
assert json_data["body"] == "Detailed description"
assert json_data["labels"] == [1, 2]
assert json_data["milestone"] == 10
assert json_data["assignees"] == ["user1", "user2"]
class TestUpdateIssue:
"""Test _update_issue function."""
@pytest.mark.asyncio
async def test_update_issue_title(self, mock_client, sample_issue):
"""Test updating issue title."""
updated_issue = sample_issue.copy()
updated_issue["title"] = "Updated Title"
mock_client.patch.return_value = updated_issue
arguments = {
"owner": "testowner",
"repo": "testrepo",
"index": 42,
"title": "Updated Title",
}
result = await _update_issue(arguments, mock_client)
assert len(result) == 1
assert "Updated issue #42" in result[0].text
mock_client.patch.assert_called_once()
call_args = mock_client.patch.call_args
assert "/repos/testowner/testrepo/issues/42" in call_args[0][0]
assert call_args[1]["json"]["title"] == "Updated Title"
@pytest.mark.asyncio
async def test_update_issue_state(self, mock_client, sample_issue):
"""Test closing an issue."""
closed_issue = sample_issue.copy()
closed_issue["state"] = "closed"
mock_client.patch.return_value = closed_issue
arguments = {
"owner": "testowner",
"repo": "testrepo",
"index": 42,
"state": "closed",
}
result = await _update_issue(arguments, mock_client)
assert "State: closed" in result[0].text
call_args = mock_client.patch.call_args
assert call_args[1]["json"]["state"] == "closed"
class TestIssueToolHandler:
"""Test handle_issue_tool function."""
@pytest.mark.asyncio
async def test_handle_list_issues(self, mock_client, sample_issue):
"""Test handler routes to _list_issues."""
mock_client.get.return_value = [sample_issue]
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await handle_issue_tool("gitea_list_issues", arguments, mock_client)
assert len(result) == 1
assert isinstance(result[0], TextContent)
mock_client.get.assert_called_once()
@pytest.mark.asyncio
async def test_handle_get_issue(self, mock_client, sample_issue):
"""Test handler routes to _get_issue."""
mock_client.get.return_value = sample_issue
arguments = {
"owner": "testowner",
"repo": "testrepo",
"index": 42,
}
result = await handle_issue_tool("gitea_get_issue", arguments, mock_client)
assert len(result) == 1
assert "Issue #42" in result[0].text
@pytest.mark.asyncio
async def test_handle_create_issue(self, mock_client, sample_issue):
"""Test handler routes to _create_issue."""
mock_client.post.return_value = sample_issue
arguments = {
"owner": "testowner",
"repo": "testrepo",
"title": "New Issue",
}
result = await handle_issue_tool("gitea_create_issue", arguments, mock_client)
assert len(result) == 1
assert "Created issue" in result[0].text
@pytest.mark.asyncio
async def test_handle_update_issue(self, mock_client, sample_issue):
"""Test handler routes to _update_issue."""
mock_client.patch.return_value = sample_issue
arguments = {
"owner": "testowner",
"repo": "testrepo",
"index": 42,
"title": "Updated",
}
result = await handle_issue_tool("gitea_update_issue", arguments, mock_client)
assert len(result) == 1
assert "Updated issue" in result[0].text
@pytest.mark.asyncio
async def test_handle_unknown_tool(self, mock_client):
"""Test handler with unknown tool name."""
result = await handle_issue_tool("gitea_unknown_tool", {}, mock_client)
assert len(result) == 1
assert "Unknown issue tool" in result[0].text
@pytest.mark.asyncio
async def test_handle_client_error(self, mock_client):
"""Test handler gracefully handles GiteaClientError."""
mock_client.get.side_effect = GiteaClientError("API Error")
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await handle_issue_tool("gitea_list_issues", arguments, mock_client)
assert len(result) == 1
assert "Error:" in result[0].text
assert "API Error" in result[0].text

View File

@@ -1,256 +0,0 @@
"""Tests for label operations tools."""
import pytest
from mcp.types import Tool, TextContent
from gitea_mcp.tools.labels import (
get_label_tools,
handle_label_tool,
_list_labels,
_create_label,
)
from gitea_mcp.client import GiteaClientError
class TestLabelToolDefinitions:
"""Test label tool schema definitions."""
def test_get_label_tools_returns_list(self):
"""Test that get_label_tools returns a list of Tool objects."""
tools = get_label_tools()
assert isinstance(tools, list)
assert len(tools) == 2
assert all(isinstance(tool, Tool) for tool in tools)
def test_list_labels_tool_schema(self):
"""Test gitea_list_labels tool has correct schema."""
tools = get_label_tools()
list_tool = next(t for t in tools if t.name == "gitea_list_labels")
assert list_tool.name == "gitea_list_labels"
assert "list" in list_tool.description.lower()
assert "label" in list_tool.description.lower()
schema = list_tool.inputSchema
assert schema["type"] == "object"
assert set(schema["required"]) == {"owner", "repo"}
assert "owner" in schema["properties"]
assert "repo" in schema["properties"]
def test_create_label_tool_schema(self):
"""Test gitea_create_label tool has correct schema."""
tools = get_label_tools()
create_tool = next(t for t in tools if t.name == "gitea_create_label")
assert create_tool.name == "gitea_create_label"
assert "create" in create_tool.description.lower()
schema = create_tool.inputSchema
assert set(schema["required"]) == {"owner", "repo", "name", "color"}
assert "name" in schema["properties"]
assert "color" in schema["properties"]
assert "description" in schema["properties"]
assert "hex" in schema["properties"]["color"]["description"].lower()
class TestListLabels:
"""Test _list_labels function."""
@pytest.mark.asyncio
async def test_list_labels_success(self, mock_client, sample_label):
"""Test successful label listing."""
additional_label = {
"id": 2,
"name": "enhancement",
"color": "00ff00",
"description": "New feature or request",
}
mock_client.get.return_value = [sample_label, additional_label]
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await _list_labels(mock_client, arguments)
assert isinstance(result, list)
assert len(result) == 1
assert isinstance(result[0], TextContent)
assert "bug" in result[0].text
assert "enhancement" in result[0].text
assert "#ff0000" in result[0].text
assert "#00ff00" in result[0].text
assert "Something isn't working" in result[0].text
assert "New feature" in result[0].text
mock_client.get.assert_called_once_with("/repos/testowner/testrepo/labels")
@pytest.mark.asyncio
async def test_list_labels_empty(self, mock_client):
"""Test listing when no labels found."""
mock_client.get.return_value = []
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await _list_labels(mock_client, arguments)
assert len(result) == 1
assert "No labels found" in result[0].text
assert "testowner/testrepo" in result[0].text
@pytest.mark.asyncio
async def test_list_labels_without_description(self, mock_client):
"""Test listing labels without descriptions."""
label_no_desc = {
"id": 1,
"name": "bug",
"color": "ff0000",
}
mock_client.get.return_value = [label_no_desc]
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await _list_labels(mock_client, arguments)
assert len(result) == 1
assert "bug" in result[0].text
assert "#ff0000" in result[0].text
class TestCreateLabel:
"""Test _create_label function."""
@pytest.mark.asyncio
async def test_create_label_minimal(self, mock_client, sample_label):
"""Test creating label with minimal required fields."""
mock_client.post.return_value = sample_label
arguments = {
"owner": "testowner",
"repo": "testrepo",
"name": "bug",
"color": "ff0000",
}
result = await _create_label(mock_client, arguments)
assert len(result) == 1
assert "Created label 'bug'" in result[0].text
assert "#ff0000" in result[0].text
assert "testowner/testrepo" in result[0].text
mock_client.post.assert_called_once()
call_args = mock_client.post.call_args
assert call_args[0][0] == "/repos/testowner/testrepo/labels"
payload = call_args[0][1]
assert payload["name"] == "bug"
assert payload["color"] == "ff0000"
@pytest.mark.asyncio
async def test_create_label_with_description(self, mock_client, sample_label):
"""Test creating label with description."""
mock_client.post.return_value = sample_label
arguments = {
"owner": "testowner",
"repo": "testrepo",
"name": "bug",
"color": "ff0000",
"description": "Something isn't working",
}
result = await _create_label(mock_client, arguments)
assert len(result) == 1
call_args = mock_client.post.call_args
payload = call_args[0][1]
assert payload["description"] == "Something isn't working"
@pytest.mark.asyncio
async def test_create_label_strips_hash(self, mock_client, sample_label):
"""Test creating label with # prefix is handled correctly."""
mock_client.post.return_value = sample_label
arguments = {
"owner": "testowner",
"repo": "testrepo",
"name": "bug",
"color": "#ff0000",
}
result = await _create_label(mock_client, arguments)
call_args = mock_client.post.call_args
payload = call_args[0][1]
assert payload["color"] == "ff0000"
assert not payload["color"].startswith("#")
class TestLabelToolHandler:
"""Test handle_label_tool function."""
@pytest.mark.asyncio
async def test_handle_list_labels(self, mock_client, sample_label):
"""Test handler routes to _list_labels."""
mock_client.get.return_value = [sample_label]
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await handle_label_tool(mock_client, "gitea_list_labels", arguments)
assert len(result) == 1
assert isinstance(result[0], TextContent)
assert "bug" in result[0].text
mock_client.get.assert_called_once()
@pytest.mark.asyncio
async def test_handle_create_label(self, mock_client, sample_label):
"""Test handler routes to _create_label."""
mock_client.post.return_value = sample_label
arguments = {
"owner": "testowner",
"repo": "testrepo",
"name": "bug",
"color": "ff0000",
}
result = await handle_label_tool(mock_client, "gitea_create_label", arguments)
assert len(result) == 1
assert "Created label" in result[0].text
mock_client.post.assert_called_once()
@pytest.mark.asyncio
async def test_handle_unknown_tool(self, mock_client):
"""Test handler with unknown tool name."""
result = await handle_label_tool(mock_client, "gitea_unknown_tool", {})
assert len(result) == 1
assert "Unknown label tool" in result[0].text
@pytest.mark.asyncio
async def test_handle_client_error(self, mock_client):
"""Test handler gracefully handles GiteaClientError."""
mock_client.get.side_effect = GiteaClientError("API Error")
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await handle_label_tool(mock_client, "gitea_list_labels", arguments)
assert len(result) == 1
assert "Gitea API error:" in result[0].text
assert "API Error" in result[0].text

View File

@@ -1,334 +0,0 @@
"""Tests for milestone operations tools."""
import pytest
from mcp.types import Tool, TextContent
from gitea_mcp.tools.milestones import (
get_milestone_tools,
handle_milestone_tool,
_list_milestones,
_create_milestone,
)
from gitea_mcp.client import GiteaClientError
class TestMilestoneToolDefinitions:
"""Test milestone tool schema definitions."""
def test_get_milestone_tools_returns_list(self):
"""Test that get_milestone_tools returns a list of Tool objects."""
tools = get_milestone_tools()
assert isinstance(tools, list)
assert len(tools) == 2
assert all(isinstance(tool, Tool) for tool in tools)
def test_list_milestones_tool_schema(self):
"""Test gitea_list_milestones tool has correct schema."""
tools = get_milestone_tools()
list_tool = next(t for t in tools if t.name == "gitea_list_milestones")
assert list_tool.name == "gitea_list_milestones"
assert "list" in list_tool.description.lower()
assert "milestone" in list_tool.description.lower()
schema = list_tool.inputSchema
assert schema["type"] == "object"
assert set(schema["required"]) == {"owner", "repo"}
assert "owner" in schema["properties"]
assert "repo" in schema["properties"]
assert "state" in schema["properties"]
assert schema["properties"]["state"]["enum"] == ["open", "closed", "all"]
def test_create_milestone_tool_schema(self):
"""Test gitea_create_milestone tool has correct schema."""
tools = get_milestone_tools()
create_tool = next(t for t in tools if t.name == "gitea_create_milestone")
assert create_tool.name == "gitea_create_milestone"
assert "create" in create_tool.description.lower()
schema = create_tool.inputSchema
assert set(schema["required"]) == {"owner", "repo", "title"}
assert "title" in schema["properties"]
assert "description" in schema["properties"]
assert "due_on" in schema["properties"]
assert "iso 8601" in schema["properties"]["due_on"]["description"].lower()
class TestListMilestones:
"""Test _list_milestones function."""
@pytest.mark.asyncio
async def test_list_milestones_success(self, mock_client, sample_milestone):
"""Test successful milestone listing."""
additional_milestone = {
"id": 11,
"title": "v2.0",
"description": "Second major release",
"state": "open",
"open_issues": 10,
"closed_issues": 0,
"created_at": "2024-02-01T00:00:00Z",
"updated_at": "2024-02-01T00:00:00Z",
}
mock_client.get.return_value = [sample_milestone, additional_milestone]
arguments = {
"owner": "testowner",
"repo": "testrepo",
"state": "open",
}
result = await _list_milestones(arguments, mock_client)
assert isinstance(result, list)
assert len(result) == 1
assert isinstance(result[0], TextContent)
assert "v1.0" in result[0].text
assert "v2.0" in result[0].text
assert "First major release" in result[0].text
assert "Open Issues: 5" in result[0].text
assert "Closed Issues: 15" in result[0].text
mock_client.get.assert_called_once()
call_args = mock_client.get.call_args
assert "/repos/testowner/testrepo/milestones" in call_args[0][0]
assert call_args[1]["params"]["state"] == "open"
@pytest.mark.asyncio
async def test_list_milestones_empty(self, mock_client):
"""Test listing when no milestones found."""
mock_client.get.return_value = []
arguments = {
"owner": "testowner",
"repo": "testrepo",
"state": "closed",
}
result = await _list_milestones(arguments, mock_client)
assert len(result) == 1
assert "No closed milestones found" in result[0].text
assert "testowner/testrepo" in result[0].text
@pytest.mark.asyncio
async def test_list_milestones_default_state(self, mock_client, sample_milestone):
"""Test listing milestones with default state."""
mock_client.get.return_value = [sample_milestone]
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await _list_milestones(arguments, mock_client)
call_args = mock_client.get.call_args
assert call_args[1]["params"]["state"] == "open"
@pytest.mark.asyncio
async def test_list_milestones_all_states(self, mock_client, sample_milestone):
"""Test listing milestones with state=all."""
mock_client.get.return_value = [sample_milestone]
arguments = {
"owner": "testowner",
"repo": "testrepo",
"state": "all",
}
result = await _list_milestones(arguments, mock_client)
assert "Found 1 all milestone(s)" in result[0].text
@pytest.mark.asyncio
async def test_list_milestones_with_due_date(self, mock_client, sample_milestone):
"""Test milestone listing includes due date."""
mock_client.get.return_value = [sample_milestone]
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await _list_milestones(arguments, mock_client)
assert "Due: 2024-12-31T23:59:59Z" in result[0].text
@pytest.mark.asyncio
async def test_list_milestones_without_optional_fields(self, mock_client):
"""Test milestone listing without description and due date."""
minimal_milestone = {
"id": 10,
"title": "v1.0",
"state": "open",
"open_issues": 5,
"closed_issues": 15,
"created_at": "2024-01-01T00:00:00Z",
}
mock_client.get.return_value = [minimal_milestone]
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await _list_milestones(arguments, mock_client)
assert "v1.0" in result[0].text
assert "State: open" in result[0].text
assert "Created: 2024-01-01T00:00:00Z" in result[0].text
class TestCreateMilestone:
"""Test _create_milestone function."""
@pytest.mark.asyncio
async def test_create_milestone_minimal(self, mock_client, sample_milestone):
"""Test creating milestone with minimal required fields."""
mock_client.post.return_value = sample_milestone
arguments = {
"owner": "testowner",
"repo": "testrepo",
"title": "v1.0",
}
result = await _create_milestone(arguments, mock_client)
assert len(result) == 1
assert "Created milestone: v1.0" in result[0].text
mock_client.post.assert_called_once()
call_args = mock_client.post.call_args
assert "/repos/testowner/testrepo/milestones" in call_args[0][0]
json_data = call_args[1]["json"]
assert json_data["title"] == "v1.0"
@pytest.mark.asyncio
async def test_create_milestone_with_description(self, mock_client, sample_milestone):
"""Test creating milestone with description."""
mock_client.post.return_value = sample_milestone
arguments = {
"owner": "testowner",
"repo": "testrepo",
"title": "v1.0",
"description": "First major release",
}
result = await _create_milestone(arguments, mock_client)
assert "Description: First major release" in result[0].text
call_args = mock_client.post.call_args
json_data = call_args[1]["json"]
assert json_data["description"] == "First major release"
@pytest.mark.asyncio
async def test_create_milestone_with_due_date(self, mock_client, sample_milestone):
"""Test creating milestone with due date."""
mock_client.post.return_value = sample_milestone
arguments = {
"owner": "testowner",
"repo": "testrepo",
"title": "v1.0",
"due_on": "2024-12-31T23:59:59Z",
}
result = await _create_milestone(arguments, mock_client)
assert "Due: 2024-12-31T23:59:59Z" in result[0].text
call_args = mock_client.post.call_args
json_data = call_args[1]["json"]
assert json_data["due_on"] == "2024-12-31T23:59:59Z"
@pytest.mark.asyncio
async def test_create_milestone_full(self, mock_client, sample_milestone):
"""Test creating milestone with all optional fields."""
mock_client.post.return_value = sample_milestone
arguments = {
"owner": "testowner",
"repo": "testrepo",
"title": "v1.0",
"description": "First major release",
"due_on": "2024-12-31T23:59:59Z",
}
result = await _create_milestone(arguments, mock_client)
assert len(result) == 1
assert "Created milestone: v1.0" in result[0].text
assert "Description: First major release" in result[0].text
assert "Due: 2024-12-31T23:59:59Z" in result[0].text
call_args = mock_client.post.call_args
json_data = call_args[1]["json"]
assert json_data["title"] == "v1.0"
assert json_data["description"] == "First major release"
assert json_data["due_on"] == "2024-12-31T23:59:59Z"
class TestMilestoneToolHandler:
"""Test handle_milestone_tool function."""
@pytest.mark.asyncio
async def test_handle_list_milestones(self, mock_client, sample_milestone):
"""Test handler routes to _list_milestones."""
mock_client.get.return_value = [sample_milestone]
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await handle_milestone_tool("gitea_list_milestones", arguments, mock_client)
assert len(result) == 1
assert isinstance(result[0], TextContent)
assert "v1.0" in result[0].text
mock_client.get.assert_called_once()
@pytest.mark.asyncio
async def test_handle_create_milestone(self, mock_client, sample_milestone):
"""Test handler routes to _create_milestone."""
mock_client.post.return_value = sample_milestone
arguments = {
"owner": "testowner",
"repo": "testrepo",
"title": "v1.0",
}
result = await handle_milestone_tool("gitea_create_milestone", arguments, mock_client)
assert len(result) == 1
assert "Created milestone" in result[0].text
mock_client.post.assert_called_once()
@pytest.mark.asyncio
async def test_handle_unknown_tool(self, mock_client):
"""Test handler with unknown tool name."""
result = await handle_milestone_tool("gitea_unknown_tool", {}, mock_client)
assert len(result) == 1
assert "Unknown milestone tool" in result[0].text
@pytest.mark.asyncio
async def test_handle_client_error(self, mock_client):
"""Test handler gracefully handles GiteaClientError."""
mock_client.get.side_effect = GiteaClientError("API Error")
arguments = {
"owner": "testowner",
"repo": "testrepo",
}
result = await handle_milestone_tool("gitea_list_milestones", arguments, mock_client)
assert len(result) == 1
assert "Error:" in result[0].text
assert "API Error" in result[0].text