Research-Stack/5-Applications/nodupe/tests/mime/test_mime_tool.py
2026-05-05 21:15:26 -05:00

720 lines
24 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2025 Allaun
"""Tests for nodupe/tools/mime/mime_tool.py - MIME detection tool integration.
Comprehensive tests covering:
- Tool properties and metadata
- Tool initialization and shutdown
- API method exposure
- Integration with MIMEDetection
- Capabilities reporting
"""
from unittest.mock import MagicMock, patch
import pytest
from nodupe.tools.mime.mime_logic import MIMEDetection
from nodupe.tools.mime.mime_tool import (
StandardMIMETool,
register_tool,
)
# =============================================================================
# Test StandardMIMETool Properties
# =============================================================================
class TestStandardMIMEToolProperties:
"""Test StandardMIMETool properties."""
def test_name_property(self):
"""name property returns correct value."""
tool = StandardMIMETool()
assert tool.name == "standard_mime"
def test_version_property(self):
"""version property returns correct value."""
tool = StandardMIMETool()
assert tool.version == "1.0.0"
def test_dependencies_property(self):
"""dependencies property returns empty list."""
tool = StandardMIMETool()
assert tool.dependencies == []
def test_detector_initialized(self):
"""detector is initialized as MIMEDetection."""
tool = StandardMIMETool()
assert isinstance(tool.detector, MIMEDetection)
# =============================================================================
# Test StandardMIMETool API Methods
# =============================================================================
class TestStandardMIMEToolApiMethods:
"""Test StandardMIMETool api_methods property."""
def test_api_methods_property(self):
"""api_methods returns correct dictionary."""
tool = StandardMIMETool()
api_methods = tool.api_methods
assert isinstance(api_methods, dict)
assert 'detect_mime_type' in api_methods
assert 'is_text' in api_methods
assert 'is_image' in api_methods
assert 'is_archive' in api_methods
def test_api_methods_bound_to_detector(self):
"""api_methods are bound to detector methods."""
tool = StandardMIMETool()
api_methods = tool.api_methods
assert api_methods['detect_mime_type'] == tool.detector.detect_mime_type
assert api_methods['is_text'] == tool.detector.is_text
assert api_methods['is_image'] == tool.detector.is_image
assert api_methods['is_archive'] == tool.detector.is_archive
def test_api_methods_are_callable(self):
"""All api_methods are callable."""
tool = StandardMIMETool()
api_methods = tool.api_methods
for name, method in api_methods.items():
assert callable(method), f"{name} should be callable"
# =============================================================================
# Test StandardMIMETool Initialize
# =============================================================================
class TestStandardMIMEToolInitialize:
"""Test StandardMIMETool.initialize() method."""
def test_initialize_registers_service(self):
"""initialize() registers mime_service."""
tool = StandardMIMETool()
container = MagicMock()
tool.initialize(container)
container.register_service.assert_called_once_with(
'mime_service', tool.detector
)
def test_initialize_with_mock_container(self):
"""initialize() works with mock container."""
tool = StandardMIMETool()
container = MagicMock()
# Should not raise
tool.initialize(container)
assert container.register_service.called
def test_initialize_preserves_detector(self):
"""initialize() preserves the same detector instance."""
tool = StandardMIMETool()
original_detector = tool.detector
container = MagicMock()
tool.initialize(container)
assert tool.detector is original_detector
# =============================================================================
# Test StandardMIMETool Shutdown
# =============================================================================
class TestStandardMIMEToolShutdown:
"""Test StandardMIMETool.shutdown() method."""
def test_shutdown_no_error(self):
"""shutdown() completes without error."""
tool = StandardMIMETool()
# Should not raise
tool.shutdown()
def test_shutdown_multiple_calls(self):
"""shutdown() can be called multiple times safely."""
tool = StandardMIMETool()
# Should not raise on multiple calls
tool.shutdown()
tool.shutdown()
tool.shutdown()
# =============================================================================
# Test StandardMIMETool get_capabilities
# =============================================================================
class TestStandardMIMEToolGetCapabilities:
"""Test StandardMIMETool.get_capabilities() method."""
def test_get_capabilities_returns_dict(self):
"""get_capabilities() returns a dictionary."""
tool = StandardMIMETool()
capabilities = tool.get_capabilities()
assert isinstance(capabilities, dict)
def test_get_capabilities_features(self):
"""get_capabilities() includes features."""
tool = StandardMIMETool()
capabilities = tool.get_capabilities()
assert 'features' in capabilities
features = capabilities['features']
assert isinstance(features, list)
assert 'magic_number_detection' in features
assert 'extension_mapping' in features
assert 'rfc6838_compliance' in features
def test_get_capabilities_features_count(self):
"""get_capabilities() returns expected number of features."""
tool = StandardMIMETool()
capabilities = tool.get_capabilities()
assert len(capabilities['features']) == 3
# =============================================================================
# Test register_tool function
# =============================================================================
class TestRegisterTool:
"""Test register_tool() function."""
def test_register_tool_returns_tool(self):
"""register_tool() returns a StandardMIMETool instance."""
tool = register_tool()
assert isinstance(tool, StandardMIMETool)
def test_register_tool_creates_new_instance(self):
"""register_tool() creates a new instance each call."""
tool1 = register_tool()
tool2 = register_tool()
assert tool1 is not tool2
assert tool1.name == tool2.name
assert tool1.version == tool2.version
# =============================================================================
# Test StandardMIMETool Integration
# =============================================================================
class TestStandardMIMEToolIntegration:
"""Integration tests for StandardMIMETool."""
def test_tool_lifecycle(self):
"""Test complete tool lifecycle: create -> initialize -> use -> shutdown."""
tool = StandardMIMETool()
container = MagicMock()
# Initialize
tool.initialize(container)
container.register_service.assert_called_once()
# Use - check capabilities
capabilities = tool.get_capabilities()
assert 'features' in capabilities
# Use - check API methods
api_methods = tool.api_methods
assert callable(api_methods['detect_mime_type'])
# Shutdown
tool.shutdown()
def test_tool_properties_consistency(self):
"""Test that tool properties return consistent values."""
tool1 = StandardMIMETool()
tool2 = StandardMIMETool()
# Properties should be consistent across instances
assert tool1.name == tool2.name
assert tool1.version == tool2.version
assert tool1.dependencies == tool2.dependencies
def test_detect_mime_type_integration(self, tmp_path):
"""Test MIME type detection through tool."""
tool = StandardMIMETool()
# Create a test file
test_file = tmp_path / "test.txt"
test_file.write_text("test content")
mime_type = tool.detector.detect_mime_type(str(test_file))
assert isinstance(mime_type, str)
assert len(mime_type) > 0
def test_is_text_integration(self, tmp_path):
"""Test is_text check through tool."""
tool = StandardMIMETool()
# Create a text file
test_file = tmp_path / "test.txt"
test_file.write_text("test content")
mime_type = tool.detector.detect_mime_type(str(test_file))
is_text = tool.detector.is_text(mime_type)
assert isinstance(is_text, bool)
def test_is_image_integration(self, tmp_path):
"""Test is_image check through tool."""
tool = StandardMIMETool()
# Create a fake image file with PNG magic bytes
test_file = tmp_path / "test.png"
test_file.write_bytes(b'\x89PNG\r\n\x1a\n' + b'fake image data')
mime_type = tool.detector.detect_mime_type(str(test_file))
is_image = tool.detector.is_image(mime_type)
assert isinstance(is_image, bool)
def test_is_archive_integration(self, tmp_path):
"""Test is_archive check through tool."""
tool = StandardMIMETool()
# Create a fake zip file with ZIP magic bytes
test_file = tmp_path / "test.zip"
test_file.write_bytes(b'PK\x03\x04' + b'fake zip data')
mime_type = tool.detector.detect_mime_type(str(test_file))
is_archive = tool.detector.is_archive(mime_type)
assert isinstance(is_archive, bool)
# =============================================================================
# Test StandardMIMETool Edge Cases
# =============================================================================
class TestStandardMIMEToolEdgeCases:
"""Test StandardMIMETool edge cases."""
def test_api_methods_all_present(self):
"""All expected API methods are present."""
tool = StandardMIMETool()
api_methods = tool.api_methods
expected_methods = ['detect_mime_type', 'is_text', 'is_image', 'is_archive']
for method in expected_methods:
assert method in api_methods, f"{method} should be in api_methods"
def test_detector_independent_of_tool(self):
"""Detector can be used independently of tool."""
tool = StandardMIMETool()
detector = tool.detector
# Detector should work without tool
assert hasattr(detector, 'detect_mime_type')
assert hasattr(detector, 'is_text')
assert hasattr(detector, 'is_image')
assert hasattr(detector, 'is_archive')
def test_tool_without_initialize(self):
"""Tool methods work without calling initialize."""
tool = StandardMIMETool()
# Should be able to get capabilities without initialize
capabilities = tool.get_capabilities()
assert 'features' in capabilities
# Should be able to access api_methods without initialize
api_methods = tool.api_methods
assert len(api_methods) > 0
def test_multiple_tools_independent(self):
"""Multiple tool instances are independent."""
tool1 = StandardMIMETool()
tool2 = StandardMIMETool()
# Each should have its own detector
assert tool1.detector is not tool2.detector
# But they should behave the same
assert tool1.name == tool2.name
assert tool1.version == tool2.version
def test_capabilities_immutability(self):
"""get_capabilities returns independent dict each call."""
tool = StandardMIMETool()
caps1 = tool.get_capabilities()
caps2 = tool.get_capabilities()
# Should be different dict objects
assert caps1 is not caps2
# But have same content
assert caps1 == caps2
def test_detector_type(self):
"""detector is correct type."""
tool = StandardMIMETool()
assert isinstance(tool.detector, MIMEDetection)
def test_tool_inherits_from_base(self):
"""StandardMIMETool inherits from Tool base class."""
from nodupe.core.tool_system.base import Tool
tool = StandardMIMETool()
assert isinstance(tool, Tool)
def test_api_methods_dict_not_shared(self):
"""api_methods returns new dict each call."""
tool = StandardMIMETool()
api1 = tool.api_methods
api2 = tool.api_methods
# Should be different dict objects
assert api1 is not api2
# But have same content
assert api1 == api2
# =============================================================================
# Test StandardMIMETool with Mocked Detector
# =============================================================================
class TestStandardMIMEToolMocked:
"""Test StandardMIMETool with mocked detector."""
def test_api_methods_use_detector(self):
"""API methods delegate to detector."""
tool = StandardMIMETool()
# Verify methods are callable and delegate to detector
assert callable(tool.api_methods['detect_mime_type'])
assert callable(tool.api_methods['is_text'])
assert callable(tool.api_methods['is_image'])
assert callable(tool.api_methods['is_archive'])
# Verify they produce the same results as detector methods
assert tool.api_methods['detect_mime_type']('/test.txt') == tool.detector.detect_mime_type('/test.txt')
assert tool.api_methods['is_text']('text/plain') == tool.detector.is_text('text/plain')
assert tool.api_methods['is_image']('image/jpeg') == tool.detector.is_image('image/jpeg')
assert tool.api_methods['is_archive']('application/zip') == tool.detector.is_archive('application/zip')
def test_initialize_calls_container(self):
"""initialize properly calls container.register_service."""
tool = StandardMIMETool()
container = MagicMock()
tool.initialize(container)
container.register_service.assert_called_once_with(
'mime_service', tool.detector
)
def test_initialize_with_different_containers(self):
"""initialize can be called with different containers."""
tool = StandardMIMETool()
container1 = MagicMock()
container2 = MagicMock()
tool.initialize(container1)
tool.initialize(container2)
assert container1.register_service.called
assert container2.register_service.called
# =============================================================================
# Test StandardMIMETool Functional Tests
# =============================================================================
class TestStandardMIMEToolFunctional:
"""Functional tests for StandardMIMETool."""
def test_detect_common_extensions(self, tmp_path):
"""Test detection of common file extensions."""
tool = StandardMIMETool()
test_cases = [
("test.txt", "text/plain"),
("test.jpg", "image/jpeg"),
("test.png", "image/png"),
("test.pdf", "application/pdf"),
("test.zip", "application/zip"),
]
for filename, expected_mime in test_cases:
test_file = tmp_path / filename
test_file.write_text("test content")
mime_type = tool.detector.detect_mime_type(str(test_file))
# Just verify it returns a string, actual MIME detection may vary
assert isinstance(mime_type, str)
def test_detect_magic_bytes(self, tmp_path):
"""Test detection using magic bytes."""
tool = StandardMIMETool()
# PNG magic bytes
png_file = tmp_path / "test.png"
png_file.write_bytes(b'\x89PNG\r\n\x1a\n' + b'fake png')
mime_type = tool.detector.detect_mime_type(str(png_file))
assert mime_type == 'image/png'
# ZIP magic bytes
zip_file = tmp_path / "test.zip"
zip_file.write_bytes(b'PK\x03\x04' + b'fake zip')
mime_type = tool.detector.detect_mime_type(str(zip_file))
assert mime_type == 'application/zip'
def test_is_text_various_types(self, tmp_path):
"""Test is_text with various MIME types."""
tool = StandardMIMETool()
text_types = [
'text/plain',
'text/html',
'application/json',
'application/xml',
]
for mime_type in text_types:
assert tool.detector.is_text(mime_type) is True
non_text_types = [
'image/png',
'application/zip',
'video/mp4',
]
for mime_type in non_text_types:
assert tool.detector.is_text(mime_type) is False
def test_is_image_various_types(self, tmp_path):
"""Test is_image with various MIME types."""
tool = StandardMIMETool()
image_types = [
'image/png',
'image/jpeg',
'image/gif',
]
for mime_type in image_types:
assert tool.detector.is_image(mime_type) is True
non_image_types = [
'text/plain',
'application/zip',
'video/mp4',
]
for mime_type in non_image_types:
assert tool.detector.is_image(mime_type) is False
def test_is_archive_various_types(self, tmp_path):
"""Test is_archive with various MIME types."""
tool = StandardMIMETool()
archive_types = [
'application/zip',
'application/x-tar',
'application/gzip',
]
for mime_type in archive_types:
assert tool.detector.is_archive(mime_type) is True
non_archive_types = [
'text/plain',
'image/png',
'video/mp4',
]
for mime_type in non_archive_types:
assert tool.detector.is_archive(mime_type) is False
# =============================================================================
# Test StandardMIMETool run_standalone
# =============================================================================
class TestStandardMIMEToolRunStandalone:
"""Test StandardMIMETool.run_standalone() method."""
def test_run_standalone_no_args_shows_help(self, capsys):
"""run_standalone() with no args shows help."""
tool = StandardMIMETool()
with pytest.raises(SystemExit) as exc_info:
tool.run_standalone([])
assert exc_info.value.code == 0
captured = capsys.readouterr()
assert "usage:" in captured.out.lower() or "file" in captured.out.lower()
def test_run_standalone_with_file(self, capsys, tmp_path):
"""run_standalone() with file argument detects MIME type."""
tool = StandardMIMETool()
# Create a test file
test_file = tmp_path / "test.txt"
test_file.write_text("test content")
result = tool.run_standalone([str(test_file)])
assert result == 0
captured = capsys.readouterr()
assert "MIME Type:" in captured.out
def test_run_standalone_with_image_file(self, capsys, tmp_path):
"""run_standalone() with image file detects correctly."""
tool = StandardMIMETool()
# Create a PNG file with magic bytes
test_file = tmp_path / "test.png"
test_file.write_bytes(b'\x89PNG\r\n\x1a\n' + b'fake png')
result = tool.run_standalone([str(test_file)])
assert result == 0
captured = capsys.readouterr()
assert "image/png" in captured.out
def test_run_standalone_with_archive_file(self, capsys, tmp_path):
"""run_standalone() with archive file detects correctly."""
tool = StandardMIMETool()
# Create a ZIP file with magic bytes
test_file = tmp_path / "test.zip"
test_file.write_bytes(b'PK\x03\x04' + b'fake zip')
result = tool.run_standalone([str(test_file)])
assert result == 0
captured = capsys.readouterr()
assert "application/zip" in captured.out
def test_run_standalone_with_nonexistent_file(self, capsys, tmp_path):
"""run_standalone() handles nonexistent file."""
tool = StandardMIMETool()
nonexistent = tmp_path / "nonexistent.txt"
# The MIME detector may return a default type for nonexistent files
# The tool should handle this gracefully
result = tool.run_standalone([str(nonexistent)])
# Result may be 0 (default type returned) or 1 (error)
assert result in [0, 1]
captured = capsys.readouterr()
# Should output something (either MIME type or error)
assert len(captured.out) > 0
def test_run_standalone_with_help_flag(self, capsys):
"""run_standalone() with --help flag."""
tool = StandardMIMETool()
with pytest.raises(SystemExit) as exc_info:
tool.run_standalone(['--help'])
assert exc_info.value.code == 0
captured = capsys.readouterr()
assert "usage" in captured.out.lower()
def test_describe_usage_content(self):
"""describe_usage() returns meaningful content."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert isinstance(description, str)
assert len(description) > 50
assert "file" in description.lower() or "type" in description.lower()
# =============================================================================
# Test StandardMIMETool Missing Coverage - Error Paths
# =============================================================================
class TestStandardMIMEToolMissingCoverage:
"""Test missing coverage paths in StandardMIMETool."""
def test_run_standalone_exception_handling(self, capsys, tmp_path):
"""run_standalone() handles exceptions and returns 1."""
tool = StandardMIMETool()
# Create a test file
test_file = tmp_path / "test.txt"
test_file.write_text("test content")
# Mock detector to raise an exception
with patch.object(tool.detector, 'detect_mime_type', side_effect=Exception("Detection failed")):
result = tool.run_standalone([str(test_file)])
assert result == 1
captured = capsys.readouterr()
assert "Error:" in captured.out
def test_run_standalone_verbose_success(self, capsys, tmp_path):
"""run_standalone() with verbose flag shows detailed output."""
tool = StandardMIMETool()
test_file = tmp_path / "test.txt"
test_file.write_text("test content")
# Mock detector to return a known MIME type
with patch.object(tool.detector, 'detect_mime_type', return_value='text/plain'):
with patch.object(tool.detector, 'is_text', return_value=True):
with patch.object(tool.detector, 'is_image', return_value=False):
with patch.object(tool.detector, 'is_archive', return_value=False):
result = tool.run_standalone([str(test_file), '--verbose'])
assert result == 0
captured = capsys.readouterr()
assert "Is text:" in captured.out
assert "Is image:" in captured.out
assert "Is archive:" in captured.out
def test_run_standalone_with_file_flag(self, capsys, tmp_path):
"""run_standalone() with --file flag."""
tool = StandardMIMETool()
test_file = tmp_path / "test.txt"
test_file.write_text("test content")
with patch.object(tool.detector, 'detect_mime_type', return_value='text/plain'):
result = tool.run_standalone(['--file', str(test_file)])
assert result == 0
captured = capsys.readouterr()
assert "MIME Type:" in captured.out
def test_run_standalone_unknown_flag_no_file(self, capsys):
"""run_standalone() with unknown flag and no file shows help."""
tool = StandardMIMETool()
with pytest.raises(SystemExit) as exc_info:
result = tool.run_standalone(['--unknown-flag'])
assert exc_info.value.code == 0
captured = capsys.readouterr()
assert len(captured.out) > 0
def test_run_standalone_verbose_error_path(self, capsys, tmp_path):
"""run_standalone() error path with verbose output."""
tool = StandardMIMETool()
test_file = tmp_path / "test.txt"
test_file.write_text("test")
# Mock to raise exception during detection
with patch.object(tool.detector, 'detect_mime_type', side_effect=RuntimeError("Runtime error")):
result = tool.run_standalone([str(test_file)])
assert result == 1
captured = capsys.readouterr()
assert "Error:" in captured.out