Snapshot of the existing codebase before working through the TASKS.md backlog. Real library data (data/) and the iTunes import fixture (itunes-test-library/) are gitignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
150 lines
4.7 KiB
Python
150 lines
4.7 KiB
Python
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from lintunes.storage.conflict_resolver import resolve_conflicts
|
|
|
|
|
|
def _write_json(path: Path, data: dict):
|
|
with open(path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
|
|
def _read_json(path: Path) -> dict:
|
|
with open(path) as f:
|
|
return json.load(f)
|
|
|
|
|
|
class TestConflictResolver:
|
|
def test_no_conflicts(self, tmp_path):
|
|
data_dir = tmp_path / ".lintunes"
|
|
data_dir.mkdir()
|
|
_write_json(data_dir / "library.json", {"1": {"track_id": 1, "name": "Song"}})
|
|
resolve_conflicts(data_dir)
|
|
# Should not crash, data should be unchanged
|
|
result = _read_json(data_dir / "library.json")
|
|
assert result["1"]["name"] == "Song"
|
|
|
|
def test_nonexistent_dir(self, tmp_path):
|
|
# Should not crash
|
|
resolve_conflicts(tmp_path / "nonexistent")
|
|
|
|
def test_merge_play_counts(self, tmp_path):
|
|
data_dir = tmp_path / ".lintunes"
|
|
data_dir.mkdir()
|
|
|
|
original = {
|
|
"1": {"track_id": 1, "name": "Song", "play_count": 5, "skip_count": 2}
|
|
}
|
|
conflict = {
|
|
"1": {"track_id": 1, "name": "Song", "play_count": 8, "skip_count": 1}
|
|
}
|
|
|
|
_write_json(data_dir / "library.json", original)
|
|
_write_json(
|
|
data_dir / "library.sync-conflict-20240101-120000-ABCDEFG.json",
|
|
conflict,
|
|
)
|
|
|
|
resolve_conflicts(data_dir)
|
|
|
|
result = _read_json(data_dir / "library.json")
|
|
assert result["1"]["play_count"] == 8 # max(5, 8)
|
|
assert result["1"]["skip_count"] == 2 # max(2, 1)
|
|
|
|
def test_merge_loved_or(self, tmp_path):
|
|
data_dir = tmp_path / ".lintunes"
|
|
data_dir.mkdir()
|
|
|
|
original = {"1": {"track_id": 1, "loved": False}}
|
|
conflict = {"1": {"track_id": 1, "loved": True}}
|
|
|
|
_write_json(data_dir / "library.json", original)
|
|
_write_json(
|
|
data_dir / "library.sync-conflict-20240101-120000-ABCDEFG.json",
|
|
conflict,
|
|
)
|
|
|
|
resolve_conflicts(data_dir)
|
|
|
|
result = _read_json(data_dir / "library.json")
|
|
assert result["1"]["loved"] is True
|
|
|
|
def test_merge_date_added_earliest(self, tmp_path):
|
|
data_dir = tmp_path / ".lintunes"
|
|
data_dir.mkdir()
|
|
|
|
original = {"1": {"track_id": 1, "date_added": "2022-06-01T00:00:00"}}
|
|
conflict = {"1": {"track_id": 1, "date_added": "2020-01-15T00:00:00"}}
|
|
|
|
_write_json(data_dir / "library.json", original)
|
|
_write_json(
|
|
data_dir / "library.sync-conflict-20240101-120000-ABCDEFG.json",
|
|
conflict,
|
|
)
|
|
|
|
resolve_conflicts(data_dir)
|
|
|
|
result = _read_json(data_dir / "library.json")
|
|
assert result["1"]["date_added"] == "2020-01-15T00:00:00"
|
|
|
|
def test_merge_new_track_from_conflict(self, tmp_path):
|
|
data_dir = tmp_path / ".lintunes"
|
|
data_dir.mkdir()
|
|
|
|
original = {"1": {"track_id": 1, "name": "Song A"}}
|
|
conflict = {
|
|
"1": {"track_id": 1, "name": "Song A"},
|
|
"2": {"track_id": 2, "name": "Song B"},
|
|
}
|
|
|
|
_write_json(data_dir / "library.json", original)
|
|
_write_json(
|
|
data_dir / "library.sync-conflict-20240101-120000-ABCDEFG.json",
|
|
conflict,
|
|
)
|
|
|
|
resolve_conflicts(data_dir)
|
|
|
|
result = _read_json(data_dir / "library.json")
|
|
assert "2" in result
|
|
assert result["2"]["name"] == "Song B"
|
|
|
|
def test_playlist_merge_union(self, tmp_path):
|
|
data_dir = tmp_path / ".lintunes"
|
|
playlists_dir = data_dir / "playlists"
|
|
playlists_dir.mkdir(parents=True)
|
|
|
|
original = {"name": "My Playlist", "track_ids": [1, 2, 3], "settings": {}}
|
|
conflict = {"name": "My Playlist", "track_ids": [2, 3, 4, 5], "settings": {}}
|
|
|
|
orig_path = playlists_dir / "PL1.json"
|
|
conf_path = playlists_dir / "PL1.sync-conflict-20240101-120000-ABCDEFG.json"
|
|
|
|
_write_json(orig_path, original)
|
|
_write_json(conf_path, conflict)
|
|
|
|
resolve_conflicts(data_dir)
|
|
|
|
result = _read_json(orig_path)
|
|
# All track IDs should be present (union)
|
|
assert set(result["track_ids"]) == {1, 2, 3, 4, 5}
|
|
|
|
def test_conflicts_archived(self, tmp_path):
|
|
data_dir = tmp_path / ".lintunes"
|
|
data_dir.mkdir()
|
|
|
|
_write_json(data_dir / "library.json", {"1": {"track_id": 1}})
|
|
conf_path = data_dir / "library.sync-conflict-20240101-120000-ABCDEFG.json"
|
|
_write_json(conf_path, {"1": {"track_id": 1}})
|
|
|
|
resolve_conflicts(data_dir)
|
|
|
|
assert not conf_path.exists()
|
|
resolved_dir = data_dir / ".resolved"
|
|
assert resolved_dir.exists()
|
|
archived = list(resolved_dir.iterdir())
|
|
assert len(archived) == 1
|