"""Tests for MkDocsMCPServer."""
import pytest
from pathlib import Path
from unittest.mock import AsyncMock, patch
from mkdocs_mcp.server import MkDocsMCPServer
class TestMkDocsMCPServer:
"""Test cases for MkDocsMCPServer."""
def test_server_initialization_default_path(self):
"""Test server initialization with default docs path."""
server = MkDocsMCPServer()
# Should set a default path relative to server location
assert server.docs_path is not None
assert isinstance(server.docs_path, Path)
assert server.docs_path.name == "docs"
def test_server_initialization_custom_path(self, temp_docs_dir: Path):
"""Test server initialization with custom docs path."""
server = MkDocsMCPServer(docs_path=temp_docs_dir)
assert server.docs_path == temp_docs_dir
def test_server_has_managers(self, temp_docs_dir: Path):
"""Test that server creates resource and tool managers."""
server = MkDocsMCPServer(docs_path=temp_docs_dir)
assert server.resource_manager is not None
assert server.tool_manager is not None
assert server.resource_manager.docs_path == temp_docs_dir
assert server.tool_manager.docs_path == temp_docs_dir
def test_server_has_mcp_server(self, temp_docs_dir: Path):
"""Test that MCP server is created."""
server = MkDocsMCPServer(docs_path=temp_docs_dir)
assert server.server is not None
assert server.server.name == "mkdocs-mcp"
@pytest.mark.asyncio
async def test_server_handlers_registered(self, temp_docs_dir: Path):
"""Test that MCP handlers are properly registered."""
server = MkDocsMCPServer(docs_path=temp_docs_dir)
# Check that handlers are registered by inspecting the server
# This is a bit of a white-box test, but necessary to verify setup
assert hasattr(server.server, '_list_resources_handler')
assert hasattr(server.server, '_read_resource_handler')
assert hasattr(server.server, '_list_tools_handler')
assert hasattr(server.server, '_call_tool_handler')
@pytest.mark.asyncio
async def test_run_nonexistent_docs(self, empty_docs_dir: Path):
"""Test running server with non-existent docs directory."""
# Create server with path that doesn't exist
nonexistent_path = empty_docs_dir / "nonexistent"
server = MkDocsMCPServer(docs_path=nonexistent_path)
with pytest.raises(FileNotFoundError):
await server.run()
@pytest.mark.asyncio
async def test_run_docs_not_directory(self, temp_docs_dir: Path):
"""Test running server when docs path is a file, not directory."""
# Create a file instead of directory
file_path = temp_docs_dir / "somefile.txt"
file_path.write_text("not a directory")
server = MkDocsMCPServer(docs_path=file_path)
with pytest.raises(NotADirectoryError):
await server.run()
@pytest.mark.asyncio
@patch('mkdocs_mcp.server.stdio_server')
@patch('mkdocs_mcp.server.anyio.run')
async def test_run_success(self, mock_anyio_run, mock_stdio_server, temp_docs_dir: Path):
"""Test successful server run (mocked)."""
# Mock the stdio_server context manager
mock_read_stream = AsyncMock()
mock_write_stream = AsyncMock()
mock_stdio_server.return_value.__aenter__.return_value = (mock_read_stream, mock_write_stream)
# Mock server.run to avoid actual network operations
server = MkDocsMCPServer(docs_path=temp_docs_dir)
with patch.object(server.server, 'run', new_callable=AsyncMock) as mock_server_run:
await server.run()
# Verify that server.run was called
mock_server_run.assert_called_once()
# Check the arguments passed to server.run
args, kwargs = mock_server_run.call_args
assert len(args) >= 3 # read_stream, write_stream, init_options
# Check initialization options
init_options = args[2]
assert init_options.server_name == "mkdocs-mcp"
assert init_options.server_version == "0.1.0"
assert init_options.capabilities is not None
def test_main_function_exists(self):
"""Test that main function exists and is callable."""
from mkdocs_mcp.server import main
assert callable(main)
@patch('mkdocs_mcp.server.anyio.run')
@patch('mkdocs_mcp.server.MkDocsMCPServer')
def test_main_function_integration(self, mock_server_class, mock_anyio_run):
"""Test main function creates server and runs it."""
from mkdocs_mcp.server import main
mock_server_instance = AsyncMock()
mock_server_class.return_value = mock_server_instance
main()
# Verify server was created
mock_server_class.assert_called_once()
# Verify anyio.run was called with server.run
mock_anyio_run.assert_called_once()
args, kwargs = mock_anyio_run.call_args
assert len(args) == 1 # Should be called with server.run
@patch('mkdocs_mcp.server.MkDocsMCPServer')
@patch('mkdocs_mcp.server.anyio.run')
def test_main_keyboard_interrupt(self, mock_anyio_run, mock_server_class):
"""Test main function handles KeyboardInterrupt."""
from mkdocs_mcp.server import main
mock_anyio_run.side_effect = KeyboardInterrupt()
# Should not raise exception
main()
mock_server_class.assert_called_once()
mock_anyio_run.assert_called_once()
@patch('mkdocs_mcp.server.MkDocsMCPServer')
@patch('mkdocs_mcp.server.anyio.run')
@patch('mkdocs_mcp.server.sys.exit')
def test_main_exception_handling(self, mock_exit, mock_anyio_run, mock_server_class):
"""Test main function handles exceptions."""
from mkdocs_mcp.server import main
mock_anyio_run.side_effect = RuntimeError("Test error")
main()
# Should call sys.exit(1) on exception
mock_exit.assert_called_once_with(1)