soulsync/tests/test_watchlist_itunes_match.py
BoulderBadgeDad 377254572b Watchlist: iTunes ID backfill never worked in the normal wiring — use the registry client
Boulder noticed his recently added watchlist artists (June 4 batch) have no
iTunes match while Spotify/Deezer/MusicBrainz matched fine. The rotated log
has the receipt: "Cannot match to iTunes - MetadataService not available" ×8
→ "Backfilled 0/8 artists with itunes IDs", every scan.

_match_to_itunes was the only matcher with no fallback: it read the PRIVATE
_metadata_service attr, which is None whenever the scanner is constructed
from a spotify_client — the normal web_server wiring — and gave up, while a
lazy-loading metadata_service property sat right next to it and the deezer/
discogs/musicbrainz matchers all fall back to their registry clients. Bonus
landmine: even when set, metadata_service.itunes is the FALLBACK-client slot
and may actually be a DeezerClient (per _match_to_deezer's own comment), so
"iTunes" matching could have stored a Deezer artist ID as itunes_artist_id.

Now mirrors the other matchers: canonical registry get_itunes_client().
Self-healing — missing IDs are re-attempted every scan, so existing
watchlists backfill on the next run with no migration needed.

Tests: match works with _metadata_service=None (the exact production
condition), unconfident result returns None, missing client degrades
gracefully. 103 watchlist tests pass.
2026-06-07 11:22:03 -07:00

50 lines
1.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Watchlist iTunes ID backfill (Boulder: recent watchlist artists never got
their iTunes match — 'MetadataService not available' ×8, Backfilled 0/8).
_match_to_itunes was the only matcher with no fallback: it read the PRIVATE
_metadata_service attr, which is None in the normal web_server wiring
(scanner built from a spotify_client), and gave up. It must use the canonical
registry iTunes client like the deezer/discogs/musicbrainz matchers do.
"""
from __future__ import annotations
from types import SimpleNamespace
import core.watchlist_scanner as ws
from core.watchlist_scanner import WatchlistScanner
def _scanner():
# The normal web_server wiring: spotify_client only, no MetadataService.
return WatchlistScanner(spotify_client=SimpleNamespace())
def _registry(monkeypatch, results):
client = SimpleNamespace(search_artists=lambda name, limit=5: results)
import core.metadata.registry as registry
monkeypatch.setattr(registry, 'get_itunes_client', lambda *a, **k: client)
return client
def test_itunes_match_works_without_metadata_service(monkeypatch):
s = _scanner()
assert s._metadata_service is None # the exact production condition
_registry(monkeypatch, [SimpleNamespace(name='Green Day', id='it123', popularity=80)])
assert s._match_to_itunes('Green Day') == 'it123'
def test_itunes_match_unconfident_returns_none(monkeypatch):
s = _scanner()
_registry(monkeypatch, [SimpleNamespace(name='Completely Different Band', id='x1', popularity=10)])
assert s._match_to_itunes('Green Day') is None
def test_itunes_match_no_client_returns_none(monkeypatch):
s = _scanner()
import core.metadata.registry as registry
monkeypatch.setattr(registry, 'get_itunes_client', lambda *a, **k: None)
assert s._match_to_itunes('Green Day') is None