Ryan Malloy 0748eec48d Fix FastMCP stdio server import
- Use app.run_stdio_async() instead of deprecated stdio_server import
- Aligns with FastMCP 2.11.3 API
- Server now starts correctly with uv run mcp-office-tools
- Maintains all MCPMixin functionality and tool registration
2025-09-26 15:49:00 -06:00
..
2025-09-26 15:49:00 -06:00
2025-09-26 15:49:00 -06:00
2025-09-26 15:49:00 -06:00
2025-09-26 15:49:00 -06:00

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:

@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:

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:

@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:

@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:

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:

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

uv run pytest

Specific Test Categories

# 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

# 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

@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

@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

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.