199 lines
6.6 KiB
Python
199 lines
6.6 KiB
Python
"""
|
|
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"])
|