From cce7df4f3d9c6ef18cb1baade9f88014d59bf67f Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Tue, 23 Jun 2026 17:58:17 -0700 Subject: [PATCH] #918: iTunes album fetch no longer truncates albums >50 tracks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iTunes get_album_tracks called _lookup(id, entity='song') with no limit. The iTunes Lookup API returns only 50 related entities unless limit is passed (max 200), so albums over 50 tracks showed only the first 50 in the download window. Pass limit=200 on the main lookup AND the fallback- storefront request. Proven against the live iTunes API on the reporter's exact album (Frieren OST, id 1739445636, 70 tracks): no limit -> 50 songs, limit=200 -> 70 songs. Spotify already paginates; Deezer uses limit=500 — iTunes was the only truncating source. Regression test asserts limit=200 is requested. --- core/itunes_client.py | 6 ++-- tests/test_itunes_album_tracks_limit.py | 46 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/test_itunes_album_tracks_limit.py diff --git a/core/itunes_client.py b/core/itunes_client.py index 25f9c73a..0d34b6db 100644 --- a/core/itunes_client.py +++ b/core/itunes_client.py @@ -741,7 +741,9 @@ class iTunesClient: if cached and cached.get('items'): return cached - results = self._lookup(id=album_id, entity='song') + # #918: the iTunes Lookup API returns only 50 related entities unless `limit` is + # passed (max 200), so albums >50 tracks were truncated in the download window. + results = self._lookup(id=album_id, entity='song', limit=200) if not results: return None @@ -763,7 +765,7 @@ class iTunesClient: try: fb_results = self.session.get( self.LOOKUP_URL, - params={'id': album_id, 'entity': 'song', 'country': fallback}, + params={'id': album_id, 'entity': 'song', 'country': fallback, 'limit': 200}, # #918 timeout=15 ) if fb_results.status_code == 200: diff --git a/tests/test_itunes_album_tracks_limit.py b/tests/test_itunes_album_tracks_limit.py new file mode 100644 index 00000000..1fa36e40 --- /dev/null +++ b/tests/test_itunes_album_tracks_limit.py @@ -0,0 +1,46 @@ +"""iTunes album-track fetch must request the full album, not the 50-entity default (#918). + +The iTunes Lookup API returns only 50 related entities unless `limit` is passed (max 200), +so albums >50 tracks were truncated to the first 50 in the download window. get_album_tracks +must pass limit=200. +""" + +from __future__ import annotations + +from core import itunes_client as ic + + +class _Cache: + """Cache stub: always a miss; any write method is a no-op.""" + def get_entity(self, *a, **k): + return None + + def __getattr__(self, _name): + return lambda *a, **k: None + + +_RESULTS = [ + {'wrapperType': 'collection', 'collectionId': 123, 'collectionName': 'Big OST', + 'artistName': 'Composer', 'trackCount': 70, 'artworkUrl100': 'http://x/100x100bb.jpg'}, + {'wrapperType': 'track', 'kind': 'song', 'trackId': 1, 'trackName': 'Track 1', + 'trackNumber': 1, 'discNumber': 1, 'artistName': 'Composer', 'trackTimeMillis': 1000}, +] + + +def test_get_album_tracks_requests_limit_200(monkeypatch): + client = ic.iTunesClient(country='US') + captured = {} + + def fake_lookup(**params): + captured.update(params) + return _RESULTS + + monkeypatch.setattr(client, '_lookup', fake_lookup) + monkeypatch.setattr(ic, 'get_metadata_cache', lambda: _Cache()) + + result = client.get_album_tracks('123') + + assert captured.get('limit') == 200 # #918: full album, not the iTunes 50 default + assert captured.get('entity') == 'song' + assert captured.get('id') == '123' + assert result is not None