Ctrl K
ComunidadGuías…

backend/tests/test_animetosho_identity.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""GH #210: TYBW episode 21 must not become original Bleach absolute 21."""

from unittest.mock import MagicMock

import pytest

from providers.animetosho import AnimeToshoProvider
from providers.animetosho_matching import match_entry, match_release
from providers.base import VideoQuery


@pytest.fixture
def query():
    return VideoQuery(
        series_title="Bleach", season=2, episode=1, absolute_episode=21, languages=["en"]
    )


def _file(name, attach_id=1):
    return {
        "filename": name,
        "attachments": [
            {"id": attach_id, "type": "subtitle", "info": {"lang": "eng", "codec": "ASS"}}
        ],
    }


@pytest.mark.parametrize(
    "title",
    [
        "[LostYears] Bleach Thousand-Year Blood War - S17E21 (WEB 1080p x264 AAC) [Dual Audio]",
        "Bleach S17E21 1080p WEB H.264 AAC -Tsundere-Raws (DSNP).mkv",
        "[ZigZag] BLEACH: Thousand-Year Blood War S02E08 [1080p] (Bleach S17E21)",
        "[Pro Bono] Bleach Sennen Kessen-hen - 21.5",
        "[Group] Bleach Thousand-Year Blood War - 21 [1080p]",
        "[Group] Bleach - 21.5 [1080p]",
        "[Group] Bleach - 387 [1080p]",
        "[Group] Bleach S02E21 [1080p]",
        "[DKB] Bleach - Sennen Kessen-hen - 21 [1080p][HEVC x265 10bit][Multi-Subs][weekly]",
        "[Nokiya-Fansubs] Bleach - Sennen Kessen-hen - 21 | Thousand-Year Blood War [1080p AMZN WEB-DL]",
    ],
)
def test_rejects_live_sequel_titles_and_wrong_numbering(query, title):
    assert match_entry({"title": title}, query) is None


@pytest.mark.parametrize(
    "title",
    [
        "[Group] Bleach - 21 [1080p].mkv",
        "[Group] Bleach - 21v2 [1080p].mkv",
        "Bleach.S02E01.1080p.mkv",
        "BLEACH - 0021.mkv",
    ],
)
def test_accepts_original_episode_in_absolute_or_season_order(query, title):
    assert {"series", "episode"} <= match_entry({"title": title}, query)


def test_even_a_matching_feed_id_cannot_override_a_wrong_season(query):
    # The live .xyz feed really marks some S17E21 releases as AniDB 2369.
    query.anidb_id = 2369
    assert match_entry({"title": "Bleach S17E21 1080p.mkv", "anidb_aid": 2369}, query) is None


@pytest.mark.parametrize("field,value", [("anidb_aid", 15449), ("anidb_eid", 260810)])
def test_conflicting_feed_ids_are_rejected(query, field, value):
    query.anidb_id = 2369
    query.anidb_episode_id = 30266
    assert match_entry({"title": "Bleach - 21 [1080p]", field: value}, query) is None


def test_missing_ids_do_not_reject_a_proven_title_match(query):
    query.anidb_id = 2369
    assert {"series", "episode"} <= match_entry({"title": "Bleach - 21 [1080p]"}, query)


def test_title_substrings_do_not_establish_series_identity(query):
    assert match_release("[Group] Bleach Something Else - 21 [1080p]", query) is None


def test_pack_only_returns_the_attachment_for_the_requested_file(query):
    provider = AnimeToshoProvider()
    provider._fetch_torrent_detail = MagicMock(
        return_value={
            "files": [
                _file("Bleach - 20 [1080p].mkv", 20),
                _file("Bleach - 21 [1080p].mkv", 21),
                _file("Bleach - 22 [1080p].mkv", 22),
                _file("Bleach Thousand-Year Blood War - 21 [1080p].mkv", 387),
                _file("extras.mkv", 99),
            ]
        }
    )
    results = provider._process_entry(
        {"id": 1, "title": "Bleach - 01-366 [1080p]", "num_files": 5}, query
    )
    assert [r.subtitle_id for r in results] == ["1:21"]
    assert {"series", "episode"} <= results[0].matches


def test_single_file_can_inherit_an_unambiguous_episode(query):
    provider = AnimeToshoProvider()
    provider._fetch_torrent_detail = MagicMock(return_value={"files": [_file("")]})
    results = provider._process_entry(
        {"id": 1, "title": "Bleach - 21 [1080p]", "num_files": 1}, query
    )
    assert len(results) == 1


def test_unnumbered_pack_files_cannot_inherit_the_feed_episode(query):
    provider = AnimeToshoProvider()
    provider._fetch_torrent_detail = MagicMock(return_value={"files": [_file(""), _file("", 2)]})
    assert (
        provider._process_entry({"id": 1, "title": "Bleach - 21 [1080p]", "num_files": 2}, query)
        == []
    )


def test_wrong_entries_do_not_exhaust_the_eight_detail_requests(query):
    provider = AnimeToshoProvider()
    provider.session = MagicMock()
    response = provider.session.get.return_value
    response.status_code = 200
    response.json.return_value = [
        {"id": n, "title": "Bleach S17E21 1080p.mkv", "num_files": 1} for n in range(10)
    ] + [{"id": 20, "title": "Bleach S02E01 1080p.mkv", "num_files": 1}]
    provider._fetch_torrent_detail = MagicMock(return_value={"files": [_file("Bleach S02E01.mkv")]})
    results = provider.search(query)
    assert len(results) == 1
    provider._fetch_torrent_detail.assert_called_once_with(20)


