""" Unit tests for playlist merge functionality. """ import os from pathlib import Path import pytest from app.utils.playlist_merge import ( merge_playlists, ConflictResolutionStrategy, load_paths, save_paths, ) # Test data directory TEST_CASE_DIR = Path(__file__).parent.parent / "test_case" TEST_RES_DIR = Path(__file__).parent.parent / "test_res" DOCKERAPP_TEST_DIR = Path(__file__).parent.parent / "dockerapp" / "test_playlists" def normalize_playlist_content(file_path: str | Path) -> list[str]: """ Read a playlist file and return normalized content (without comments/blanks). Args: file_path: Path to the playlist file Returns: List of track paths (non-empty, non-comment lines) """ with open(file_path, 'r', encoding='utf-8') as f: content = f.read() # Remove blank lines and comments lines = [ line.strip() for line in content.splitlines() if line.strip() and not line.startswith('#') ] return lines def assert_playlists_equal(actual_file: str | Path, expected_file: str | Path): """ Assert that two playlist files have identical content (ignoring comments). Args: actual_file: Path to the generated playlist expected_file: Path to the expected playlist Raises: AssertionError: If playlists differ """ actual_content = normalize_playlist_content(actual_file) expected_content = normalize_playlist_content(expected_file) if actual_content != expected_content: # Provide detailed diff on failure import difflib diff = difflib.unified_diff( expected_content, actual_content, fromfile=str(expected_file), tofile=str(actual_file), lineterm='' ) diff_text = '\n'.join(diff) pytest.fail(f"Playlist files differ:\n{diff_text}") class TestPlaylistMergeBasics: """Test basic playlist merge operations.""" def test_load_paths_removes_comments(self): """Test that load_paths correctly filters comments and blanks.""" text = "#EXTM3U\n\n/path/to/track1.mp3\n#Comment\n/path/to/track2.mp3\n" paths = load_paths(text) assert paths == ["/path/to/track1.mp3", "/path/to/track2.mp3"] def test_save_paths_adds_header(self): """Test that save_paths adds proper m3u header.""" paths = ["/path/to/track1.mp3", "/path/to/track2.mp3"] text = save_paths(paths) assert text.startswith("#EXTM3U\n") assert "/path/to/track1.mp3" in text assert "/path/to/track2.mp3" in text def test_empty_playlist_merge(self): """Test merging empty playlists.""" result = merge_playlists("", "", "", test_folder=str(DOCKERAPP_TEST_DIR / "empty_test")) assert result.merged_paths == [] assert result.conflicts == [] @pytest.mark.parametrize("case_num", [1, 2, 3, 4]) class TestPlaylistMergeLocalPriority: """Test playlist merge with local priority strategy.""" def test_merge_local_priority(self, case_num, tmp_path): """Test case {case_num} with local priority.""" # Load input files base_file = TEST_CASE_DIR / "base" / f"case{case_num}.m3u" local_file = TEST_CASE_DIR / "local_playlist" / f"case{case_num}.m3u" remote_file = TEST_CASE_DIR / "remote_playlist" / f"case{case_num}.m3u" with open(base_file, 'r', encoding='utf-8') as f: base_text = f.read() with open(local_file, 'r', encoding='utf-8') as f: local_text = f.read() with open(remote_file, 'r', encoding='utf-8') as f: remote_text = f.read() # Create test output folder test_folder = str(tmp_path / f"case{case_num}_local") # Perform merge result = merge_playlists( base_text=base_text, local_text=local_text, remote_text=remote_text, strategy=ConflictResolutionStrategy.LOCAL_PRIORITY, test_folder=test_folder ) # Compare with expected result expected_file = TEST_RES_DIR / f"case{case_num}_merged_local_primary.m3u" actual_file = Path(test_folder) / "local_result.m3u8" assert_playlists_equal(actual_file, expected_file) @pytest.mark.parametrize("case_num", [1, 2, 3, 4]) class TestPlaylistMergeRemotePriority: """Test playlist merge with remote priority strategy.""" def test_merge_remote_priority(self, case_num, tmp_path): """Test case {case_num} with remote priority.""" # Load input files base_file = TEST_CASE_DIR / "base" / f"case{case_num}.m3u" local_file = TEST_CASE_DIR / "local_playlist" / f"case{case_num}.m3u" remote_file = TEST_CASE_DIR / "remote_playlist" / f"case{case_num}.m3u" with open(base_file, 'r', encoding='utf-8') as f: base_text = f.read() with open(local_file, 'r', encoding='utf-8') as f: local_text = f.read() with open(remote_file, 'r', encoding='utf-8') as f: remote_text = f.read() # Create test output folder test_folder = str(tmp_path / f"case{case_num}_remote") # Perform merge result = merge_playlists( base_text=base_text, local_text=local_text, remote_text=remote_text, strategy=ConflictResolutionStrategy.REMOTE_PRIORITY, test_folder=test_folder ) # Compare with expected result expected_file = TEST_RES_DIR / f"case{case_num}_merged_remote_primary.m3u" actual_file = Path(test_folder) / "remote_result.m3u8" assert_playlists_equal(actual_file, expected_file) class TestConflictDetection: """Test conflict detection in merges.""" def test_conflict_reporting(self, tmp_path): """Test that conflicts are properly reported.""" base = "#EXTM3U\n/track1.mp3\n" local = "#EXTM3U\n/track2.mp3\n" # Changed remote = "#EXTM3U\n/track3.mp3\n" # Changed differently result = merge_playlists( base_text=base, local_text=local, remote_text=remote, strategy=ConflictResolutionStrategy.LOCAL_PRIORITY, test_folder=str(tmp_path / "conflict_test") ) # Should detect conflict assert len(result.conflicts) > 0 # Local priority should prefer local version assert "/track2.mp3" in result.merged_paths if __name__ == "__main__": # Allow running tests directly pytest.main([__file__, "-v"])