mcp-office-tools/tests/conftest.py
Ryan Malloy 31948d6ffc
Some checks are pending
Test Dashboard / test-and-dashboard (push) Waiting to run
Rename package to mcwaddams
Named for Milton Waddams, who was relocated to the basement with
boxes of legacy documents. He handles the .doc and .xls files from
1997 that nobody else wants to touch.

- Rename package from mcp-office-tools to mcwaddams
- Update author to Ryan Malloy
- Update all imports and references
- Add Office Space themed README narrative
- All 53 tests passing
2026-01-11 11:35:35 -07:00

296 lines
8.9 KiB
Python

"""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 mcwaddams.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 = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>Test document content for testing purposes.</w:t>
</w:r>
</w:p>
</w:body>
</w:document>'''
zf.writestr('word/document.xml', document_xml)
# Minimal content types
content_types = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>'''
zf.writestr('[Content_Types].xml', content_types)
# Minimal relationships
rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>'''
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."""
mixin = UniversalMixin()
mixin.register_all(fast_mcp_app)
return mixin
@pytest.fixture
def word_mixin(fast_mcp_app):
"""Create a WordMixin instance for testing."""
mixin = WordMixin()
mixin.register_all(fast_mcp_app)
return mixin
@pytest.fixture
def composed_app():
"""Create a fully composed FastMCP app with all mixins."""
app = FastMCP("Composed Test App")
# Initialize and register all mixins
UniversalMixin().register_all(app)
WordMixin().register_all(app)
ExcelMixin().register_all(app)
PowerPointMixin().register_all(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._tool_manager._tools:
raise ValueError(f"Tool '{tool_name}' not found")
tool = self.app._tool_manager._tools[tool_name]
return await tool.fn(**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 mcwaddams.utils.validation
import mcwaddams.utils.file_detection
from unittest.mock import patch
if self.resolve_path:
p1 = patch('mcwaddams.utils.validation.resolve_office_file_path',
return_value=self.resolve_path)
self.patches.append(p1)
p1.start()
p2 = patch('mcwaddams.utils.validation.validate_office_file',
return_value=self.validation_result)
self.patches.append(p2)
p2.start()
p3 = patch('mcwaddams.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 and dashboard plugin
pytest_plugins = ["pytest_asyncio", "tests.pytest_dashboard_plugin"]
# 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