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

643 lines
24 KiB
Python

# SPDX-License-Identifier: Apache-20
# Copyright (c) 2025 Allaun
"""Comprehensive tests for nodupe/tools/mime/mime_tool.py.
This test file provides comprehensive coverage for the StandardMIMETool class,
focusing on areas not covered by the basic test suite. It includes tests for:
- All run_standalone argument combinations
- Edge cases and error paths
- Detailed content verification
- Integration scenarios
Target: Improve coverage from 68.0% to 90%+
"""
from unittest.mock import MagicMock, patch
import pytest
from nodupe.tools.mime.mime_tool import (
StandardMIMETool,
register_tool,
)
# =============================================================================
# Test run_standalone with Various Flag Combinations
# =============================================================================
class TestRunStandaloneFlagCombinations:
"""Test run_standalone() with all flag combinations."""
def test_run_standalone_short_help_flag(self, capsys):
"""run_standalone() with -h short flag shows help."""
tool = StandardMIMETool()
with pytest.raises(SystemExit) as exc_info:
tool.run_standalone(['-h'])
assert exc_info.value.code == 0
captured = capsys.readouterr()
assert "Standard MIME Detection Tool" in captured.out
def test_run_standalone_short_file_flag(self, capsys, tmp_path):
"""run_standalone() with -f short flag detects MIME type."""
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(['-f', str(test_file)])
assert result == 0
captured = capsys.readouterr()
assert f"File: {test_file}" in captured.out
assert "MIME Type: text/plain" in captured.out
def test_run_standalone_short_verbose_flag(self, capsys, tmp_path):
"""run_standalone() with -v short flag shows verbose output."""
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'):
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), '-v'])
assert result == 0
captured = capsys.readouterr()
assert "Is text: True" in captured.out
assert "Is image: False" in captured.out
assert "Is archive: False" in captured.out
def test_run_standalone_combined_short_flags(self, capsys, tmp_path):
"""run_standalone() with combined short flags."""
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'):
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(['-f', str(test_file), '-v'])
assert result == 0
captured = capsys.readouterr()
assert "MIME Type: text/plain" in captured.out
assert "Is text:" in captured.out
def test_run_standalone_long_and_short_verbose_together(self, capsys, tmp_path):
"""run_standalone() with both --verbose and -v flags."""
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'):
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', '-v'])
assert result == 0
captured = capsys.readouterr()
# Should still show verbose output (flags are just checked for presence)
assert "Is text:" in captured.out
def test_run_standalone_help_in_middle_of_args(self, capsys):
"""run_standalone() with --help in middle of args shows help."""
tool = StandardMIMETool()
with pytest.raises(SystemExit) as exc_info:
tool.run_standalone(['--file', 'test.txt', '--help'])
assert exc_info.value.code == 0
captured = capsys.readouterr()
assert "Standard MIME Detection Tool" in captured.out
def test_run_standalone_file_flag_takes_precedence(self, capsys, tmp_path):
"""run_standalone() --file flag takes precedence over positional."""
tool = StandardMIMETool()
file1 = tmp_path / "file1.txt"
file2 = tmp_path / "file2.txt"
file1.write_text("content 1")
file2.write_text("content 2")
# Mock to track which file is detected
detected_files = []
def track_detection(path):
detected_files.append(path)
return 'text/plain'
with patch.object(tool.detector, 'detect_mime_type', side_effect=track_detection):
result = tool.run_standalone(['--file', str(file1), str(file2)])
assert result == 0
assert len(detected_files) == 1
assert str(file1) in detected_files[0]
captured = capsys.readouterr()
assert f"File: {file1}" in captured.out
def test_run_standalone_file_flag_no_path_following(self, capsys):
"""run_standalone() --file with no path following shows help."""
tool = StandardMIMETool()
with pytest.raises(SystemExit) as exc_info:
tool.run_standalone(['--file'])
assert exc_info.value.code == 0
captured = capsys.readouterr()
assert "Standard MIME Detection Tool" in captured.out
def test_run_standalone_short_file_flag_no_path_following(self, capsys):
"""run_standalone() -f with no path following shows help."""
tool = StandardMIMETool()
with pytest.raises(SystemExit) as exc_info:
tool.run_standalone(['-f'])
assert exc_info.value.code == 0
captured = capsys.readouterr()
assert "Standard MIME Detection Tool" in captured.out
# =============================================================================
# Test run_standalone Error Handling and Edge Cases
# =============================================================================
class TestRunStandaloneErrorHandling:
"""Test run_standalone() error handling paths."""
def test_run_standalone_nonexistent_file_error(self, capsys, tmp_path):
"""run_standalone() with nonexistent file handles gracefully."""
tool = StandardMIMETool()
nonexistent = tmp_path / "does_not_exist.txt"
# Mock detector to raise error for nonexistent file
with patch.object(tool.detector, 'detect_mime_type', side_effect=FileNotFoundError("File not found")):
result = tool.run_standalone([str(nonexistent)])
assert result == 1
captured = capsys.readouterr()
assert "Error:" in captured.out
def test_run_standalone_permission_error(self, capsys, tmp_path):
"""run_standalone() handles permission errors."""
tool = StandardMIMETool()
test_file = tmp_path / "test.txt"
test_file.write_text("test")
with patch.object(tool.detector, 'detect_mime_type', side_effect=PermissionError("Permission denied")):
result = tool.run_standalone([str(test_file)])
assert result == 1
captured = capsys.readouterr()
assert "Error:" in captured.out
def test_run_standalone_os_error(self, capsys, tmp_path):
"""run_standalone() handles OS errors."""
tool = StandardMIMETool()
test_file = tmp_path / "test.txt"
test_file.write_text("test")
with patch.object(tool.detector, 'detect_mime_type', side_effect=OSError("OS error")):
result = tool.run_standalone([str(test_file)])
assert result == 1
captured = capsys.readouterr()
assert "Error:" in captured.out
def test_run_standalone_value_error(self, capsys, tmp_path):
"""run_standalone() handles value errors."""
tool = StandardMIMETool()
test_file = tmp_path / "test.txt"
test_file.write_text("test")
with patch.object(tool.detector, 'detect_mime_type', side_effect=ValueError("Invalid value")):
result = tool.run_standalone([str(test_file)])
assert result == 1
captured = capsys.readouterr()
assert "Error:" in captured.out
def test_run_standalone_with_directory_instead_of_file(self, capsys, tmp_path):
"""run_standalone() with directory path handles gracefully."""
tool = StandardMIMETool()
test_dir = tmp_path / "test_dir"
test_dir.mkdir()
# The detector may handle directories differently
result = tool.run_standalone([str(test_dir)])
# Should not crash - either returns 0 with default type or 1 with error
assert result in [0, 1]
captured = capsys.readouterr()
assert len(captured.out) > 0
def test_run_standalone_with_empty_file(self, capsys, tmp_path):
"""run_standalone() with empty file."""
tool = StandardMIMETool()
test_file = tmp_path / "empty.txt"
test_file.write_text("")
result = tool.run_standalone([str(test_file)])
# Should handle empty file gracefully
assert result in [0, 1]
captured = capsys.readouterr()
# Should output something
assert len(captured.out) > 0
def test_run_standalone_with_binary_file(self, capsys, tmp_path):
"""run_standalone() with binary file."""
tool = StandardMIMETool()
test_file = tmp_path / "binary.bin"
test_file.write_bytes(b'\x00\x01\x02\x03\x04\x05')
result = tool.run_standalone([str(test_file)])
# Should handle binary file
assert result == 0
captured = capsys.readouterr()
assert "MIME Type:" in captured.out
def test_run_standalone_with_file_path_spaces(self, capsys, tmp_path):
"""run_standalone() with file path containing spaces."""
tool = StandardMIMETool()
test_file = tmp_path / "file with spaces.txt"
test_file.write_text("test content")
with patch.object(tool.detector, 'detect_mime_type', return_value='text/plain'):
result = tool.run_standalone([str(test_file)])
assert result == 0
captured = capsys.readouterr()
assert f"File: {test_file}" in captured.out
# =============================================================================
# Test describe_usage Method Content
# =============================================================================
class TestDescribeUsageContent:
"""Test describe_usage() method content verification."""
def test_describe_usage_contains_title(self):
"""describe_usage() contains tool title."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert "Standard MIME Detection Tool" in description
def test_describe_usage_contains_file_option(self):
"""describe_usage() contains --file option."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert "--file" in description
def test_describe_usage_contains_verbose_option(self):
"""describe_usage() contains --verbose option."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert "--verbose" in description
def test_describe_usage_contains_examples_section(self):
"""describe_usage() contains examples section."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert "Examples:" in description or "example" in description.lower()
def test_describe_usage_contains_features_section(self):
"""describe_usage() contains features section."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert "Features:" in description or "features" in description.lower()
def test_describe_usage_contains_magic_number_reference(self):
"""describe_usage() mentions magic number detection."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert "magic" in description.lower()
def test_describe_usage_contains_extension_reference(self):
"""describe_usage() mentions extension-based detection."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert "extension" in description.lower()
def test_describe_usage_contains_rfc_reference(self):
"""describe_usage() mentions RFC 6838 compliance."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert "RFC" in description or "rfc" in description.lower()
def test_describe_usage_multiline_content(self):
"""describe_usage() returns multiline content."""
tool = StandardMIMETool()
description = tool.describe_usage()
lines = description.strip().split('\n')
assert len(lines) > 5
def test_describe_usage_contains_path_argument_example(self):
"""describe_usage() contains PATH argument example."""
tool = StandardMIMETool()
description = tool.describe_usage()
assert "PATH" in description or "path" in description.lower()
# =============================================================================
# Test get_capabilities Detailed Content
# =============================================================================
class TestGetCapabilitiesDetailed:
"""Test get_capabilities() detailed content verification."""
def test_get_capabilities_returns_features_list(self):
"""get_capabilities() returns features as a list."""
tool = StandardMIMETool()
capabilities = tool.get_capabilities()
assert isinstance(capabilities['features'], list)
def test_get_capabilities_magic_number_detection_feature(self):
"""get_capabilities() includes magic_number_detection feature."""
tool = StandardMIMETool()
capabilities = tool.get_capabilities()
assert 'magic_number_detection' in capabilities['features']
def test_get_capabilities_extension_mapping_feature(self):
"""get_capabilities() includes extension_mapping feature."""
tool = StandardMIMETool()
capabilities = tool.get_capabilities()
assert 'extension_mapping' in capabilities['features']
def test_get_capabilities_rfc6838_compliance_feature(self):
"""get_capabilities() includes rfc6838_compliance feature."""
tool = StandardMIMETool()
capabilities = tool.get_capabilities()
assert 'rfc6838_compliance' in capabilities['features']
def test_get_capabilities_no_extra_keys(self):
"""get_capabilities() returns only expected keys."""
tool = StandardMIMETool()
capabilities = tool.get_capabilities()
# Should only have 'features' key
assert set(capabilities.keys()) == {'features'}
# =============================================================================
# Test register_tool Function
# =============================================================================
class TestRegisterToolDetailed:
"""Test register_tool() function detailed behavior."""
def test_register_tool_returns_fresh_instance(self):
"""register_tool() returns a fresh tool instance."""
tool = register_tool()
assert isinstance(tool, StandardMIMETool)
assert tool.name == "standard_mime"
assert tool.version == "1.0.0"
def test_register_tool_detector_is_fresh(self):
"""register_tool() creates tool with fresh detector."""
tool1 = register_tool()
tool2 = register_tool()
# Each tool should have its own detector
assert tool1.detector is not tool2.detector
def test_register_tool_capabilities_after_registration(self):
"""register_tool() returns tool with working capabilities."""
tool = register_tool()
capabilities = tool.get_capabilities()
assert 'features' in capabilities
assert len(capabilities['features']) > 0
def test_register_tool_api_methods_accessible(self):
"""register_tool() returns tool with accessible API methods."""
tool = register_tool()
api_methods = tool.api_methods
assert 'detect_mime_type' in api_methods
assert callable(api_methods['detect_mime_type'])
# =============================================================================
# Test Integration Scenarios
# =============================================================================
class TestIntegrationScenarios:
"""Test integration scenarios for StandardMIMETool."""
def test_tool_full_lifecycle_with_container(self):
"""Test full tool lifecycle with container integration."""
tool = StandardMIMETool()
container = MagicMock()
# Initialize
tool.initialize(container)
container.register_service.assert_called_once_with(
'mime_service', tool.detector
)
# Use capabilities
capabilities = tool.get_capabilities()
assert 'features' in capabilities
# Use API methods
api_methods = tool.api_methods
assert len(api_methods) == 4
# Shutdown
tool.shutdown()
# Verify container was only called once during initialize
assert container.register_service.call_count == 1
def test_tool_reinitialize_with_same_container(self):
"""Test reinitializing tool with same container."""
tool = StandardMIMETool()
container = MagicMock()
tool.initialize(container)
tool.initialize(container)
# Should register service twice
assert container.register_service.call_count == 2
def test_tool_api_methods_delegate_correctly(self, tmp_path):
"""Test that API methods correctly delegate to detector."""
tool = StandardMIMETool()
test_file = tmp_path / "test.png"
test_file.write_bytes(b'\x89PNG\r\n\x1a\n' + b'fake png')
# Test through API methods
mime_type = tool.api_methods['detect_mime_type'](str(test_file))
assert mime_type == 'image/png'
is_image = tool.api_methods['is_image'](mime_type)
assert is_image is True
is_text = tool.api_methods['is_text'](mime_type)
assert is_text is False
is_archive = tool.api_methods['is_archive'](mime_type)
assert is_archive is False
def test_run_standalone_verbose_with_image_file(self, capsys, tmp_path):
"""run_standalone() verbose output with image file."""
tool = StandardMIMETool()
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), '--verbose'])
assert result == 0
captured = capsys.readouterr()
assert "image/png" in captured.out
assert "Is text:" in captured.out
assert "Is image:" in captured.out
assert "Is archive:" in captured.out
def test_run_standalone_verbose_with_archive_file(self, capsys, tmp_path):
"""run_standalone() verbose output with archive file."""
tool = StandardMIMETool()
test_file = tmp_path / "test.zip"
test_file.write_bytes(b'PK\x03\x04' + b'fake zip data')
result = tool.run_standalone([str(test_file), '--verbose'])
assert result == 0
captured = capsys.readouterr()
assert "application/zip" in captured.out
assert "Is archive:" in captured.out
# =============================================================================
# Test Edge Cases for Complete Coverage
# =============================================================================
class TestEdgeCasesCompleteCoverage:
"""Test edge cases for complete code coverage."""
def test_run_standalone_multiple_positional_args_uses_first(self, capsys, tmp_path):
"""run_standalone() with multiple positional args uses first."""
tool = StandardMIMETool()
file1 = tmp_path / "first.txt"
file2 = tmp_path / "second.txt"
file1.write_text("first")
file2.write_text("second")
detected_files = []
def track_detection(path):
detected_files.append(path)
return 'text/plain'
with patch.object(tool.detector, 'detect_mime_type', side_effect=track_detection):
result = tool.run_standalone([str(file1), str(file2)])
assert result == 0
assert len(detected_files) == 1
captured = capsys.readouterr()
assert f"File: {file1}" in captured.out
def test_run_standalone_unknown_flags_with_file(self, capsys, tmp_path):
"""run_standalone() with unknown flags still processes file."""
tool = StandardMIMETool()
test_file = tmp_path / "test.txt"
test_file.write_text("test")
with patch.object(tool.detector, 'detect_mime_type', return_value='text/plain'):
result = tool.run_standalone(['--unknown', str(test_file), '--another-unknown'])
assert result == 0
captured = capsys.readouterr()
assert "MIME Type:" in captured.out
def test_run_standalone_verbose_flag_before_file(self, capsys, tmp_path):
"""run_standalone() with --verbose before file argument."""
tool = StandardMIMETool()
test_file = tmp_path / "test.txt"
test_file.write_text("test")
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(['--verbose', str(test_file)])
assert result == 0
captured = capsys.readouterr()
assert "Is text:" in captured.out
def test_run_standalone_all_flags_combined(self, capsys, tmp_path):
"""run_standalone() with all flags combined."""
tool = StandardMIMETool()
test_file = tmp_path / "test.txt"
test_file.write_text("test")
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(['--verbose', '-v', '--file', str(test_file)])
assert result == 0
captured = capsys.readouterr()
assert "MIME Type:" in captured.out
assert "Is text:" in captured.out
def test_detector_methods_with_none_mime_type(self):
"""Test detector methods handle edge case MIME types."""
tool = StandardMIMETool()
# Test with empty string
assert tool.detector.is_text('') is False
assert tool.detector.is_image('') is False
assert tool.detector.is_archive('') is False
# Test with None-like values
assert tool.detector.is_text('application/octet-stream') is False
def test_tool_properties_are_read_only(self):
"""Test that tool properties cannot be modified."""
tool = StandardMIMETool()
# Properties should be read-only (property decorators)
# Just verify they return consistent values
name1 = tool.name
name2 = tool.name
assert name1 == name2
version1 = tool.version
version2 = tool.version
assert version1 == version2
def test_tool_string_representation(self):
"""Test tool has meaningful string representation."""
tool = StandardMIMETool()
# Should have standard object representation
assert str(tool) is not None
assert repr(tool) is not None