"""Test suite for MCP Office Tools server with mixin architecture.""" import pytest import tempfile import os from pathlib import Path from unittest.mock import patch, MagicMock # FastMCP testing - using direct tool access from mcp_office_tools.server import app from mcp_office_tools.utils import OfficeFileError class TestServerInitialization: """Test server initialization and basic functionality.""" def test_app_creation(self): """Test that FastMCP app is created correctly.""" assert app is not None assert hasattr(app, 'get_tools') @pytest.mark.asyncio async def test_all_mixins_tools_registered(self): """Test that all mixin tools are registered correctly.""" # Get all registered tool names tool_names = await app.get_tools() tool_names_set = set(tool_names) # Expected tools from all mixins expected_universal_tools = { "extract_text", "extract_images", "extract_metadata", "detect_office_format", "analyze_document_health", "get_supported_formats" } expected_word_tools = {"convert_to_markdown", "extract_word_tables", "analyze_word_structure"} expected_excel_tools = {"analyze_excel_data", "extract_excel_formulas", "create_excel_chart_data"} # Verify universal tools are registered assert expected_universal_tools.issubset(tool_names_set), f"Missing universal tools: {expected_universal_tools - tool_names_set}" # Verify word tools are registered assert expected_word_tools.issubset(tool_names_set), f"Missing word tools: {expected_word_tools - tool_names_set}" # Verify excel tools are registered assert expected_excel_tools.issubset(tool_names_set), f"Missing excel tools: {expected_excel_tools - tool_names_set}" # Verify minimum number of tools assert len(tool_names) >= 12 # 6 universal + 3 word + 3 excel (+ future PowerPoint tools) def test_mixin_composition_works(self): """Test that mixin composition created the expected server structure.""" # Import the server module to ensure all mixins are initialized import mcp_office_tools.server as server_module # Verify the mixins were created assert hasattr(server_module, 'universal_mixin') assert hasattr(server_module, 'word_mixin') assert hasattr(server_module, 'excel_mixin') assert hasattr(server_module, 'powerpoint_mixin') # Verify mixin instances are correct types from mcp_office_tools.mixins import UniversalMixin, WordMixin, ExcelMixin, PowerPointMixin assert isinstance(server_module.universal_mixin, UniversalMixin) assert isinstance(server_module.word_mixin, WordMixin) assert isinstance(server_module.excel_mixin, ExcelMixin) assert isinstance(server_module.powerpoint_mixin, PowerPointMixin) class TestToolAccess: """Test tool accessibility and metadata.""" @pytest.mark.asyncio async def test_get_tool_metadata(self): """Test getting tool metadata through FastMCP API.""" # Test that we can get tool metadata tool = await app.get_tool("get_supported_formats") assert tool is not None assert tool.name == "get_supported_formats" assert "Get list of all supported Office document formats" in tool.description assert hasattr(tool, 'fn') # Has the actual function @pytest.mark.asyncio async def test_all_expected_tools_accessible(self): """Test that all expected tools are accessible via get_tool.""" expected_tools = [ # Universal tools "extract_text", "extract_images", "extract_metadata", "detect_office_format", "analyze_document_health", "get_supported_formats", # Word tools "convert_to_markdown", "extract_word_tables", "analyze_word_structure", # Excel tools "analyze_excel_data", "extract_excel_formulas", "create_excel_chart_data" ] for tool_name in expected_tools: tool = await app.get_tool(tool_name) assert tool is not None, f"Tool {tool_name} should be accessible" assert tool.name == tool_name assert hasattr(tool, 'fn'), f"Tool {tool_name} should have a function" @pytest.mark.asyncio async def test_tool_function_binding(self): """Test that tools are properly bound to mixin instances.""" # Get a universal tool universal_tool = await app.get_tool("get_supported_formats") assert 'UniversalMixin' in str(type(universal_tool.fn.__self__)) # Get a word tool word_tool = await app.get_tool("convert_to_markdown") assert 'WordMixin' in str(type(word_tool.fn.__self__)) class TestMixinIntegration: """Test integration between different mixins.""" @pytest.mark.asyncio async def test_universal_and_word_tools_coexist(self): """Test that universal and word tools can coexist properly.""" # Verify both universal and word tools are available # This test confirms the mixin composition works correctly # Get tools from both mixins universal_tool = await app.get_tool("get_supported_formats") word_tool = await app.get_tool("convert_to_markdown") # Verify they're bound to different mixin instances assert universal_tool.fn.__self__ != word_tool.fn.__self__ assert 'UniversalMixin' in str(type(universal_tool.fn.__self__)) assert 'WordMixin' in str(type(word_tool.fn.__self__)) @pytest.mark.asyncio async def test_no_tool_name_conflicts(self): """Test that there are no tool name conflicts between mixins.""" tool_names = await app.get_tools() # Verify no duplicates assert len(tool_names) == len(set(tool_names)), "Tool names should be unique" # Verify expected count: 6 universal + 3 word + 3 excel = 12 assert len(tool_names) == 12, f"Expected 12 tools, got {len(tool_names)}: {list(tool_names.keys())}" if __name__ == "__main__": pytest.main([__file__, "-v"])