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>
176 lines
6.0 KiB
Python
176 lines
6.0 KiB
Python
import pytest
|
|
from datetime import datetime
|
|
|
|
from lintunes.models import Track, Playlist, PlaylistSettings, PlaylistType, Library
|
|
|
|
|
|
class TestTrack:
|
|
def test_to_dict_omits_defaults(self):
|
|
track = Track(track_id=1, name="Test Song")
|
|
d = track.to_dict()
|
|
assert d == {"track_id": 1, "name": "Test Song"}
|
|
assert "genre" not in d
|
|
assert "play_count" not in d
|
|
|
|
def test_from_dict_round_trip(self):
|
|
track = Track(
|
|
track_id=42,
|
|
name="My Song",
|
|
artist="The Artist",
|
|
album="The Album",
|
|
genre="Rock",
|
|
total_time=180000,
|
|
play_count=5,
|
|
rating=80,
|
|
loved=True,
|
|
date_added="2020-01-15T10:30:00",
|
|
)
|
|
d = track.to_dict()
|
|
restored = Track.from_dict(d)
|
|
assert restored.track_id == 42
|
|
assert restored.name == "My Song"
|
|
assert restored.artist == "The Artist"
|
|
assert restored.play_count == 5
|
|
assert restored.rating == 80
|
|
assert restored.loved is True
|
|
|
|
def test_from_dict_ignores_unknown_fields(self):
|
|
d = {"track_id": 1, "name": "Test", "unknown_field": "ignored"}
|
|
track = Track.from_dict(d)
|
|
assert track.track_id == 1
|
|
assert track.name == "Test"
|
|
|
|
def test_from_itunes_dict(self):
|
|
itunes_data = {
|
|
"Track ID": 100,
|
|
"Name": "iTunes Song",
|
|
"Artist": "iTunes Artist",
|
|
"Album": "iTunes Album",
|
|
"Genre": "Pop",
|
|
"Total Time": 240000,
|
|
"Play Count": 10,
|
|
"Play Date UTC": datetime(2023, 6, 15, 12, 0, 0),
|
|
"Date Added": datetime(2020, 3, 1, 8, 0, 0),
|
|
"Rating": 100,
|
|
"Loved": True,
|
|
"Persistent ID": "ABC123",
|
|
"Location": "file://localhost/Users/me/Music/Artist/Album/Song.mp3",
|
|
}
|
|
track = Track.from_itunes_dict(itunes_data)
|
|
assert track.track_id == 100
|
|
assert track.name == "iTunes Song"
|
|
assert track.total_time == 240000
|
|
assert track.play_count == 10
|
|
assert track.rating == 100
|
|
assert track.loved is True
|
|
assert track.play_date_utc == "2023-06-15T12:00:00"
|
|
assert track.location == "/Users/me/Music/Artist/Album/Song.mp3"
|
|
|
|
def test_location_decoding(self):
|
|
itunes_data = {
|
|
"Track ID": 1,
|
|
"Location": "file://localhost/Users/me/Music/The%20Artist/My%20Album/01%20Song.mp3",
|
|
}
|
|
track = Track.from_itunes_dict(itunes_data)
|
|
assert track.location == "/Users/me/Music/The Artist/My Album/01 Song.mp3"
|
|
|
|
def test_location_decoding_volumes_form(self):
|
|
# Modern iTunes XML uses file:///Volumes/... URLs
|
|
itunes_data = {
|
|
"Track ID": 1,
|
|
"Location": "file:///Volumes/Lucia/iTunes/iTunes%20Media/Music/A/B/C.m4a",
|
|
}
|
|
track = Track.from_itunes_dict(itunes_data)
|
|
assert track.location == "/Volumes/Lucia/iTunes/iTunes Media/Music/A/B/C.m4a"
|
|
|
|
|
|
class TestPlaylist:
|
|
def test_to_dict_from_dict_round_trip(self):
|
|
playlist = Playlist(
|
|
name="My Favorites",
|
|
persistent_id="ABCD1234",
|
|
playlist_type=PlaylistType.REGULAR,
|
|
track_ids=[1, 2, 3, 4, 5],
|
|
)
|
|
d = playlist.to_dict()
|
|
restored = Playlist.from_dict(d)
|
|
assert restored.name == "My Favorites"
|
|
assert restored.persistent_id == "ABCD1234"
|
|
assert restored.playlist_type == PlaylistType.REGULAR
|
|
assert restored.track_ids == [1, 2, 3, 4, 5]
|
|
|
|
def test_from_itunes_dict_regular(self):
|
|
itunes_data = {
|
|
"Name": "Party Mix",
|
|
"Playlist Persistent ID": "XYZ789",
|
|
"Playlist Items": [
|
|
{"Track ID": 10},
|
|
{"Track ID": 20},
|
|
{"Track ID": 30},
|
|
],
|
|
}
|
|
playlist = Playlist.from_itunes_dict(itunes_data)
|
|
assert playlist.name == "Party Mix"
|
|
assert playlist.persistent_id == "XYZ789"
|
|
assert playlist.playlist_type == PlaylistType.REGULAR
|
|
assert playlist.track_ids == [10, 20, 30]
|
|
assert playlist.is_system is False
|
|
|
|
def test_from_itunes_dict_system(self):
|
|
itunes_data = {
|
|
"Name": "Library",
|
|
"Master": True,
|
|
"Playlist Persistent ID": "MASTER1",
|
|
"Playlist Items": [{"Track ID": 1}],
|
|
}
|
|
playlist = Playlist.from_itunes_dict(itunes_data)
|
|
assert playlist.is_system is True
|
|
assert playlist.playlist_type == PlaylistType.SYSTEM
|
|
|
|
def test_from_itunes_dict_folder(self):
|
|
itunes_data = {
|
|
"Name": "My Folder",
|
|
"Folder": True,
|
|
"Playlist Persistent ID": "FOLDER1",
|
|
}
|
|
playlist = Playlist.from_itunes_dict(itunes_data)
|
|
assert playlist.playlist_type == PlaylistType.FOLDER
|
|
|
|
def test_from_itunes_dict_smart(self):
|
|
itunes_data = {
|
|
"Name": "Recently Added",
|
|
"Smart Info": b"...",
|
|
"Smart Criteria": b"...",
|
|
"Playlist Persistent ID": "SMART1",
|
|
}
|
|
playlist = Playlist.from_itunes_dict(itunes_data)
|
|
assert playlist.playlist_type == PlaylistType.SMART
|
|
|
|
|
|
class TestPlaylistSettings:
|
|
def test_default_settings(self):
|
|
settings = PlaylistSettings()
|
|
assert "name" in settings.visible_columns
|
|
assert settings.sort_ascending is True
|
|
|
|
def test_round_trip(self):
|
|
settings = PlaylistSettings(
|
|
visible_columns=["name", "artist"],
|
|
sort_column="artist",
|
|
sort_ascending=False,
|
|
column_widths={"name": 300},
|
|
)
|
|
d = settings.to_dict()
|
|
restored = PlaylistSettings.from_dict(d)
|
|
assert restored.visible_columns == ["name", "artist"]
|
|
assert restored.sort_column == "artist"
|
|
assert restored.sort_ascending is False
|
|
assert restored.column_widths == {"name": 300}
|
|
|
|
|
|
class TestLibrary:
|
|
def test_empty_library(self):
|
|
lib = Library()
|
|
assert len(lib.tracks) == 0
|
|
assert len(lib.playlists) == 0
|