mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 12:05:07 +00:00
Renames 314 defs and 41 module/class-level constants. Keeps 11 names underscored where the stripped form collides in scope, plus 5.5.0 shims for the three symbols external service repos call.
260 lines
10 KiB
Python
260 lines
10 KiB
Python
"""Precedence of user-supplied external IDs in providers.resolve_by_ids."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional, Union
|
|
|
|
import pytest
|
|
|
|
from unshackle.core import providers
|
|
from unshackle.core.config import config
|
|
from unshackle.core.providers._base import ExternalIds, MetadataProvider, MetadataResult
|
|
|
|
|
|
class FakeProvider(MetadataProvider):
|
|
"""Stands in for a real provider; records the ID it was asked for."""
|
|
|
|
def __init__(self, name: str, result: Optional[MetadataResult] = None, raises: bool = False) -> None:
|
|
self.NAME = name
|
|
self._result = result
|
|
self._raises = raises
|
|
self.asked: list[Union[int, str]] = []
|
|
super().__init__()
|
|
|
|
def is_available(self) -> bool:
|
|
return True
|
|
|
|
def search(self, title: str, year: Optional[int], kind: str) -> Optional[MetadataResult]:
|
|
raise AssertionError("resolve_by_ids must not search when an ID was supplied")
|
|
|
|
def get_by_id(self, provider_id: Union[int, str], kind: str) -> Optional[MetadataResult]:
|
|
self.asked.append(provider_id)
|
|
if self._raises:
|
|
raise ValueError("provider is down")
|
|
return self._result
|
|
|
|
def get_external_ids(self, provider_id: Union[int, str], kind: str) -> ExternalIds:
|
|
return ExternalIds()
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def isolate(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Keep every test off the network and on the default provider order."""
|
|
monkeypatch.setattr(config, "metadata_providers", [])
|
|
monkeypatch.setattr(providers, "fetch_external_ids", lambda *a, **kw: ExternalIds())
|
|
monkeypatch.setattr(providers, "enrich_ids", lambda result: None)
|
|
monkeypatch.setattr(
|
|
providers,
|
|
"search_metadata",
|
|
lambda *a, **kw: pytest.fail("search_metadata must not run when an ID was supplied"),
|
|
)
|
|
|
|
|
|
def patch_providers(monkeypatch: pytest.MonkeyPatch, available: dict[str, FakeProvider]) -> None:
|
|
monkeypatch.setattr(providers, "get_provider", lambda name: available.get(name))
|
|
|
|
|
|
def test_supplied_ids_survive_a_providers_own_answer(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
# tmdb answers with a different imdb id; the one the user gave must win
|
|
tmdb = FakeProvider(
|
|
"tmdb",
|
|
MetadataResult(title="Inception", year=2010, kind="movie", external_ids=ExternalIds(imdb_id="tt9999999")),
|
|
)
|
|
patch_providers(monkeypatch, {"tmdb": tmdb})
|
|
|
|
result = providers.resolve_by_ids(27205, "tt1375666", kind="movie")
|
|
|
|
assert result is not None
|
|
assert result.external_ids.tmdb_id == 27205
|
|
assert result.external_ids.tmdb_kind == "movie"
|
|
assert result.external_ids.imdb_id == "tt1375666"
|
|
|
|
|
|
def test_imdb_falls_through_to_omdb(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
imdb = FakeProvider("imdb", raises=True)
|
|
omdb = FakeProvider("omdb", MetadataResult(title="Inception", year=2010, kind="movie"))
|
|
patch_providers(monkeypatch, {"imdb": imdb, "omdb": omdb})
|
|
|
|
result = providers.resolve_by_ids(imdb_id="tt1375666", kind="movie")
|
|
|
|
assert result is not None
|
|
assert result.title == "Inception"
|
|
assert imdb.asked == ["tt1375666"]
|
|
assert omdb.asked == ["tt1375666"]
|
|
|
|
|
|
def test_all_providers_down_synthesizes_a_result(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
patch_providers(monkeypatch, {})
|
|
|
|
result = providers.resolve_by_ids(tvdb_id=73871, title="Futurama", year=1999, kind="tv")
|
|
|
|
assert result is not None
|
|
assert result.title == "Futurama"
|
|
assert result.year == 1999
|
|
assert result.external_ids.tvdb_id == 73871
|
|
|
|
|
|
def test_no_ids_searches(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
calls: list[tuple] = []
|
|
monkeypatch.setattr(providers, "search_metadata", lambda *a, **kw: calls.append(a) or None)
|
|
patch_providers(monkeypatch, {})
|
|
|
|
providers.resolve_by_ids(title="Futurama", year=1999, kind="tv")
|
|
|
|
assert calls and calls[0][:3] == ("Futurama", 1999, "tv")
|
|
|
|
|
|
def test_no_ids_and_no_title_returns_none(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
patch_providers(monkeypatch, {})
|
|
assert providers.resolve_by_ids(kind="tv") is None
|
|
|
|
|
|
def test_per_kind_order_puts_tvdb_first_for_tv(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(config, "metadata_providers", {"tv": ["tvdb", "tmdb", "imdb", "omdb"]})
|
|
tvdb = FakeProvider("tvdb", MetadataResult(title="Futurama", year=1999, kind="tv"))
|
|
tmdb = FakeProvider("tmdb", MetadataResult(title="Wrong Show", year=2020, kind="tv"))
|
|
patch_providers(monkeypatch, {"tvdb": tvdb, "tmdb": tmdb})
|
|
|
|
result = providers.resolve_by_ids(615, tvdb_id=73871, kind="tv")
|
|
|
|
assert result is not None
|
|
assert result.title == "Futurama"
|
|
assert tmdb.asked == []
|
|
# movies keep the default order, so tmdb is consulted before tvdb there
|
|
assert [cls.NAME for cls in providers.provider_order("movie")] == list(providers.DEFAULT_ORDER)
|
|
|
|
|
|
def test_flat_list_config_applies_to_both_kinds(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(config, "metadata_providers", ["tvdb", "tmdb"])
|
|
assert [cls.NAME for cls in providers.provider_order("tv")] == ["tvdb", "tmdb"]
|
|
assert [cls.NAME for cls in providers.provider_order("movie")] == ["tvdb", "tmdb"]
|
|
assert [cls.NAME for cls in providers.provider_order()] == ["tvdb", "tmdb"]
|
|
|
|
|
|
# ---------- the anilist namespace ----------
|
|
|
|
|
|
def test_an_anilist_id_reaches_the_anilist_provider(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
anilist = FakeProvider(
|
|
"anilist",
|
|
MetadataResult(title="One Piece", year=1999, kind="tv", external_ids=ExternalIds(anilist_id=21)),
|
|
)
|
|
tmdb = FakeProvider("tmdb", MetadataResult(title="Wrong Show", year=2020, kind="tv"))
|
|
patch_providers(monkeypatch, {"anilist": anilist, "tmdb": tmdb})
|
|
|
|
result = providers.resolve_by_ids(anilist_id=21, kind="tv")
|
|
|
|
assert result is not None
|
|
assert result.title == "One Piece"
|
|
assert result.external_ids.anilist_id == 21
|
|
assert anilist.asked == [21]
|
|
assert tmdb.asked == []
|
|
|
|
|
|
def test_a_supplied_anilist_id_survives_a_provider_miss(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
patch_providers(monkeypatch, {})
|
|
|
|
result = providers.resolve_by_ids(anilist_id="21", title="One Piece", kind="tv")
|
|
|
|
assert result is not None
|
|
assert result.external_ids.anilist_id == 21
|
|
|
|
|
|
def test_a_mal_reference_carries_only_what_the_lookup_answered(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
anilist = FakeProvider("anilist", MetadataResult(title="One Piece", external_ids=ExternalIds(anilist_id=21)))
|
|
patch_providers(monkeypatch, {"anilist": anilist})
|
|
|
|
result = providers.resolve_by_ids(anilist_id="mal:21", kind="tv")
|
|
|
|
assert result is not None
|
|
assert anilist.asked == ["mal:21"]
|
|
assert result.external_ids.anilist_id == 21
|
|
|
|
|
|
def test_a_mal_reference_that_misses_leaves_no_anilist_id(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
patch_providers(monkeypatch, {})
|
|
|
|
result = providers.resolve_by_ids(anilist_id="mal:21", title="One Piece", kind="tv")
|
|
|
|
assert result is not None
|
|
assert result.external_ids.anilist_id is None
|
|
|
|
|
|
def test_anilist_combines_with_a_western_id(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
tmdb = FakeProvider("tmdb", MetadataResult(title="One Piece", year=1999, kind="tv"))
|
|
anilist = FakeProvider("anilist", MetadataResult(title="One Piece", external_ids=ExternalIds(anilist_id=21)))
|
|
patch_providers(monkeypatch, {"tmdb": tmdb, "anilist": anilist})
|
|
|
|
result = providers.resolve_by_ids(37854, anilist_id=21, kind="tv")
|
|
|
|
assert result is not None
|
|
assert result.external_ids.tmdb_id == 37854
|
|
assert result.external_ids.anilist_id == 21
|
|
|
|
|
|
# ---------- the anime hint ----------
|
|
|
|
|
|
def test_anime_puts_anilist_first_with_the_rest_behind_it() -> None:
|
|
ordered = [cls.NAME for cls in providers.provider_order("tv", anime=True)]
|
|
assert ordered[0] == "anilist"
|
|
assert ordered[1:] == [name for name in providers.DEFAULT_ORDER if name != "anilist"]
|
|
|
|
|
|
def test_without_the_hint_anilist_stays_last() -> None:
|
|
assert [cls.NAME for cls in providers.provider_order("tv")] == list(providers.DEFAULT_ORDER)
|
|
assert providers.DEFAULT_ORDER[-1] == "anilist"
|
|
|
|
|
|
def test_the_hint_cannot_add_a_provider_the_config_left_out(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(config, "metadata_providers", ["tmdb", "tvdb"])
|
|
assert [cls.NAME for cls in providers.provider_order("tv", anime=True)] == ["tmdb", "tvdb"]
|
|
|
|
|
|
def test_an_anime_hint_falls_through_to_the_normal_order_on_a_miss(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""anilist is asked first but knows no imdb ids, so imdb still answers."""
|
|
anilist = FakeProvider("anilist", None)
|
|
imdb = FakeProvider("imdb", MetadataResult(title="One Piece", year=1999, kind="tv"))
|
|
patch_providers(monkeypatch, {"anilist": anilist, "imdb": imdb})
|
|
|
|
result = providers.resolve_by_ids(imdb_id="tt0388629", kind="tv", anime=True)
|
|
|
|
assert result is not None
|
|
assert result.title == "One Piece"
|
|
# the imdb id is in no namespace anilist consumes, so it is never asked
|
|
assert anilist.asked == []
|
|
assert imdb.asked == ["tt0388629"]
|
|
|
|
|
|
def test_the_anime_hint_reaches_search(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
calls: list[dict] = []
|
|
monkeypatch.setattr(providers, "search_metadata", lambda *a, **kw: calls.append({"args": a}) or None)
|
|
patch_providers(monkeypatch, {})
|
|
|
|
providers.resolve_by_ids(title="One Piece", year=1999, kind="tv", anime=True)
|
|
|
|
assert calls and calls[0]["args"][-1] is True
|
|
|
|
|
|
def test_all_supplied_ids_survive_together(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""anilist_id combined with a western trio member: every supplied ID reaches external_ids."""
|
|
tvdb = FakeProvider(
|
|
"tvdb",
|
|
MetadataResult(title="One Piece", year=1999, kind="tv", external_ids=ExternalIds(tvdb_id=81797)),
|
|
)
|
|
patch_providers(monkeypatch, {"tvdb": tvdb})
|
|
|
|
result = providers.resolve_by_ids(tvdb_id=81797, anilist_id="mal:21", imdb_id="tt0388629", kind="tv")
|
|
|
|
assert result is not None
|
|
assert result.external_ids.tvdb_id == 81797
|
|
assert result.external_ids.imdb_id == "tt0388629"
|
|
# a mal: ref the provider never resolved contributes no anilist_id; a bare id
|
|
# would have been forced back in
|
|
assert result.external_ids.anilist_id is None
|
|
|
|
result = providers.resolve_by_ids(tvdb_id=81797, anilist_id=21, kind="tv")
|
|
assert result is not None
|
|
assert result.external_ids.tvdb_id == 81797
|
|
assert result.external_ids.anilist_id == 21
|