Import existing LinTunes project

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>
This commit is contained in:
2026-06-26 21:12:01 -04:00
commit f6d35fa594
70 changed files with 8992 additions and 0 deletions

105
tests/test_round3.py Normal file
View File

@ -0,0 +1,105 @@
import hashlib
import json
import pytest
from lintunes.lastfm import sign, LastFm
from lintunes.player import make_shuffle_order
from lintunes.preferences import Preferences
class TestLastFmSignature:
def test_signature_matches_hand_computed(self):
params = {
"method": "auth.getMobileSession",
"api_key": "KEY",
"username": "trav",
"password": "hunter2",
"format": "json", # must be excluded from the signature
}
expected = hashlib.md5(
("api_keyKEY"
"methodauth.getMobileSession"
"passwordhunter2"
"usernametrav"
"SECRET").encode()).hexdigest()
assert sign(params, "SECRET") == expected
def test_format_and_callback_excluded(self):
base = {"method": "x", "api_key": "k"}
with_extras = dict(base, format="json", callback="cb")
assert sign(base, "s") == sign(with_extras, "s")
class TestScrobbleQueue:
def test_queue_round_trip(self, qapp, tmp_path):
prefs = Preferences(tmp_path)
lastfm = LastFm(prefs, tmp_path)
entry = {"artist": "A", "track": "T", "album": "L",
"timestamp": 123, "duration": 180}
lastfm._enqueue(entry)
assert lastfm._load_queue() == [entry]
lastfm._save_queue([])
assert lastfm._load_queue() == []
assert not (tmp_path / "scrobble_queue.json").exists()
def test_queue_capped(self, qapp, tmp_path):
prefs = Preferences(tmp_path)
lastfm = LastFm(prefs, tmp_path)
for i in range(600):
lastfm._enqueue({"artist": "A", "track": str(i), "timestamp": i})
queue = lastfm._load_queue()
assert len(queue) == 500
assert queue[-1]["track"] == "599"
class TestShuffleOrder:
def test_permutation_starts_at_current(self):
order = make_shuffle_order(10, 4)
assert order[0] == 4
assert sorted(order) == list(range(10))
def test_single_track(self):
assert make_shuffle_order(1, 0) == [0]
def test_empty(self):
assert make_shuffle_order(0, 0) == []
def test_is_randomized(self):
# With 20 tracks, two consecutive shuffles colliding entirely is
# vanishingly unlikely (1/19!)
orders = {tuple(make_shuffle_order(20, 0)) for _ in range(5)}
assert len(orders) > 1
class TestPreferences:
def test_defaults(self, qapp, tmp_path):
prefs = Preferences(tmp_path)
assert prefs.get("ui_scale") == "medium"
assert prefs.get("highlight") == "blue"
assert prefs.lastfm["scrobble_enabled"] is False
def test_round_trip(self, qapp, tmp_path):
prefs = Preferences(tmp_path)
prefs.set("ui_scale", "large")
prefs.update_lastfm(username="trav", session_key="abc")
reloaded = Preferences(tmp_path)
assert reloaded.get("ui_scale") == "large"
assert reloaded.lastfm["username"] == "trav"
assert reloaded.lastfm["session_key"] == "abc"
# Unknown/missing keys fall back to defaults
assert reloaded.get("highlight") == "blue"
def test_changed_signal(self, qapp, tmp_path):
prefs = Preferences(tmp_path)
fired = []
prefs.changed.connect(lambda: fired.append(1))
prefs.set("highlight", "seafoam")
prefs.set("highlight", "seafoam") # no-op, shouldn't fire again
assert len(fired) == 1
def test_corrupt_file_falls_back(self, qapp, tmp_path):
(tmp_path / "preferences.json").write_text("{not json")
prefs = Preferences(tmp_path)
assert prefs.get("ui_scale") == "medium"