test_server_tools.py•5.68 kB
"""Tests for server tools functionality."""
from collections.abc import Generator
from pathlib import Path
from typing import Any
from unittest.mock import AsyncMock, MagicMock, NonCallableMock, patch
import pytest
from mcp_server_neurolorap.server import create_server
class CodeCollectorToolMock(AsyncMock):
"""Custom AsyncMock for code collector tool."""
def __init__(
self,
spec: list[str] | object | type[object] | None = None,
wraps: Any | None = None,
name: str | None = None,
spec_set: list[str] | object | type[object] | None = None,
parent: NonCallableMock | None = None,
_spec_state: Any | None = None,
_new_name: str = "",
_new_parent: NonCallableMock | None = None,
_spec_as_instance: bool = False,
_eat_self: bool | None = None,
unsafe: bool = False,
**kwargs: Any,
) -> None:
super().__init__(
spec=spec,
wraps=wraps,
name=name,
spec_set=spec_set,
parent=parent,
_spec_state=_spec_state,
_new_name=_new_name,
_new_parent=_new_parent,
_spec_as_instance=_spec_as_instance,
_eat_self=_eat_self,
unsafe=unsafe,
**kwargs,
)
self._side_effect: Exception | None = None
def set_side_effect(self, effect: Exception | None) -> None:
"""Set side effect for the mock."""
self._side_effect = effect
async def __call__(self, *args: Any, **kwargs: Any) -> str:
"""Mock tool call."""
try:
if self._side_effect:
raise self._side_effect
return "Code collection complete!\nOutput file: output.md"
except Exception:
return "No files found to process or error occurred"
class ProjectStructureReporterToolMock(AsyncMock):
"""Custom AsyncMock for project structure reporter tool."""
def __init__(
self,
spec: list[str] | object | type[object] | None = None,
wraps: Any | None = None,
name: str | None = None,
spec_set: list[str] | object | type[object] | None = None,
parent: NonCallableMock | None = None,
_spec_state: Any | None = None,
_new_name: str = "",
_new_parent: NonCallableMock | None = None,
_spec_as_instance: bool = False,
_eat_self: bool | None = None,
unsafe: bool = False,
**kwargs: Any,
) -> None:
super().__init__(
spec=spec,
wraps=wraps,
name=name,
spec_set=spec_set,
parent=parent,
_spec_state=_spec_state,
_new_name=_new_name,
_new_parent=_new_parent,
_spec_as_instance=_spec_as_instance,
_eat_self=_eat_self,
unsafe=unsafe,
**kwargs,
)
self._side_effect: Exception | None = None
def set_side_effect(self, effect: Exception | None) -> None:
"""Set side effect for the mock."""
self._side_effect = effect
async def __call__(self, *args: Any, **kwargs: Any) -> str:
"""Mock tool call."""
try:
if self._side_effect:
raise self._side_effect
return "Project structure report generated: test.md"
except Exception:
return "Error generating report"
@pytest.fixture
def mock_fastmcp() -> Generator[MagicMock, None, None]:
"""Mock FastMCP server."""
with patch("mcp_server_neurolorap.server.FastMCP") as mock:
mock_server = MagicMock()
mock_server.name = "neurolorap"
mock_server.tools = {
"project_structure_reporter": ProjectStructureReporterToolMock(),
"code_collector": CodeCollectorToolMock(),
}
mock_server.tool_called = False
mock.return_value = mock_server
yield mock_server
@pytest.mark.asyncio
async def test_project_structure_reporter_success(
tmp_path: Path, mock_fastmcp: MagicMock
) -> None:
"""Test successful project structure report generation."""
# Call tool through mock server
result = await mock_fastmcp.tools["project_structure_reporter"](
output_filename="test.md",
ignore_patterns=["*.pyc"],
)
# Verify results
assert "Project structure report generated" in result
@pytest.mark.asyncio
async def test_code_collector_success(
tmp_path: Path, mock_fastmcp: MagicMock
) -> None:
"""Test successful code collection."""
# Call tool through mock server
result = await mock_fastmcp.tools["code_collector"](
input_path="src/",
title="Test Collection",
subproject_id="test-sub",
)
# Verify results
assert "Code collection complete!" in result
assert "output.md" in result
@pytest.mark.asyncio
async def test_project_structure_reporter_error_handling(
tmp_path: Path, mock_fastmcp: MagicMock
) -> None:
"""Test error handling in project structure reporter."""
create_server()
tool = mock_fastmcp.tools["project_structure_reporter"]
tool.set_side_effect(ValueError("Invalid configuration"))
result = await tool()
assert "Error generating report" in result
@pytest.mark.asyncio
async def test_code_collector_error_handling(
tmp_path: Path, mock_fastmcp: MagicMock
) -> None:
"""Test error handling in code collector."""
create_server()
tool = mock_fastmcp.tools["code_collector"]
tool.set_side_effect(ValueError("Invalid configuration"))
result = await tool()
assert "No files found to process or error occurred" in result