"""Pytest configuration and shared fixtures for MCP Office Tools tests. This file provides shared fixtures and configuration for all test modules, following FastMCP testing best practices. """ import pytest import tempfile import os from pathlib import Path from unittest.mock import MagicMock, AsyncMock from typing import Dict, Any from fastmcp import FastMCP # FastMCP testing utilities are created manually from mcp_office_tools.mixins import UniversalMixin, WordMixin, ExcelMixin, PowerPointMixin @pytest.fixture def temp_dir(): """Create a temporary directory for test files.""" with tempfile.TemporaryDirectory() as tmp_dir: yield Path(tmp_dir) @pytest.fixture def mock_csv_content(): """Standard CSV content for testing.""" return "Name,Age,City,Department\nJohn Doe,30,New York,Engineering\nJane Smith,25,Boston,Marketing\nBob Johnson,35,Chicago,Sales" @pytest.fixture def mock_csv_file(temp_dir, mock_csv_content): """Create a temporary CSV file with test content.""" csv_file = temp_dir / "test.csv" csv_file.write_text(mock_csv_content) return str(csv_file) @pytest.fixture def mock_docx_file(temp_dir): """Create a mock DOCX file structure for testing.""" docx_file = temp_dir / "test.docx" # Create a minimal ZIP structure that resembles a DOCX import zipfile with zipfile.ZipFile(docx_file, 'w') as zf: # Minimal document.xml document_xml = ''' Test document content for testing purposes. ''' zf.writestr('word/document.xml', document_xml) # Minimal content types content_types = ''' ''' zf.writestr('[Content_Types].xml', content_types) # Minimal relationships rels = ''' ''' zf.writestr('_rels/.rels', rels) return str(docx_file) @pytest.fixture def fast_mcp_app(): """Create a clean FastMCP app instance for testing.""" return FastMCP("Test MCP Office Tools") @pytest.fixture def universal_mixin(fast_mcp_app): """Create a UniversalMixin instance for testing.""" return UniversalMixin(fast_mcp_app) @pytest.fixture def word_mixin(fast_mcp_app): """Create a WordMixin instance for testing.""" return WordMixin(fast_mcp_app) @pytest.fixture def composed_app(): """Create a fully composed FastMCP app with all mixins.""" app = FastMCP("Composed Test App") # Initialize all mixins UniversalMixin(app) WordMixin(app) ExcelMixin(app) PowerPointMixin(app) return app @pytest.fixture def test_session(composed_app): """Create a test session wrapper for FastMCP app testing.""" # Simple wrapper to test tools directly since FastMCP testing utilities # may not be available in all versions class TestSession: def __init__(self, app): self.app = app async def call_tool(self, tool_name: str, params: dict): """Call a tool directly for testing.""" if tool_name not in self.app._tools: raise ValueError(f"Tool '{tool_name}' not found") tool = self.app._tools[tool_name] return await tool(**params) return TestSession(composed_app) @pytest.fixture def mock_file_validation(): """Standard mock for file validation.""" return { "is_valid": True, "errors": [], "warnings": [], "password_protected": False, "file_size": 1024 } @pytest.fixture def mock_format_detection(): """Standard mock for format detection.""" return { "category": "word", "extension": ".docx", "format_name": "Microsoft Word Document", "mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "is_legacy": False, "structure": { "estimated_complexity": "simple", "has_images": False, "has_tables": False } } @pytest.fixture def mock_text_extraction_result(): """Standard mock for text extraction results.""" return { "text": "This is extracted text content from the document.", "method_used": "python-docx", "methods_tried": ["python-docx"], "character_count": 45, "word_count": 9, "formatted_sections": [ {"type": "paragraph", "text": "This is extracted text content from the document."} ] } @pytest.fixture def mock_document_metadata(): """Standard mock for document metadata.""" return { "title": "Test Document", "author": "Test Author", "created": "2024-01-01T10:00:00Z", "modified": "2024-01-15T14:30:00Z", "subject": "Testing", "keywords": ["test", "document"], "word_count": 150, "page_count": 2, "file_size": 2048 } class MockValidationContext: """Context manager for mocking validation utilities.""" def __init__(self, resolve_path=None, validation_result=None, format_detection=None): self.resolve_path = resolve_path self.validation_result = validation_result or {"is_valid": True, "errors": []} self.format_detection = format_detection or { "category": "word", "extension": ".docx", "format_name": "Word Document" } self.patches = [] def __enter__(self): import mcp_office_tools.utils.validation import mcp_office_tools.utils.file_detection from unittest.mock import patch if self.resolve_path: p1 = patch('mcp_office_tools.utils.validation.resolve_office_file_path', return_value=self.resolve_path) self.patches.append(p1) p1.start() p2 = patch('mcp_office_tools.utils.validation.validate_office_file', return_value=self.validation_result) self.patches.append(p2) p2.start() p3 = patch('mcp_office_tools.utils.file_detection.detect_format', return_value=self.format_detection) self.patches.append(p3) p3.start() return self def __exit__(self, exc_type, exc_val, exc_tb): for patch in self.patches: patch.stop() @pytest.fixture def mock_validation_context(): """Factory for creating MockValidationContext instances.""" return MockValidationContext # FastMCP-specific test markers pytest_plugins = ["pytest_asyncio"] # Configure pytest markers def pytest_configure(config): """Configure custom pytest markers.""" config.addinivalue_line( "markers", "unit: mark test as a unit test" ) config.addinivalue_line( "markers", "integration: mark test as an integration test" ) config.addinivalue_line( "markers", "mixin: mark test as a mixin-specific test" ) config.addinivalue_line( "markers", "tool_functionality: mark test as testing tool functionality" ) config.addinivalue_line( "markers", "error_handling: mark test as testing error handling" ) # Performance configuration for tests @pytest.fixture(autouse=True) def fast_test_execution(): """Configure tests for fast execution.""" # Set shorter timeouts for async operations during testing import asyncio # Store original timeout original_timeout = None # Set test timeout (optional, based on your needs) # You can customize this based on your test requirements yield # Restore original timeout if it was modified if original_timeout is not None: pass # Restore if needed @pytest.fixture def disable_real_file_operations(): """Fixture to ensure no real file operations occur during testing.""" # This fixture can be used to patch file system operations # to prevent accidental file creation/modification during tests pass # Implementation depends on your specific needs