generated from personal-projects/leo-claude-mktplace
Implemented MCP server core infrastructure with authentication and HTTP client: - Created auth.py for API token management - Loads GITEA_API_URL and GITEA_API_TOKEN from environment - Uses python-dotenv for .env file support - Validates required configuration on initialization - Provides authentication headers for API requests - Created client.py with base HTTP client - GiteaClient class using httpx AsyncClient - Async HTTP methods: get(), post(), patch(), delete() - Comprehensive error handling for HTTP status codes - Custom exception hierarchy for different error types - Configurable timeout (default 30s) - Updated server.py with MCP server setup - Initialized MCP server with StdioServerTransport - Integrated AuthConfig and GiteaClient - Registered placeholder tool handlers (list_repositories, create_issue, create_pull_request) - Added CLI with --help and --version options - Proper error handling for configuration failures - Updated pyproject.toml - Added console script entry point: gitea-mcp - Created comprehensive unit tests - test_auth.py: Tests for AuthConfig validation and headers - test_client.py: Tests for GiteaClient initialization and error handling All acceptance criteria met: - MCP server initializes with StdioServerTransport - Authentication loads from environment variables - Base HTTP client with auth headers implemented - Error handling for API connection failures - Server reports available tools (placeholders for future issues) Closes #2 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
94 lines
2.8 KiB
Python
94 lines
2.8 KiB
Python
"""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)
|