"""Comprehensive tests for docx-mcp server."""
import pytest
import tempfile
from pathlib import Path
from PIL import Image
from docx import Document
# ============================================================================
# FIXTURES
# ============================================================================
@pytest.fixture
def temp_doc():
"""Create a temporary document for testing."""
with tempfile.TemporaryDirectory() as tmpdir:
filepath = str(Path(tmpdir) / "test.docx")
# Directly create using python-docx instead of MCP tool
doc = Document()
doc.add_paragraph("Line 1")
doc.add_paragraph("Line 2")
doc.add_paragraph("Line 3")
doc.add_paragraph("Line 4")
doc.add_paragraph("Line 5")
doc.save(filepath)
yield filepath
@pytest.fixture
def temp_image():
"""Create a temporary test image."""
with tempfile.TemporaryDirectory() as tmpdir:
img = Image.new('RGB', (100, 100), color='red')
filepath = str(Path(tmpdir) / "test_image.png")
img.save(filepath)
yield filepath
@pytest.fixture
def temp_dir():
"""Create a temporary directory."""
with tempfile.TemporaryDirectory() as tmpdir:
yield tmpdir
# ============================================================================
# IMPORT TESTS
# ============================================================================
def test_server_imports():
"""Test that server can be imported."""
from docx_mcp.server import app
assert app is not None
assert app.name == "docx-mcp"
def test_config_imports():
"""Test that config can be imported."""
from docx_mcp.config import config, DocxMcpConfig
assert config is not None
assert isinstance(config, DocxMcpConfig)
def test_exceptions_imports():
"""Test that exceptions can be imported."""
from docx_mcp.exceptions import (
DocxMcpError,
FileNotFoundError,
InvalidPathError,
DocumentError,
)
assert DocxMcpError is not None
assert FileNotFoundError is not None
assert InvalidPathError is not None
assert DocumentError is not None
def test_utils_imports():
"""Test that utils can be imported."""
from docx_mcp.utils import (
normalize_path,
validate_file_path,
safe_open_document,
get_document_info,
)
assert normalize_path is not None
assert validate_file_path is not None
assert safe_open_document is not None
assert get_document_info is not None
def test_logging_imports():
"""Test that logging can be imported."""
from docx_mcp.logging_config import setup_logging, get_logger
assert setup_logging is not None
assert get_logger is not None
logger = get_logger(__name__)
assert logger is not None
# ============================================================================
# UTILITY FUNCTION TESTS
# ============================================================================
def test_normalize_path():
"""Test path normalization."""
from docx_mcp.utils import normalize_path
from pathlib import Path
# Use project directory path which is always allowed
project_path = Path.cwd() / "test_document.docx"
normalized = normalize_path(str(project_path))
assert isinstance(normalized, Path)
def test_normalize_path_relative():
"""Test normalizing relative paths."""
from docx_mcp.utils import normalize_path
normalized = normalize_path("./document.docx")
assert isinstance(normalized, Path)
assert normalized.name == "document.docx"
def test_validate_file_path_project_dir():
"""Test file path validation in project directory."""
from docx_mcp.utils import validate_file_path
from pathlib import Path
# Create file in project directory to pass path validation
project_dir = Path.cwd()
doc_path = project_dir / ".test_doc_validate.docx"
doc_path.touch()
try:
# Should not raise for valid path in project dir
validate_file_path(str(doc_path))
finally:
doc_path.unlink(missing_ok=True)
def test_validate_file_path_nonexistent_file():
"""Test validation fails for non-existent file."""
from docx_mcp.utils import validate_file_path
from docx_mcp.exceptions import FileNotFoundError
from pathlib import Path
# Non-existent file in project dir
nonexistent = Path.cwd() / ".nonexistent_doc_12345.docx"
with pytest.raises(FileNotFoundError):
validate_file_path(str(nonexistent))
def test_extract_text_from_document():
"""Test extracting text from document."""
from docx_mcp.utils import extract_all_text
from pathlib import Path
# Create in project directory
filepath = Path.cwd() / ".test_extract_text.docx"
try:
doc = Document()
doc.add_paragraph("Line 1")
doc.add_paragraph("Line 2")
doc.add_paragraph("Line 5")
doc.save(str(filepath))
text = extract_all_text(str(filepath))
assert "Line 1" in text
assert "Line 2" in text
assert "Line 5" in text
finally:
filepath.unlink(missing_ok=True)
def test_get_document_info():
"""Test getting document information."""
from docx_mcp.utils import get_document_info
from pathlib import Path
filepath = Path.cwd() / ".test_get_info.docx"
try:
doc = Document()
for i in range(1, 6):
doc.add_paragraph(f"Line {i}")
doc.save(str(filepath))
info = get_document_info(str(filepath))
assert "paragraphs" in info
assert "properties" in info
assert info["paragraphs"] == 5
assert info["tables"] == 0
finally:
filepath.unlink(missing_ok=True)
def test_safe_open_document():
"""Test safe document opening."""
from docx_mcp.utils import safe_open_document
from docx import Document
from pathlib import Path
filepath = Path.cwd() / ".test_safe_open.docx"
try:
doc = Document()
for i in range(1, 6):
doc.add_paragraph(f"Line {i}")
doc.save(str(filepath))
opened_doc = safe_open_document(str(filepath))
# Verify it's a Document object by checking attributes
assert hasattr(opened_doc, "paragraphs")
assert len(opened_doc.paragraphs) == 5
assert hasattr(opened_doc, "save")
finally:
filepath.unlink(missing_ok=True)
def test_safe_open_document_nonexistent():
"""Test safe opening of non-existent document."""
from docx_mcp.utils import safe_open_document
from docx_mcp.exceptions import DocumentError
from pathlib import Path
nonexistent = Path.cwd() / ".nonexistent_doc_safe_open_12345.docx"
with pytest.raises(DocumentError):
safe_open_document(str(nonexistent))
# ============================================================================
# DOCUMENT MANIPULATION TESTS
# ============================================================================
def test_document_creation():
"""Test document creation and manipulation."""
with tempfile.TemporaryDirectory() as tmpdir:
filepath = str(Path(tmpdir) / "test.docx")
doc = Document()
doc.add_paragraph("Test paragraph")
doc.save(filepath)
assert Path(filepath).exists()
# Verify by loading
loaded = Document(filepath)
assert len(loaded.paragraphs) == 1
assert "Test paragraph" in loaded.paragraphs[0].text
def test_document_paragraph_operations(temp_doc):
"""Test paragraph operations."""
doc = Document(temp_doc)
# Should have 5 paragraphs from fixture
assert len(doc.paragraphs) == 5
# Add more paragraphs
doc.add_paragraph("New paragraph")
doc.save(temp_doc)
# Reload and verify
loaded = Document(temp_doc)
assert len(loaded.paragraphs) == 6
def test_document_style_application(temp_doc):
"""Test applying styles to paragraphs."""
doc = Document(temp_doc)
if len(doc.styles):
paragraph = doc.paragraphs[0]
# Try to apply a standard style
paragraph.style = 'Heading 1'
doc.save(temp_doc)
loaded = Document(temp_doc)
assert loaded.paragraphs[0].style.name == 'Heading 1'
def test_document_copy(temp_doc):
"""Test copying a document."""
with tempfile.TemporaryDirectory() as tmpdir:
dest_path = str(Path(tmpdir) / "copy.docx")
# Copy using shutil
import shutil
shutil.copy(temp_doc, dest_path)
assert Path(dest_path).exists()
# Verify copy has same content
original = Document(temp_doc)
copied = Document(dest_path)
assert len(original.paragraphs) == len(copied.paragraphs)
def test_document_properties_manipulation():
"""Test document property manipulation."""
with tempfile.TemporaryDirectory() as tmpdir:
filepath = str(Path(tmpdir) / "test.docx")
doc = Document()
doc.core_properties.title = "Test Title"
doc.core_properties.author = "Test Author"
doc.core_properties.subject = "Test Subject"
doc.save(filepath)
loaded = Document(filepath)
assert loaded.core_properties.title == "Test Title"
assert loaded.core_properties.author == "Test Author"
if __name__ == "__main__":
pytest.main([__file__, "-v"])