mirror of
https://github.com/beetbox/beets.git
synced 2026-03-15 09:53:30 +01:00
Move search orchestration into SearchApiMetadataSourcePlugin. Migrate Deezer, Spotify, and Discogs to provider hooks. Keep query handling, logging, and limits centralized.
122 lines
3.7 KiB
Python
122 lines
3.7 KiB
Python
from collections.abc import Iterable
|
|
|
|
import pytest
|
|
|
|
from beets import metadata_plugins
|
|
from beets.test.helper import PluginMixin
|
|
|
|
|
|
class ErrorMetadataMockPlugin(metadata_plugins.MetadataSourcePlugin):
|
|
"""A metadata source plugin that raises errors in all its methods."""
|
|
|
|
def candidates(self, *args, **kwargs):
|
|
raise ValueError("Mocked error")
|
|
|
|
def item_candidates(self, *args, **kwargs):
|
|
for i in range(3):
|
|
raise ValueError("Mocked error")
|
|
yield # This is just to make this a generator
|
|
|
|
def album_for_id(self, *args, **kwargs):
|
|
raise ValueError("Mocked error")
|
|
|
|
def track_for_id(self, *args, **kwargs):
|
|
raise ValueError("Mocked error")
|
|
|
|
|
|
class TestMetadataPluginsException(PluginMixin):
|
|
"""Check that errors during the metadata plugins do not crash beets.
|
|
They should be logged as errors instead.
|
|
"""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
metadata_plugins.find_metadata_source_plugins.cache_clear()
|
|
self.register_plugin(ErrorMetadataMockPlugin)
|
|
yield
|
|
self.unload_plugins()
|
|
|
|
@pytest.fixture
|
|
def call_method(self, method_name, args):
|
|
def _call():
|
|
result = getattr(metadata_plugins, method_name)(*args)
|
|
return list(result) if isinstance(result, Iterable) else result
|
|
|
|
return _call
|
|
|
|
@pytest.mark.parametrize(
|
|
"method_name,error_method_name,args",
|
|
[
|
|
("candidates", "candidates", ()),
|
|
("item_candidates", "item_candidates", ()),
|
|
("albums_for_ids", "albums_for_ids", (["some_id"],)),
|
|
("tracks_for_ids", "tracks_for_ids", (["some_id"],)),
|
|
# Currently, singular methods call plural ones internally and log
|
|
# errors from there
|
|
("album_for_id", "albums_for_ids", ("some_id",)),
|
|
("track_for_id", "tracks_for_ids", ("some_id",)),
|
|
],
|
|
)
|
|
def test_logging(self, caplog, call_method, error_method_name):
|
|
self.config["raise_on_error"] = False
|
|
|
|
call_method()
|
|
|
|
assert (
|
|
f"Error in 'ErrorMetadataMock.{error_method_name}': Mocked error"
|
|
in caplog.text
|
|
)
|
|
|
|
@pytest.mark.parametrize(
|
|
"method_name,args",
|
|
[
|
|
("candidates", ()),
|
|
("item_candidates", ()),
|
|
("album_for_id", ("some_id",)),
|
|
("track_for_id", ("some_id",)),
|
|
],
|
|
)
|
|
def test_raising(self, call_method):
|
|
self.config["raise_on_error"] = True
|
|
|
|
with pytest.raises(ValueError, match="Mocked error"):
|
|
call_method()
|
|
|
|
|
|
class TestSearchApiMetadataSourcePlugin(PluginMixin):
|
|
plugin = "none"
|
|
preload_plugin = False
|
|
|
|
class RaisingSearchApiMetadataMockPlugin(
|
|
metadata_plugins.SearchApiMetadataSourcePlugin[
|
|
metadata_plugins.IDResponse
|
|
]
|
|
):
|
|
def get_search_response(self, _):
|
|
raise ValueError("Search failure")
|
|
|
|
def album_for_id(self, _):
|
|
return None
|
|
|
|
def track_for_id(self, _):
|
|
return None
|
|
|
|
@pytest.fixture
|
|
def search_plugin(self):
|
|
return self.RaisingSearchApiMetadataMockPlugin()
|
|
|
|
def test_search_api_returns_empty_when_raise_on_error_disabled(
|
|
self, config, search_plugin, caplog
|
|
):
|
|
config["raise_on_error"] = False
|
|
|
|
assert search_plugin._search_api("track", "query", {}) == ()
|
|
assert "Search failure" in caplog.text
|
|
|
|
def test_search_api_raises_when_raise_on_error_enabled(
|
|
self, config, search_plugin
|
|
):
|
|
config["raise_on_error"] = True
|
|
|
|
with pytest.raises(ValueError, match="Search failure"):
|
|
search_plugin._search_api("track", "query", {})
|