mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""RnetSession.get_sleep_time must survive hostile or malformed Retry-After headers."""
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
from email.utils import format_datetime
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from unshackle.core.providers.imdb import IMDBProvider
|
|
from unshackle.core.service import Service
|
|
from unshackle.core.session import MAX_BACKOFF, RnetSession
|
|
|
|
|
|
@pytest.fixture
|
|
def session():
|
|
return RnetSession()
|
|
|
|
|
|
def response(retry_after=None):
|
|
headers = {} if retry_after is None else {"Retry-After": retry_after}
|
|
return SimpleNamespace(headers=headers)
|
|
|
|
|
|
def test_numeric_retry_after_passes_through(session):
|
|
assert session.get_sleep_time(response("3"), 1) == 3.0
|
|
|
|
|
|
def test_huge_numeric_retry_after_is_clamped(session):
|
|
assert session.get_sleep_time(response("86400"), 1) == session.max_backoff
|
|
|
|
|
|
def test_huge_numeric_retry_after_respects_session_cap():
|
|
session = RnetSession(max_backoff=5.0)
|
|
assert session.get_sleep_time(response("86400"), 1) == 5.0
|
|
|
|
|
|
def test_http_date_retry_after_honoured(session):
|
|
when = format_datetime(datetime.now(timezone.utc) + timedelta(seconds=10), usegmt=True)
|
|
assert 5 <= session.get_sleep_time(response(when), 1) <= 10
|
|
|
|
|
|
def test_naive_http_date_does_not_raise(session):
|
|
# "-0000" makes parsedate_to_datetime return a naive datetime
|
|
when = format_datetime(datetime.now(timezone.utc) + timedelta(seconds=10)).replace("+0000", "-0000")
|
|
assert 5 <= session.get_sleep_time(response(when), 1) <= 10
|
|
|
|
|
|
def test_past_http_date_yields_no_sleep(session):
|
|
when = format_datetime(datetime.now(timezone.utc) - timedelta(seconds=30), usegmt=True)
|
|
assert session.get_sleep_time(response(when), 1) <= 0
|
|
|
|
|
|
def test_malformed_retry_after_falls_back_to_backoff(session):
|
|
value = session.get_sleep_time(response("not-a-date"), 1)
|
|
assert 0 < value <= session.max_backoff
|
|
|
|
|
|
def test_empty_retry_after_falls_back_to_backoff(session):
|
|
value = session.get_sleep_time(response(""), 1)
|
|
assert 0 < value <= session.max_backoff
|
|
|
|
|
|
def test_exponential_backoff_without_retry_after(session):
|
|
assert session.get_sleep_time(response(), 0) == 0.0
|
|
base = session.backoff_factor * 4
|
|
assert base * 0.9 <= session.get_sleep_time(response(), 3) <= base * 1.1
|
|
assert session.get_sleep_time(response(), 30) == session.max_backoff
|
|
|
|
|
|
def test_no_response_still_backs_off(session):
|
|
assert session.get_sleep_time(None, 30) == session.max_backoff
|
|
|
|
|
|
def test_service_session_caps_retry_after():
|
|
retry = Service.get_session().get_adapter("https://example.invalid/").max_retries
|
|
assert retry.respect_retry_after_header
|
|
assert retry.retry_after_max == MAX_BACKOFF
|
|
|
|
|
|
def test_provider_session_gives_up_on_a_rate_limit():
|
|
retry = IMDBProvider().session.get_adapter("https://example.invalid/").max_retries
|
|
assert not retry.respect_retry_after_header
|
|
assert 429 not in retry.status_forcelist
|