mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-15 21:45:10 +00:00
feat(metadata)!: replace animeapi with a first-class anilist provider
--anilist supersedes --animeapi (REST: anilist_id); the keyless AniList GraphQL provider joins the registry with title search, mal: ID lookup, an ANILIST mkv tag, and the anilist_title_language config option. Services mark anime via Service.ANIME / Title.anime for anilist-first metadata resolution with fall-through.
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
"""AniList provider: search, ID lookup, title-variant config and derived fields."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
from unshackle.core.config import config
|
||||
from unshackle.core.providers.anilist import AniListProvider, parse_anilist_ref
|
||||
|
||||
ONE_PIECE = {
|
||||
"id": 21,
|
||||
"idMal": 21,
|
||||
"title": {"romaji": "ONE PIECE", "english": "One Piece", "native": "ONE PIECE"},
|
||||
"format": "TV",
|
||||
"startDate": {"year": 1999},
|
||||
"countryOfOrigin": "JP",
|
||||
}
|
||||
|
||||
ONE_PIECE_MOVIE = {
|
||||
"id": 459,
|
||||
"idMal": 459,
|
||||
"title": {"romaji": "ONE PIECE (Movie)", "english": "One Piece: The Movie", "native": "ONE PIECE (Movie)"},
|
||||
"format": "MOVIE",
|
||||
"startDate": {"year": 2000},
|
||||
"countryOfOrigin": "JP",
|
||||
}
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, body: dict) -> None:
|
||||
self._body = body
|
||||
|
||||
def json(self) -> dict:
|
||||
return self._body
|
||||
|
||||
|
||||
def _provider(monkeypatch: pytest.MonkeyPatch, body: dict) -> tuple[AniListProvider, list[dict]]:
|
||||
"""An AniListProvider whose every POST answers `body`; returns the sent payloads."""
|
||||
provider = AniListProvider()
|
||||
sent: list[dict] = []
|
||||
|
||||
def fake_post(url: str, json: Optional[dict] = None, **kwargs: Any) -> FakeResponse:
|
||||
sent.append(json or {})
|
||||
return FakeResponse(body)
|
||||
|
||||
monkeypatch.setattr(provider, "_session", type("S", (), {"post": staticmethod(fake_post)})())
|
||||
return provider, sent
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _default_title_language(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(config, "anilist_title_language", "english")
|
||||
|
||||
|
||||
# ---------- reference parsing ----------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("value", "expected"),
|
||||
[
|
||||
(21, ("id", 21)),
|
||||
("21", ("id", 21)),
|
||||
(" 21 ", ("id", 21)),
|
||||
("mal:21", ("mal", 21)),
|
||||
("MAL:21", ("mal", 21)),
|
||||
("mal:007", ("mal", 7)),
|
||||
("tt1375666", None),
|
||||
("anilist:21", None),
|
||||
("mal:", None),
|
||||
("", None),
|
||||
(0, None),
|
||||
("0", None),
|
||||
("mal:0", None),
|
||||
(-3, None),
|
||||
("21.5", None),
|
||||
("²", None), # superscript two passes isdigit() but crashes int()
|
||||
],
|
||||
)
|
||||
def test_parse_anilist_ref(value: Any, expected: Optional[tuple[str, int]]) -> None:
|
||||
assert parse_anilist_ref(value) == expected
|
||||
|
||||
|
||||
# ---------- search ----------
|
||||
|
||||
|
||||
def test_search_returns_the_best_match(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, sent = _provider(monkeypatch, {"data": {"Page": {"media": [ONE_PIECE, ONE_PIECE_MOVIE]}}})
|
||||
|
||||
result = provider.search("one piece", None, "tv")
|
||||
|
||||
assert result is not None
|
||||
assert result.title == "One Piece"
|
||||
assert result.year == 1999
|
||||
assert result.kind == "tv"
|
||||
assert result.original_language == "ja"
|
||||
assert result.external_ids.anilist_id == 21
|
||||
assert result.source == "anilist"
|
||||
assert sent[0]["variables"] == {"search": "one piece"}
|
||||
|
||||
|
||||
def test_search_prefers_the_requested_year(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Page": {"media": [ONE_PIECE, ONE_PIECE_MOVIE]}}})
|
||||
|
||||
result = provider.search("one piece", 2000, "movie")
|
||||
|
||||
assert result is not None
|
||||
assert result.year == 2000
|
||||
assert result.kind == "movie"
|
||||
assert result.external_ids.anilist_id == 459
|
||||
|
||||
|
||||
def test_search_prefers_the_requested_kind_without_a_year(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Page": {"media": [ONE_PIECE, ONE_PIECE_MOVIE]}}})
|
||||
|
||||
result = provider.search("one piece", None, "movie")
|
||||
|
||||
assert result is not None
|
||||
assert result.kind == "movie"
|
||||
assert result.external_ids.anilist_id == 459
|
||||
|
||||
|
||||
def test_search_for_tv_is_not_shadowed_by_a_movie(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Page": {"media": [ONE_PIECE_MOVIE, ONE_PIECE]}}})
|
||||
|
||||
result = provider.search("one piece", None, "tv")
|
||||
|
||||
assert result is not None
|
||||
assert result.kind == "tv"
|
||||
assert result.external_ids.anilist_id == 21
|
||||
|
||||
|
||||
def test_search_falls_back_when_no_node_matches_the_kind(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Page": {"media": [ONE_PIECE]}}})
|
||||
|
||||
result = provider.search("one piece", None, "movie")
|
||||
|
||||
assert result is not None
|
||||
assert result.external_ids.anilist_id == 21
|
||||
|
||||
|
||||
def test_search_of_a_non_anime_title_returns_none(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Page": {"media": []}}})
|
||||
assert provider.search("The Walking Dead", None, "tv") is None
|
||||
|
||||
|
||||
def test_graphql_errors_are_a_miss_not_a_raise(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, _ = _provider(monkeypatch, {"errors": [{"message": "Not Found."}], "data": {"Media": None}})
|
||||
assert provider.search("one piece", None, "tv") is None
|
||||
assert provider.get_by_id(21, "tv") is None
|
||||
|
||||
|
||||
# ---------- id lookup ----------
|
||||
|
||||
|
||||
def test_get_by_id_queries_the_anilist_id(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, sent = _provider(monkeypatch, {"data": {"Media": ONE_PIECE}})
|
||||
|
||||
result = provider.get_by_id(21, "tv")
|
||||
|
||||
assert result is not None
|
||||
assert result.title == "One Piece"
|
||||
assert sent[0]["variables"] == {"id": 21}
|
||||
|
||||
|
||||
def test_get_by_id_resolves_a_mal_reference_natively(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, sent = _provider(monkeypatch, {"data": {"Media": ONE_PIECE}})
|
||||
|
||||
result = provider.get_by_id("mal:21", "tv")
|
||||
|
||||
assert result is not None
|
||||
assert sent[0]["variables"] == {"idMal": 21}
|
||||
assert result.external_ids.anilist_id == 21
|
||||
|
||||
|
||||
def test_get_by_id_rejects_a_foreign_reference(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, sent = _provider(monkeypatch, {"data": {"Media": ONE_PIECE}})
|
||||
assert provider.get_by_id("tt1375666", "tv") is None
|
||||
assert sent == []
|
||||
|
||||
|
||||
def test_get_external_ids_needs_no_lookup_for_a_bare_id(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
provider, sent = _provider(monkeypatch, {"data": {"Media": ONE_PIECE}})
|
||||
|
||||
assert provider.get_external_ids(21, "tv").anilist_id == 21
|
||||
assert sent == []
|
||||
assert provider.get_external_ids("mal:21", "tv").anilist_id == 21
|
||||
assert sent[0]["variables"] == {"idMal": 21}
|
||||
|
||||
|
||||
# ---------- title variant ----------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("configured", "expected"),
|
||||
[("english", "One Piece"), ("romaji", "ONE PIECE"), ("native", "ONE PIECE"), ("klingon", "One Piece")],
|
||||
)
|
||||
def test_title_language_config(monkeypatch: pytest.MonkeyPatch, configured: str, expected: str) -> None:
|
||||
monkeypatch.setattr(config, "anilist_title_language", configured)
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Media": ONE_PIECE}})
|
||||
|
||||
result = provider.get_by_id(21, "tv")
|
||||
|
||||
assert result is not None
|
||||
assert result.title == expected
|
||||
|
||||
|
||||
def test_title_language_falls_back_when_the_variant_is_unset(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
node = {**ONE_PIECE, "title": {"romaji": "Koisuru ONE PIECE", "english": None, "native": "恋するワンピース"}}
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Media": node}})
|
||||
|
||||
result = provider.get_by_id(21, "tv")
|
||||
|
||||
assert result is not None
|
||||
assert result.title == "Koisuru ONE PIECE"
|
||||
|
||||
|
||||
def test_a_title_with_no_variant_at_all_is_a_miss(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
node = {**ONE_PIECE, "title": {"romaji": None, "english": None, "native": None}}
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Media": node}})
|
||||
assert provider.get_by_id(21, "tv") is None
|
||||
|
||||
|
||||
# ---------- derived fields ----------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("country", "language"),
|
||||
[("JP", "ja"), ("KR", "ko"), ("CN", "zh"), ("TW", "zh"), ("US", None), (None, None)],
|
||||
)
|
||||
def test_country_of_origin_maps_to_a_language(
|
||||
monkeypatch: pytest.MonkeyPatch, country: Optional[str], language: Optional[str]
|
||||
) -> None:
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Media": {**ONE_PIECE, "countryOfOrigin": country}}})
|
||||
|
||||
result = provider.get_by_id(21, "tv")
|
||||
|
||||
assert result is not None
|
||||
assert result.original_language == language
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("format_", "kind"), [("MOVIE", "movie"), ("TV", "tv"), ("ONA", "tv"), ("SPECIAL", "tv"), (None, "tv")]
|
||||
)
|
||||
def test_format_maps_to_kind(monkeypatch: pytest.MonkeyPatch, format_: Optional[str], kind: str) -> None:
|
||||
provider, _ = _provider(monkeypatch, {"data": {"Media": {**ONE_PIECE, "format": format_}}})
|
||||
|
||||
result = provider.get_by_id(21, "tv")
|
||||
|
||||
assert result is not None
|
||||
assert result.kind == kind
|
||||
|
||||
|
||||
def test_the_provider_needs_no_api_key() -> None:
|
||||
assert AniListProvider().is_available() is True
|
||||
assert AniListProvider.REQUIRES_KEY is False
|
||||
assert AniListProvider.ID_KIND == "anilist"
|
||||
|
||||
|
||||
# ---------- cache round-trip ----------
|
||||
|
||||
|
||||
def test_a_cached_node_rebuilds_with_the_read_time_title_language(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""The raw node is cached; which title variant it yields follows the config at read time."""
|
||||
from unshackle.core.providers import _cached_to_result
|
||||
|
||||
node = {**ONE_PIECE, "title": {"romaji": "WAN PIISU", "english": "One Piece", "native": "ワンピース"}}
|
||||
|
||||
monkeypatch.setattr(config, "anilist_title_language", "native")
|
||||
result = _cached_to_result(node, "anilist", "tv")
|
||||
|
||||
assert result is not None
|
||||
assert result.title == "ワンピース"
|
||||
assert result.year == 1999
|
||||
assert result.kind == "tv"
|
||||
assert result.external_ids.anilist_id == 21
|
||||
assert result.original_language == "ja"
|
||||
@@ -112,3 +112,72 @@ def test_api_accepts_one_id(payload: dict) -> None:
|
||||
def test_api_still_reports_a_malformed_id_first() -> None:
|
||||
err = validate_download_parameters({"tmdb_id": "12345", "imdb_id": "tt1"})
|
||||
assert err and "positive integer" in err
|
||||
|
||||
|
||||
# ---------- --anilist stays outside the mutual exclusion ----------
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [21, "21", "mal:21"])
|
||||
def test_anilist_alone_passes_without_any_key(monkeypatch: pytest.MonkeyPatch, value: object) -> None:
|
||||
_with_keys(monkeypatch)
|
||||
assert validate_metadata_ids(None, None, None, value) is None
|
||||
|
||||
|
||||
def test_anilist_pairs_with_one_other_id(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_with_keys(monkeypatch)
|
||||
assert validate_metadata_ids(None, "tt1375666", None, 21) is None
|
||||
|
||||
|
||||
def test_anilist_does_not_excuse_two_western_ids(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_with_keys(monkeypatch)
|
||||
with pytest.raises(click.UsageError) as exc:
|
||||
validate_metadata_ids(27205, "tt1375666", None, 21)
|
||||
assert "cannot be used together" in str(exc.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["tt1375666", "anilist:21", "mal:", "abc", "-3"])
|
||||
def test_a_malformed_anilist_value_is_refused(monkeypatch: pytest.MonkeyPatch, value: str) -> None:
|
||||
_with_keys(monkeypatch)
|
||||
with pytest.raises(click.UsageError) as exc:
|
||||
validate_metadata_ids(None, None, None, value)
|
||||
assert "--anilist" in str(exc.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("payload", [{"anilist_id": 21}, {"anilist_id": "mal:21"}, {"anilist_id": "21"}])
|
||||
def test_api_accepts_an_anilist_id(payload: dict) -> None:
|
||||
assert validate_download_parameters(payload) is None
|
||||
|
||||
|
||||
def test_api_accepts_anilist_alongside_one_western_id() -> None:
|
||||
assert validate_download_parameters({"imdb_id": "tt1375666", "anilist_id": 21}) is None
|
||||
|
||||
|
||||
def test_api_still_refuses_two_western_ids_with_anilist() -> None:
|
||||
err = validate_download_parameters({"tmdb_id": 27205, "imdb_id": "tt1375666", "anilist_id": 21})
|
||||
assert err and "multiple external IDs" in err
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [0, -3, True, "mal:", "tt1375666", "anilist:21", 1.5])
|
||||
def test_api_refuses_a_malformed_anilist_id(value: object) -> None:
|
||||
err = validate_download_parameters({"anilist_id": value})
|
||||
assert err and "anilist_id" in err
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["0", "mal:0", "²", "21.5"])
|
||||
def test_a_non_positive_or_non_decimal_anilist_value_is_refused(monkeypatch: pytest.MonkeyPatch, value: str) -> None:
|
||||
_with_keys(monkeypatch)
|
||||
with pytest.raises(click.UsageError) as exc:
|
||||
validate_metadata_ids(None, None, None, value)
|
||||
assert "--anilist" in str(exc.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("payload", [{"anilist_id": "MAL:21"}, {"anilist_id": "007"}, {"anilist_id": " 21 "}])
|
||||
def test_api_accepts_what_the_cli_accepts(payload: dict) -> None:
|
||||
"""Both surfaces share parse_anilist_ref, so their accept sets cannot drift."""
|
||||
assert validate_download_parameters(payload) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["0", "mal:0", "²"])
|
||||
def test_api_refuses_what_the_cli_refuses(value: str) -> None:
|
||||
err = validate_download_parameters({"anilist_id": value})
|
||||
assert err and "anilist_id" in err
|
||||
|
||||
@@ -129,3 +129,131 @@ def test_flat_list_config_applies_to_both_kinds(monkeypatch: pytest.MonkeyPatch)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user