Ctrl K
ComunidadGuías…

backend/tests/test_download_rate_limit_wait.py

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

Archivo original ↗Preguntar a KiBot
"""Our own rate limiter must not turn a download into a provider failure.

Prod, September 2026: 309 ``download_failed`` attempts for opensubtitles with
zero quota or API errors behind them. The shared 40-per-10s window was full of
searches, ``download_subtitle`` returned None on the limiter's refusal, and the
caller booked that as a failed download and a provider stats failure.
"""

from unittest.mock import MagicMock, patch

import pytest

import providers.download_manager as dm
from providers.download_manager import (
    DownloadRateLimitedError,
    download_subtitle,
    search_and_download_best,
)


@pytest.fixture
def short_wait(monkeypatch):
    monkeypatch.setattr(dm, "RATE_LIMIT_MAX_WAIT_S", 0.05)
    monkeypatch.setattr(dm, "_RATE_LIMIT_POLL_S", 0.005)


def _result(provider_name="opensubtitles", subtitle_id="s1", score=90):
    return MagicMock(provider_name=provider_name, subtitle_id=subtitle_id, score=score)


class TestDownloadWaitsForSlot:
    def test_waits_for_a_free_slot_then_downloads(self, monkeypatch):
        monkeypatch.setattr(dm, "RATE_LIMIT_MAX_WAIT_S", 5.0)
        monkeypatch.setattr(dm, "_RATE_LIMIT_POLL_S", 0.005)
        provider = MagicMock()
        provider.download.return_value = b"content"
        checker = MagicMock(side_effect=[False, False, True])

        ret = download_subtitle({"opensubtitles": provider}, {}, checker, _result())

        assert ret == b"content"
        assert checker.call_count == 3
        provider.download.assert_called_once()

    def test_still_refused_raises_when_asked(self, short_wait):
        provider = MagicMock()

        with pytest.raises(DownloadRateLimitedError):
            download_subtitle(
                {"opensubtitles": provider},
                {},
                MagicMock(return_value=False),
                _result(),
                raise_skips=True,
            )
        provider.download.assert_not_called()

    def test_still_refused_returns_none_by_default(self, short_wait):
        provider = MagicMock()
        breaker = MagicMock()
        breaker.allow_request.return_value = True

        ret = download_subtitle(
            {"opensubtitles": provider},
            {"opensubtitles": breaker},
            MagicMock(return_value=False),
            _result(),
        )

        assert ret is None
        provider.download.assert_not_called()
        breaker.record_failure.assert_not_called()

    def test_stop_request_ends_the_wait(self, monkeypatch):
        monkeypatch.setattr(dm, "RATE_LIMIT_MAX_WAIT_S", 30.0)
        monkeypatch.setattr(dm, "_RATE_LIMIT_POLL_S", 0.005)
        checker = MagicMock(return_value=False)

        with (
            patch("services.scheduler.cancellation.abort_requested", return_value=True),
            pytest.raises(DownloadRateLimitedError),
        ):
            download_subtitle(
                {"opensubtitles": MagicMock()},
                {},
                checker,
                _result(),
                raise_skips=True,
            )
        assert checker.call_count == 1


class TestSearchAndDownloadBestRateLimited:
    def test_rate_limited_is_not_a_provider_failure(self):
        limited = _result("opensubtitles", "os1", 95)
        other = _result("jimaku", "j1", 80)
        download_fn = MagicMock(side_effect=[DownloadRateLimitedError("full"), b"content"])
        stats_fn = MagicMock()

        with (
            patch("providers.reranker.apply_auto_reranking"),
            patch.object(dm, "decision_log") as dlog,
        ):
            ret = search_and_download_best(
                MagicMock(return_value=[limited, other]), download_fn, stats_fn, "query"
            )

        assert ret is other
        stats_fn.assert_called_once_with("jimaku", success=True, score=80)
        statuses = [c.args[2] for c in dlog.download_attempt.call_args_list]
        assert statuses == ["rate_limited", "selected"]

    def test_further_results_of_a_limited_provider_are_not_retried(self):
        first = _result("opensubtitles", "os1", 95)
        second = _result("opensubtitles", "os2", 90)
        download_fn = MagicMock(side_effect=DownloadRateLimitedError("full"))
        stats_fn = MagicMock()

        with patch.object(dm, "decision_log") as dlog:
            ret = search_and_download_best(
                MagicMock(return_value=[first, second]), download_fn, stats_fn, "query"
            )

        assert ret is None
        assert download_fn.call_count == 1
        stats_fn.assert_not_called()
        statuses = [c.args[2] for c in dlog.download_attempt.call_args_list]
        assert statuses == ["rate_limited", "rate_limited"]


class TestProviderManagerWiring:
    def test_manager_search_and_download_books_rate_limited(self, short_wait):
        from providers import ProviderManager

        manager = ProviderManager.__new__(ProviderManager)
        provider = MagicMock()
        manager._providers = {"opensubtitles": provider}
        manager._circuit_breakers = {}
        manager._check_rate_limit = MagicMock(return_value=False)
        manager.search_with_fallback = MagicMock(return_value=[_result()])

        with (
            patch("db.providers.update_provider_stats") as stats,
            patch.object(dm, "decision_log") as dlog,
        ):
            ret = manager.search_and_download_best(MagicMock())

        assert ret is None
        provider.download.assert_not_called()
        stats.assert_not_called()
        statuses = [c.args[2] for c in dlog.download_attempt.call_args_list]
        assert statuses == ["rate_limited"]