Add comprehensive tests for formatter classes, services, and utilities

- Introduced tests for various formatter classes including TextFormatter, DurationFormatter, SizeFormatter, DateFormatter, and more to ensure correct formatting behavior.
- Added tests for service classes such as FileTreeService, MetadataService, and RenameService, covering directory validation, metadata extraction, and file renaming functionalities.
- Implemented utility tests for LanguageCodeExtractor, PatternExtractor, and FrameClassMatcher to validate their extraction and matching capabilities.
- Updated test cases to use datasets for better maintainability and clarity.
- Enhanced error handling tests to ensure robustness against missing or invalid data.
This commit is contained in:
sha
2025-12-31 14:04:33 +00:00
parent c5fbd367fc
commit 262c0a7b7d
45 changed files with 3346 additions and 1057 deletions
+333
View File
@@ -0,0 +1,333 @@
"""Tests for the service layer.
Tests for FileTreeService, MetadataService, and RenameService.
"""
import pytest
from pathlib import Path
from unittest.mock import Mock, MagicMock, patch
import tempfile
import os
from renamer.services import FileTreeService, MetadataService, RenameService
from renamer.cache import Cache
from renamer.settings import Settings
class TestFileTreeService:
"""Test FileTreeService functionality."""
@pytest.fixture
def service(self):
"""Create a FileTreeService instance."""
return FileTreeService()
@pytest.fixture
def temp_dir(self):
"""Create a temporary directory with test files."""
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = Path(tmpdir)
# Create some test files
(tmpdir / "movie1.mkv").touch()
(tmpdir / "movie2.mp4").touch()
(tmpdir / "readme.txt").touch()
# Create subdirectory
subdir = tmpdir / "subdir"
subdir.mkdir()
(subdir / "movie3.avi").touch()
yield tmpdir
def test_validate_directory_valid(self, service, temp_dir):
"""Test validating a valid directory."""
is_valid, error = service.validate_directory(temp_dir)
assert is_valid is True
assert error is None
def test_validate_directory_not_exists(self, service):
"""Test validating a non-existent directory."""
is_valid, error = service.validate_directory(Path("/nonexistent"))
assert is_valid is False
assert "does not exist" in error
def test_validate_directory_is_file(self, service, temp_dir):
"""Test validating a file instead of directory."""
file_path = temp_dir / "movie1.mkv"
is_valid, error = service.validate_directory(file_path)
assert is_valid is False
assert "not a directory" in error
def test_scan_directory(self, service, temp_dir):
"""Test scanning directory for media files."""
files = service.scan_directory(temp_dir)
# Should find 3 media files (2 in root, 1 in subdir)
assert len(files) == 3
# Check file types
extensions = {f.suffix for f in files}
assert extensions == {'.mkv', '.mp4', '.avi'}
def test_scan_directory_non_recursive(self, service, temp_dir):
"""Test scanning without recursion."""
files = service.scan_directory(temp_dir, recursive=False)
# Should only find 2 files in root (not subdir)
assert len(files) == 2
def test_is_media_file(self, service):
"""Test media file detection."""
assert service._is_media_file(Path("movie.mkv")) is True
assert service._is_media_file(Path("movie.mp4")) is True
assert service._is_media_file(Path("readme.txt")) is False
assert service._is_media_file(Path("movie.MKV")) is True # Case insensitive
def test_count_media_files(self, service, temp_dir):
"""Test counting media files."""
count = service.count_media_files(temp_dir)
assert count == 3
def test_get_directory_stats(self, service, temp_dir):
"""Test getting directory statistics."""
stats = service.get_directory_stats(temp_dir)
assert stats['total_files'] == 4 # 3 media + 1 txt
assert stats['total_dirs'] == 1 # 1 subdir
assert stats['media_files'] == 3
class TestMetadataService:
"""Test MetadataService functionality."""
@pytest.fixture
def cache(self):
"""Create a cache instance."""
return Cache()
@pytest.fixture
def settings(self):
"""Create a settings instance."""
return Settings()
@pytest.fixture
def service(self, cache, settings):
"""Create a MetadataService instance."""
return MetadataService(cache, settings, max_workers=2)
@pytest.fixture
def test_file(self):
"""Create a temporary test file."""
with tempfile.NamedTemporaryFile(suffix='.mkv', delete=False) as f:
path = Path(f.name)
yield path
# Cleanup
if path.exists():
path.unlink()
def test_service_initialization(self, service):
"""Test service initializes correctly."""
assert service.max_workers == 2
assert service.executor is not None
assert service._lock is not None
def test_extract_metadata_sync(self, service, test_file):
"""Test synchronous metadata extraction."""
result = service.extract_metadata(test_file)
assert result is not None
assert 'formatted_info' in result
assert 'proposed_name' in result
assert 'mode' in result
def test_extract_metadata_async(self, service, test_file):
"""Test asynchronous metadata extraction with callback."""
callback_result = None
def callback(result):
nonlocal callback_result
callback_result = result
service.extract_metadata(test_file, callback=callback)
# Wait for async operation
import time
time.sleep(1.0)
# Callback should have been called
# May be None if file doesn't exist or extraction failed
assert callback_result is None or 'formatted_info' in callback_result
def test_get_active_extraction_count(self, service):
"""Test getting active extraction count."""
count = service.get_active_extraction_count()
assert count == 0
def test_shutdown(self, service):
"""Test service shutdown."""
service.shutdown(wait=False)
# Should not raise any errors
def test_context_manager(self, cache, settings):
"""Test using service as context manager."""
with MetadataService(cache, settings) as service:
assert service.executor is not None
# Executor should be shut down after context
class TestRenameService:
"""Test RenameService functionality."""
@pytest.fixture
def service(self):
"""Create a RenameService instance."""
return RenameService()
@pytest.fixture
def test_file(self):
"""Create a temporary test file."""
with tempfile.NamedTemporaryFile(suffix='.mkv', delete=False) as f:
path = Path(f.name)
yield path
# Cleanup
if path.exists():
path.unlink()
def test_sanitize_filename(self, service):
"""Test filename sanitization."""
assert service.sanitize_filename("Movie: Title?") == "Movie Title"
assert service.sanitize_filename("Movie<>|*.mkv") == "Movie.mkv"
assert service.sanitize_filename(" Movie ") == "Movie"
assert service.sanitize_filename("Movie...") == "Movie"
def test_validate_filename_valid(self, service):
"""Test validating a valid filename."""
is_valid, error = service.validate_filename("movie.mkv")
assert is_valid is True
assert error is None
def test_validate_filename_empty(self, service):
"""Test validating empty filename."""
is_valid, error = service.validate_filename("")
assert is_valid is False
assert "empty" in error.lower()
def test_validate_filename_too_long(self, service):
"""Test validating too long filename."""
long_name = "a" * 300
is_valid, error = service.validate_filename(long_name)
assert is_valid is False
assert "too long" in error.lower()
def test_validate_filename_reserved(self, service):
"""Test validating reserved Windows names."""
is_valid, error = service.validate_filename("CON.txt")
assert is_valid is False
assert "reserved" in error.lower()
def test_validate_filename_invalid_chars(self, service):
"""Test validating filename with invalid characters."""
is_valid, error = service.validate_filename("movie<>.mkv")
assert is_valid is False
assert "invalid" in error.lower()
def test_check_name_conflict_no_conflict(self, service, test_file):
"""Test checking for name conflict when none exists."""
has_conflict, msg = service.check_name_conflict(test_file, "newname.mkv")
assert has_conflict is False
assert msg is None
def test_check_name_conflict_exists(self, service, test_file):
"""Test checking for name conflict when file exists."""
# Use the same filename
has_conflict, msg = service.check_name_conflict(test_file, test_file.name)
assert has_conflict is False # Same file, no conflict
# Create another file
other_file = test_file.parent / "other.mkv"
other_file.touch()
has_conflict, msg = service.check_name_conflict(test_file, "other.mkv")
assert has_conflict is True
assert "already exists" in msg
# Cleanup
other_file.unlink()
def test_rename_file_dry_run(self, service, test_file):
"""Test renaming file in dry-run mode."""
success, msg = service.rename_file(test_file, "newname.mkv", dry_run=True)
assert success is True
assert "Would rename" in msg
# File should not actually be renamed
assert test_file.exists()
def test_rename_file_actual(self, service, test_file):
"""Test actually renaming a file."""
old_name = test_file.name
new_name = "renamed.mkv"
success, msg = service.rename_file(test_file, new_name, dry_run=False)
assert success is True
assert "Renamed" in msg
# Check file was renamed
new_path = test_file.parent / new_name
assert new_path.exists()
assert not test_file.exists()
# Cleanup
new_path.unlink()
def test_rename_file_not_exists(self, service):
"""Test renaming a file that doesn't exist."""
fake_path = Path("/nonexistent/file.mkv")
success, msg = service.rename_file(fake_path, "new.mkv")
assert success is False
assert "does not exist" in msg
def test_strip_markup(self, service):
"""Test stripping markup tags."""
assert service._strip_markup("[bold]text[/bold]") == "text"
assert service._strip_markup("[green]Movie[/green]") == "Movie"
assert service._strip_markup("No markup") == "No markup"
assert service._strip_markup("[bold green]text[/bold green]") == "text"
class TestServiceIntegration:
"""Integration tests for services working together."""
@pytest.fixture
def temp_dir(self):
"""Create a temporary directory with test files."""
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = Path(tmpdir)
(tmpdir / "movie1.mkv").touch()
(tmpdir / "movie2.mp4").touch()
yield tmpdir
def test_scan_and_rename_workflow(self, temp_dir):
"""Test a complete workflow: scan, then rename."""
# Scan for files
tree_service = FileTreeService()
files = tree_service.scan_directory(temp_dir)
assert len(files) == 2
# Rename one file
rename_service = RenameService()
old_file = files[0]
success, msg = rename_service.rename_file(old_file, "renamed.mkv")
assert success is True
# Scan again
new_files = tree_service.scan_directory(temp_dir)
assert len(new_files) == 2
# Check renamed file exists
renamed_path = temp_dir / "renamed.mkv"
assert renamed_path.exists()