# MCP Office Tools Testing Strategy This document outlines the comprehensive testing strategy for the mixin-based FastMCP Office Tools server. ## Testing Architecture Overview The testing suite is designed around the mixin architecture pattern and follows FastMCP best practices: ### Test Organization ``` tests/ ├── conftest.py # Shared fixtures and configuration ├── test_server.py # Integration tests for the composed server ├── test_mixins.py # Mixin architecture and composition tests ├── test_universal_mixin.py # Unit tests for UniversalMixin ├── test_word_mixin.py # Unit tests for WordMixin ├── test_excel_mixin.py # Unit tests for ExcelMixin (future) ├── test_powerpoint_mixin.py # Unit tests for PowerPointMixin (future) └── README.md # This file ``` ## Testing Patterns ### 1. Mixin Unit Testing Each mixin is tested independently with comprehensive mocking: ```python @pytest.mark.asyncio @patch('mcp_office_tools.utils.validation.resolve_office_file_path') @patch('mcp_office_tools.utils.validation.validate_office_file') @patch('mcp_office_tools.utils.file_detection.detect_format') async def test_extract_text_success(mock_detect, mock_validate, mock_resolve, mixin): # Setup mocks mock_resolve.return_value = "/test.csv" mock_validate.return_value = {"is_valid": True, "errors": []} mock_detect.return_value = {"category": "data", "extension": ".csv"} # Mock internal methods with patch.object(mixin, '_extract_text_by_category') as mock_extract: mock_extract.return_value = {"text": "test", "method_used": "pandas"} result = await mixin.extract_text("/test.csv") assert "text" in result ``` ### 2. Tool Registration Testing Verify that mixins register tools correctly: ```python def test_tool_registration_count(self): """Test that all expected tools are registered.""" app = FastMCP("Test Office Tools") universal = UniversalMixin(app) assert len(app._tools) == 6 # 6 universal tools word = WordMixin(app) assert len(app._tools) == 7 # 6 universal + 1 word tool ``` ### 3. FastMCP Session Testing Test tools through FastMCP's testing framework: ```python @pytest.mark.asyncio async def test_tool_execution_via_session(self): """Test tool execution through FastMCP test session.""" session = create_test_session(app) result = await session.call_tool("get_supported_formats", {}) assert "supported_extensions" in result ``` ### 4. Error Handling Testing Comprehensive error handling with proper exception types: ```python @pytest.mark.asyncio async def test_extract_text_nonexistent_file(self, mixin): """Test extract_text with nonexistent file raises OfficeFileError.""" with pytest.raises(OfficeFileError): await mixin.extract_text("/nonexistent/file.docx") ``` ## Mocking Strategies ### File Operations Use the `MockValidationContext` for consistent file operation mocking: ```python def test_with_mock_validation(mock_validation_context): with mock_validation_context( resolve_path="/test.docx", validation_result={"is_valid": True, "errors": []}, format_detection={"category": "word", "extension": ".docx"} ): # Test with mocked file operations pass ``` ### Office Document Processing Mock internal processing methods to test tool logic without file dependencies: ```python with patch.object(mixin, '_extract_text_by_category') as mock_extract: mock_extract.return_value = { "text": "extracted text", "method_used": "python-docx", "methods_tried": ["python-docx"] } result = await mixin.extract_text(file_path) ``` ## Test Categories ### Unit Tests (`@pytest.mark.unit`) - Individual mixin functionality - Helper method testing - Parameter validation - Error handling ### Integration Tests (`@pytest.mark.integration`) - Full server composition - Cross-mixin interactions - Tool execution via sessions - End-to-end workflows ### Tool Functionality Tests (`@pytest.mark.tool_functionality`) - Specific tool behavior - Parameter handling - Output validation - Method selection logic ## Running Tests ### All Tests ```bash uv run pytest ``` ### Specific Test Categories ```bash # Unit tests only uv run pytest -m unit # Integration tests only uv run pytest -m integration # Tool functionality tests uv run pytest -m tool_functionality # Specific mixin tests uv run pytest tests/test_universal_mixin.py # With coverage uv run pytest --cov=mcp_office_tools ``` ### Fast Development Cycle ```bash # Skip integration tests for faster feedback uv run pytest -m "not integration" ``` ## Test Fixtures ### Shared Fixtures (conftest.py) - `fast_mcp_app`: Clean FastMCP app instance - `universal_mixin`: UniversalMixin instance - `word_mixin`: WordMixin instance - `composed_app`: Fully composed app with all mixins - `test_session`: FastMCP test session - `temp_dir`: Temporary directory for test files - `mock_csv_file`: Temporary CSV file with test data - `mock_docx_file`: Mock DOCX file structure ### Mock Data Fixtures - `mock_file_validation`: Standard validation response - `mock_format_detection`: Standard format detection response - `mock_text_extraction_result`: Standard text extraction result - `mock_document_metadata`: Standard document metadata ## Best Practices ### 1. Fast Test Execution - Mock all file I/O operations - Use temporary files only when necessary - Keep tests under 1 second unless marked as integration ### 2. Comprehensive Mocking - Mock external dependencies at the boundary - Test internal logic without external dependencies - Use realistic mock data that reflects actual tool behavior ### 3. Clear Test Intent - One behavior per test - Descriptive test names - Clear arrange/act/assert structure ### 4. Error Testing - Test all error conditions - Verify specific exception types - Test error messages for helpfulness ### 5. Tool Functionality Focus - Test tool behavior, not just registration - Verify output structure and content - Test parameter combinations and edge cases ## Advanced Testing Patterns ### Testing Async Tool Methods Directly ```python @pytest.mark.asyncio async def test_tool_method_directly(universal_mixin): """Test tool method directly without session overhead.""" # Direct method testing for unit-level validation with patch('mcp_office_tools.utils.validation.validate_office_file'): result = await universal_mixin.extract_text("/test.csv") assert result is not None ``` ### Testing Tool Parameter Validation ```python @pytest.mark.asyncio async def test_parameter_validation(mixin): """Test tool parameter validation and handling.""" # Test various parameter combinations result = await mixin.extract_text( file_path="/test.csv", preserve_formatting=True, include_metadata=False, method="primary" ) # Verify parameters were used correctly assert result["metadata"]["extraction_method"] != "auto" ``` ### Testing Mixin Composition ```python def test_mixin_composition(self): """Test that mixin composition works correctly.""" app = FastMCP("Test") # Initialize mixins in order universal = UniversalMixin(app) word = WordMixin(app) # Verify no tool conflicts tool_names = set(app._tools.keys()) assert len(tool_names) == 7 # 6 + 1, no duplicates ``` ## Future Enhancements - [ ] Property-based testing for document processing - [ ] Performance benchmarking tests - [ ] Memory usage validation - [ ] Stress testing with large documents - [ ] Network operation testing for URL processing - [ ] Security testing for malicious document handling This testing strategy ensures comprehensive coverage of the mixin-based architecture while maintaining fast test execution and clear test organization.