From d123581a39e1e5f9ffac8b5c13b361f315e8b31d Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Fri, 8 May 2026 07:46:09 -0700 Subject: [PATCH] Fix: ID gate missed Deezer-track-id-only rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original gate baked into `_select_discovery_tracks` only checked Spotify + iTunes: AND (spotify_track_id IS NOT NULL OR itunes_track_id IS NOT NULL) For Deezer-primary users, discovery_pool rows have populated `deezer_track_id` but NULL Spotify + NULL iTunes IDs. The gate filtered every row out — Time Machine, Genre Browser, Hidden Gems, Discovery Shuffle, Popular Picks all rendered "no tracks found" for every tab on every Deezer-primary install. Extended the gate to include `deezer_track_id` and added that column to the standard SELECT column tuple. `_build_track_dict` already exposed `deezer_track_id` in its output shape, so frontend rendering needed no changes. Regression pinned via new test `test_discovery_helper_accepts_deezer_only_id_rows` — inserts a row with NULL Spotify + NULL iTunes but a populated `deezer_track_id` and asserts it survives the gate. 2220/2220 full suite green. --- core/personalized_playlists.py | 5 +++-- tests/test_personalized_playlists_id_gate.py | 23 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/core/personalized_playlists.py b/core/personalized_playlists.py index da507121..f6ad3bd8 100644 --- a/core/personalized_playlists.py +++ b/core/personalized_playlists.py @@ -111,6 +111,7 @@ class PersonalizedPlaylistsService: _STANDARD_DISCOVERY_COLUMNS: Tuple[str, ...] = ( 'spotify_track_id', 'itunes_track_id', + 'deezer_track_id', 'track_name', 'artist_name', 'album_name', @@ -141,7 +142,7 @@ class PersonalizedPlaylistsService: The WHERE clause always includes: source = ? - AND (spotify_track_id IS NOT NULL OR itunes_track_id IS NOT NULL) + AND (spotify_track_id IS NOT NULL OR itunes_track_id IS NOT NULL OR deezer_track_id IS NOT NULL) AND LOWER(artist_name) NOT IN (SELECT LOWER(artist_name) FROM discovery_artist_blacklist) @@ -181,7 +182,7 @@ class PersonalizedPlaylistsService: {select_cols} FROM discovery_pool WHERE source = ? - AND (spotify_track_id IS NOT NULL OR itunes_track_id IS NOT NULL) + AND (spotify_track_id IS NOT NULL OR itunes_track_id IS NOT NULL OR deezer_track_id IS NOT NULL) AND LOWER(artist_name) NOT IN (SELECT LOWER(artist_name) FROM discovery_artist_blacklist) {extra_where} ORDER BY {order_by} diff --git a/tests/test_personalized_playlists_id_gate.py b/tests/test_personalized_playlists_id_gate.py index 3f205982..568aa411 100644 --- a/tests/test_personalized_playlists_id_gate.py +++ b/tests/test_personalized_playlists_id_gate.py @@ -135,6 +135,29 @@ def test_discovery_helper_filters_null_id_rows(service): assert names == ['Has Spotify ID', 'Has iTunes ID'] +def test_discovery_helper_accepts_deezer_only_id_rows(service): + """Discovery pool rows with NULL spotify + NULL itunes but a populated + deezer_track_id MUST pass the gate. Regression test — early version + of the gate only checked Spotify + iTunes, which silently filtered + out every row for Deezer-primary users (entire Time Machine / + Genre / Hidden Gems / Shuffle / Popular Picks rendered empty).""" + svc, db = service + db.insert_discovery_track( + source='deezer', deezer_track_id='dz1', + spotify_track_id=None, itunes_track_id=None, + track_name='Deezer Only', artist_name='A', album_name='X', + popularity=50, release_date='2024-01-01', + ) + with patch.object(svc, '_get_active_source', return_value='deezer'): + tracks = svc._select_discovery_tracks( + source='deezer', + order_by='track_name', + fetch_limit=100, + ) + assert [t['track_name'] for t in tracks] == ['Deezer Only'] + assert tracks[0]['deezer_track_id'] == 'dz1' + + def test_discovery_helper_filters_blacklisted_artists(service): svc, db = service db.insert_discovery_track(