@pytest.mark.parametrize("episode_id", [None, 30266])
def test_api_eid_is_an_identity_never_the_absolute_number(query, episode_id):
    query.anidb_id = 2369
    query.anidb_episode_id = episode_id
    provider = AnimeToshoProvider()
    provider.session = MagicMock()
    provider.session.get.return_value.status_code = 200
    provider.session.get.return_value.json.return_value = []
    provider.search(query)
    calls = [c.kwargs["params"] for c in provider.session.get.call_args_list]
    assert calls[0]["q"] == "Bleach 21"
    assert calls[0].get("eid") == episode_id
    if episode_id is None:
        assert "aid" not in calls[0]
        # Nothing found without the episode identity: the season is asked next.
        assert [c["q"] for c in calls] == ["Bleach 21", "Bleach S2"]
        assert "aid" not in calls[1]
    else:
        assert len(calls) == 1


def _feed(*pages):
    """A session whose feed requests answer ``pages`` in turn."""
    session = MagicMock()
    responses = []
    for page in pages:
        response = MagicMock(status_code=200)
        response.json.return_value = page
        responses.append(response)
    session.get.side_effect = responses
    return session


def test_a_season_pack_crowded_out_by_the_airing_season_is_found_by_season():
    # Prod 2026-11: "Mushoku Tensei: Jobless Reincarnation 05" answered with
    # season 3's episode 5 and batches; season 1's BD packs never came up.
    query = VideoQuery(
        series_title="Mushoku Tensei: Jobless Reincarnation",
        season=1,
        episode=5,
        languages=["en"],
        series_aliases=[("Mushoku Tensei", None, None)],
    )
    provider = AnimeToshoProvider()
    provider.session = _feed(
        [{"id": 1, "title": "[SubsPlease] Mushoku Tensei S3 - 05 (1080p)", "num_files": 1}],
        [{"id": 2, "title": "[Fuechslein] Mushoku Tensei - S01 (BD 1080p)", "num_files": 23}],
    )
    provider._fetch_torrent_detail = MagicMock(
        return_value={"files": [_file("[Fuechslein] Mushoku Tensei - S01E05 (BD 1080p).mkv")]}
    )

    results = provider.search(query)

    assert len(results) == 1
    provider._fetch_torrent_detail.assert_called_once_with(2)
    assert provider.session.get.call_args.kwargs["params"]["q"] == (
        "Mushoku Tensei: Jobless Reincarnation S1"
    )


def test_a_found_episode_asks_no_second_time(query):
    provider = AnimeToshoProvider()
    provider.session = _feed([{"id": 20, "title": "Bleach S02E01 1080p.mkv", "num_files": 1}])
    provider._fetch_torrent_detail = MagicMock(return_value={"files": [_file("Bleach S02E01.mkv")]})

    assert len(provider.search(query)) == 1
    assert provider.session.get.call_count == 1


def test_an_alias_for_every_season_names_the_series():
    query = VideoQuery(
        series_title="Mushoku Tensei: Jobless Reincarnation",
        season=1,
        episode=5,
        series_aliases=[("Mushoku Tensei", None, None)],
    )
    assert match_entry({"title": "[Fuechslein] Mushoku Tensei - S01 (BD 1080p)"}, query)
    assert match_entry({"title": "[Fuechslein] Mushoku Tensei - S02 (BD 1080p)"}, query) is None
    # An alias Sonarr scopes to another season is not a name for this one.
    scoped = VideoQuery(
        series_title="Mushoku Tensei: Jobless Reincarnation",
        season=1,
        episode=5,
        series_aliases=[("Mushoku Tensei", 2, None)],
    )
    assert match_entry({"title": "[Fuechslein] Mushoku Tensei - S01 (BD 1080p)"}, scoped) is None


def test_movie_matching_still_uses_title_and_year():
    query = VideoQuery(title="Akira", year=1988)
    assert "title" in match_entry({"title": "Akira (1988) [1080p]"}, query)
    assert match_entry({"title": "Akira (2016) [1080p]"}, query) is None


def test_a_full_series_name_with_a_subtitle_still_matches_live_release_names():
    query = VideoQuery(series_title="Frieren: Beyond Journey's End", season=1, episode=1)
    entry = {"title": "[BlackRabbit] Frieren - Beyond Journey's End (2023) - S01 v2 [Bluray-1080p]"}
    assert {"series", "season"} <= match_entry(entry, query)
    filename = (
        "Frieren - Beyond Journey's End (2023) - S01E01 - The Journeys End [Bluray-1080p].mkv"
    )
    assert {"series", "season", "episode"} <= match_release(filename, query)


def test_four_digit_absolute_episodes_are_not_compact_season_numbers():
    query = VideoQuery(series_title="One Piece", season=21, episode=189, absolute_episode=1080)
    assert {"series", "episode"} <= match_release("[Group] One Piece - 1080 [1080p].mkv", query)