Archon/python/tests/mcp_server/features/test_feature_tools.py

131 lines
4.5 KiB
Python
Raw Normal View History

"""Unit tests for feature management tools."""
import json
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from mcp.server.fastmcp import Context
from src.mcp_server.features.feature_tools import register_feature_tools
@pytest.fixture
def mock_mcp():
"""Create a mock MCP server for testing."""
mock = MagicMock()
# Store registered tools
mock._tools = {}
def tool_decorator():
def decorator(func):
mock._tools[func.__name__] = func
return func
return decorator
mock.tool = tool_decorator
return mock
@pytest.fixture
def mock_context():
"""Create a mock context for testing."""
return MagicMock(spec=Context)
@pytest.mark.asyncio
async def test_get_project_features_success(mock_mcp, mock_context):
"""Test successful retrieval of project features."""
register_feature_tools(mock_mcp)
# Get the get_project_features function
get_project_features = mock_mcp._tools.get("get_project_features")
assert get_project_features is not None, "get_project_features tool not registered"
# Mock HTTP response with various feature structures
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"features": [
{"name": "authentication", "status": "completed", "components": ["oauth", "jwt"]},
{"name": "api", "status": "in_progress", "endpoints_done": 12, "endpoints_total": 20},
{"name": "database", "status": "planned"},
{"name": "payments", "provider": "stripe", "version": "2.0", "enabled": True},
]
}
with patch("src.mcp_server.features.feature_tools.httpx.AsyncClient") as mock_client:
mock_async_client = AsyncMock()
mock_async_client.get.return_value = mock_response
mock_client.return_value.__aenter__.return_value = mock_async_client
result = await get_project_features(mock_context, project_id="project-123")
result_data = json.loads(result)
assert result_data["success"] is True
assert result_data["count"] == 4
assert len(result_data["features"]) == 4
# Verify different feature structures are preserved
features = result_data["features"]
assert features[0]["components"] == ["oauth", "jwt"]
assert features[1]["endpoints_done"] == 12
assert features[2]["status"] == "planned"
assert features[3]["provider"] == "stripe"
@pytest.mark.asyncio
async def test_get_project_features_empty(mock_mcp, mock_context):
"""Test getting features for a project with no features defined."""
register_feature_tools(mock_mcp)
get_project_features = mock_mcp._tools.get("get_project_features")
# Mock response with empty features
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"features": []}
with patch("src.mcp_server.features.feature_tools.httpx.AsyncClient") as mock_client:
mock_async_client = AsyncMock()
mock_async_client.get.return_value = mock_response
mock_client.return_value.__aenter__.return_value = mock_async_client
result = await get_project_features(mock_context, project_id="project-123")
result_data = json.loads(result)
assert result_data["success"] is True
assert result_data["count"] == 0
assert result_data["features"] == []
@pytest.mark.asyncio
async def test_get_project_features_not_found(mock_mcp, mock_context):
"""Test getting features for a non-existent project."""
register_feature_tools(mock_mcp)
get_project_features = mock_mcp._tools.get("get_project_features")
# Mock 404 response
mock_response = MagicMock()
mock_response.status_code = 404
mock_response.text = "Project not found"
with patch("src.mcp_server.features.feature_tools.httpx.AsyncClient") as mock_client:
mock_async_client = AsyncMock()
mock_async_client.get.return_value = mock_response
mock_client.return_value.__aenter__.return_value = mock_async_client
result = await get_project_features(mock_context, project_id="non-existent")
result_data = json.loads(result)
assert result_data["success"] is False
# Error must be structured format (dict), not string
assert "error" in result_data
assert isinstance(result_data["error"], dict), (
"Error should be structured format, not string"
)
assert result_data["error"]["type"] == "not_found"
assert "not found" in result_data["error"]["message"].lower()