Cover art lookup now honors an explicit prefer_source first, falls back to the runtime primary metadata source when unset, and uses the shared source priority for the remaining fallbacks.
167 lines
5.5 KiB
Python
167 lines
5.5 KiB
Python
import sqlite3
|
|
import sys
|
|
import types
|
|
from types import SimpleNamespace
|
|
|
|
# Stub optional Spotify dependency so metadata_service can import in tests.
|
|
if 'spotipy' not in sys.modules:
|
|
spotipy = types.ModuleType('spotipy')
|
|
oauth2 = types.ModuleType('spotipy.oauth2')
|
|
|
|
class _DummySpotify:
|
|
pass
|
|
|
|
class _DummyOAuth:
|
|
pass
|
|
|
|
spotipy.Spotify = _DummySpotify
|
|
oauth2.SpotifyOAuth = _DummyOAuth
|
|
oauth2.SpotifyClientCredentials = _DummyOAuth
|
|
spotipy.oauth2 = oauth2
|
|
sys.modules['spotipy'] = spotipy
|
|
sys.modules['spotipy.oauth2'] = oauth2
|
|
|
|
if 'config.settings' not in sys.modules:
|
|
config_mod = types.ModuleType('config')
|
|
settings_mod = types.ModuleType('config.settings')
|
|
|
|
class _DummyConfigManager:
|
|
def get(self, key, default=None):
|
|
return default
|
|
|
|
settings_mod.config_manager = _DummyConfigManager()
|
|
config_mod.settings = settings_mod
|
|
sys.modules['config'] = config_mod
|
|
sys.modules['config.settings'] = settings_mod
|
|
|
|
from core.repair_jobs import missing_cover_art as mca
|
|
|
|
|
|
class _FakeClient:
|
|
def __init__(self, album_image=None, search_image=None):
|
|
self.album_image = album_image
|
|
self.search_image = search_image
|
|
self.get_album_calls = []
|
|
self.search_calls = []
|
|
|
|
def get_album(self, album_id, include_tracks=False):
|
|
self.get_album_calls.append((album_id, include_tracks))
|
|
if self.album_image is None:
|
|
return None
|
|
if isinstance(self.album_image, str):
|
|
return {'images': [{'url': self.album_image}]}
|
|
return self.album_image
|
|
|
|
def search_albums(self, query, limit=1):
|
|
self.search_calls.append((query, limit))
|
|
if self.search_image is None:
|
|
return []
|
|
return [SimpleNamespace(id='search-album', image_url=self.search_image)]
|
|
|
|
|
|
def _make_db(album_row):
|
|
conn = sqlite3.connect(':memory:')
|
|
conn.row_factory = sqlite3.Row
|
|
cursor = conn.cursor()
|
|
cursor.execute(
|
|
"""
|
|
CREATE TABLE artists (
|
|
id INTEGER PRIMARY KEY,
|
|
name TEXT,
|
|
thumb_url TEXT
|
|
)
|
|
"""
|
|
)
|
|
cursor.execute(
|
|
"""
|
|
CREATE TABLE albums (
|
|
id INTEGER PRIMARY KEY,
|
|
title TEXT,
|
|
artist_id INTEGER,
|
|
thumb_url TEXT,
|
|
spotify_album_id TEXT,
|
|
itunes_album_id TEXT,
|
|
deezer_id TEXT,
|
|
discogs_id TEXT,
|
|
soul_id TEXT
|
|
)
|
|
"""
|
|
)
|
|
cursor.execute(
|
|
"INSERT INTO artists (id, name, thumb_url) VALUES (?, ?, ?)",
|
|
(1, 'Artist', 'https://artist/thumb'),
|
|
)
|
|
cursor.execute(
|
|
"""
|
|
INSERT INTO albums
|
|
(id, title, artist_id, thumb_url, spotify_album_id, itunes_album_id, deezer_id, discogs_id, soul_id)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
album_row,
|
|
)
|
|
conn.commit()
|
|
return conn
|
|
|
|
|
|
def _make_context(conn, prefer_source=None):
|
|
job_settings = {}
|
|
if prefer_source is not None:
|
|
job_settings['prefer_source'] = prefer_source
|
|
settings = {'repair.jobs.missing_cover_art.settings': job_settings}
|
|
findings = []
|
|
return SimpleNamespace(
|
|
db=SimpleNamespace(_get_connection=lambda: conn),
|
|
config_manager=SimpleNamespace(get=lambda key, default=None: settings.get(key, default)),
|
|
check_stop=lambda: False,
|
|
wait_if_paused=lambda: False,
|
|
update_progress=lambda *args, **kwargs: None,
|
|
report_progress=lambda *args, **kwargs: None,
|
|
create_finding=lambda **kwargs: findings.append(kwargs),
|
|
findings=findings,
|
|
)
|
|
|
|
|
|
def test_missing_cover_art_prefers_explicit_source_over_primary(monkeypatch):
|
|
conn = _make_db((1, 'Album', 1, '', 'sp-album', 'it-album', 'dz-album', 'dg-album', 'hy-album'))
|
|
context = _make_context(conn, prefer_source='spotify')
|
|
|
|
deezer_client = _FakeClient(album_image='https://img/deezer-direct')
|
|
spotify_client = _FakeClient(album_image='https://img/spotify-direct')
|
|
|
|
monkeypatch.setattr(mca, 'get_primary_source', lambda: 'deezer')
|
|
monkeypatch.setattr(
|
|
mca,
|
|
'get_client_for_source',
|
|
lambda source: {'deezer': deezer_client, 'spotify': spotify_client}.get(source),
|
|
)
|
|
|
|
result = mca.MissingCoverArtJob().scan(context)
|
|
|
|
assert result.findings_created == 1
|
|
assert spotify_client.get_album_calls == [('sp-album', False)]
|
|
assert deezer_client.get_album_calls == []
|
|
assert context.findings[0]['details']['found_artwork_url'] == 'https://img/spotify-direct'
|
|
|
|
|
|
def test_missing_cover_art_uses_primary_when_prefer_unset(monkeypatch):
|
|
conn = _make_db((1, 'Album', 1, '', None, None, None, None, None))
|
|
context = _make_context(conn)
|
|
|
|
discogs_client = _FakeClient(search_image='https://img/discogs-search')
|
|
spotify_client = _FakeClient(search_image='https://img/spotify-search')
|
|
itunes_client = _FakeClient(search_image='https://img/itunes-search')
|
|
|
|
monkeypatch.setattr(mca, 'get_primary_source', lambda: 'discogs')
|
|
monkeypatch.setattr(
|
|
mca,
|
|
'get_client_for_source',
|
|
lambda source: {'discogs': discogs_client, 'spotify': spotify_client, 'itunes': itunes_client}.get(source),
|
|
)
|
|
|
|
result = mca.MissingCoverArtJob().scan(context)
|
|
|
|
assert result.findings_created == 1
|
|
assert discogs_client.search_calls == [('Artist Album', 1)]
|
|
assert spotify_client.search_calls == []
|
|
assert itunes_client.search_calls == []
|
|
assert context.findings[0]['details']['found_artwork_url'] == 'https://img/discogs-search'
|