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"