mirror of
https://github.com/shadoll/moma.git
synced 2026-08-28 11:33:25 +00:00
- 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.
334 lines
11 KiB
Python
334 lines
11 KiB
Python
"""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()
|