| """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() |
| |
| 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_project_features = mock_mcp._tools.get("get_project_features") |
|
|
| assert get_project_features is not None, "get_project_features tool not registered" |
|
|
| |
| 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 |
|
|
| |
| 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 = 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_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 |
| |
| 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() |
|
|