soulsync/tests/discovery/test_sync_database_only_matcher.py
BoulderBadgeDad 482d5fbc79 Fix: Spotify sync crash 'unexpected keyword argument candidate_pool'
When no media server is connected, discovery/sync patches sync_service's
matcher with a database-only implementation. sync_service calls it as
_find_track_in_media_server(track, candidate_pool=...) (a per-artist candidate
cache), but the database-only override took only (spotify_track) — so every
sync raised 'database_only_find_track() got an unexpected keyword argument
candidate_pool' and aborted.

Lift the override from a nested closure to a module-level
_database_only_find_track(spotify_track, candidate_pool=None) so it (a) accepts
the kwarg for interface parity with the real matcher and (b) is importable and
unit-testable. The DB-only path queries the library directly via
check_track_exists, so it accepts but doesn't need the candidate cache. Also
dropped the dead original_find_track local.

Tests: signature includes candidate_pool; called with candidate_pool={} returns
(None, 0.0) on no match; returns the match when the DB has it.
2026-05-31 23:00:24 -07:00

51 lines
2.1 KiB
Python

"""Regression: the database-only sync matcher must accept candidate_pool.
sync_service calls _find_track_in_media_server(track, candidate_pool=...). When
no media server is connected, discovery/sync patches in a database-only matcher.
That override dropped the candidate_pool kwarg, so every Spotify sync failed with
"unexpected keyword argument 'candidate_pool'". These pin the contract.
"""
from __future__ import annotations
import asyncio
import inspect
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import core.discovery.sync as sync_mod
def test_matcher_signature_accepts_candidate_pool():
sig = inspect.signature(sync_mod._database_only_find_track)
assert 'candidate_pool' in sig.parameters
def _run(track, **kw):
fake_db = MagicMock()
fake_db.read_sync_match_cache.return_value = None
fake_db.check_track_exists.return_value = (None, 0.0)
fake_cm = MagicMock()
fake_cm.get_active_media_server.return_value = "plex"
with patch("database.music_database.MusicDatabase", return_value=fake_db), \
patch("config.settings.config_manager", fake_cm):
return asyncio.run(sync_mod._database_only_find_track(track, **kw))
def test_called_with_candidate_pool_no_match():
track = SimpleNamespace(name="Song", artists=["Artist"], id="sp1")
# The exact call sync_service makes — must not raise TypeError.
assert _run(track, candidate_pool={}) == (None, 0.0)
def test_returns_match_when_db_has_it():
track = SimpleNamespace(name="HUMBLE.", artists=["Kendrick Lamar"], id="sp2")
fake_db = MagicMock()
fake_db.read_sync_match_cache.return_value = None
fake_db.check_track_exists.return_value = (SimpleNamespace(id="t1", title="HUMBLE."), 0.95)
fake_cm = MagicMock()
fake_cm.get_active_media_server.return_value = "plex"
with patch("database.music_database.MusicDatabase", return_value=fake_db), \
patch("config.settings.config_manager", fake_cm):
match, conf = asyncio.run(sync_mod._database_only_find_track(track, candidate_pool={}))
assert conf == 0.95 and match.id == "t1"