big upgrade UwU
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
|
||||
import aiohttp
|
||||
import pytest
|
||||
from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter, TimedOut
|
||||
|
||||
from sticker_downloader.errors import FetchError
|
||||
from sticker_downloader.retry import is_retriable, retry_delay, with_retries
|
||||
from tests.conftest import no_sleep
|
||||
|
||||
|
||||
async def test_returns_first_success() -> None:
|
||||
calls = 0
|
||||
|
||||
async def operation() -> str:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
return "ok"
|
||||
|
||||
assert await with_retries(operation, sleep=no_sleep) == "ok"
|
||||
assert calls == 1
|
||||
|
||||
|
||||
async def test_retries_transient_failure_then_succeeds() -> None:
|
||||
attempts = 0
|
||||
|
||||
async def operation() -> str:
|
||||
nonlocal attempts
|
||||
attempts += 1
|
||||
if attempts < 3:
|
||||
raise TimedOut
|
||||
return "ok"
|
||||
|
||||
assert await with_retries(operation, attempts=3, sleep=no_sleep) == "ok"
|
||||
assert attempts == 3
|
||||
|
||||
|
||||
async def test_raises_after_exhausting_attempts() -> None:
|
||||
attempts = 0
|
||||
|
||||
async def operation() -> str:
|
||||
nonlocal attempts
|
||||
attempts += 1
|
||||
raise NetworkError("down")
|
||||
|
||||
with pytest.raises(NetworkError):
|
||||
await with_retries(operation, attempts=2, sleep=no_sleep)
|
||||
assert attempts == 2
|
||||
|
||||
|
||||
async def test_does_not_retry_bad_request() -> None:
|
||||
"""BadRequest subclasses NetworkError in python-telegram-bot: it must not retry."""
|
||||
attempts = 0
|
||||
|
||||
async def operation() -> str:
|
||||
nonlocal attempts
|
||||
attempts += 1
|
||||
raise BadRequest("Stickerset_invalid")
|
||||
|
||||
with pytest.raises(BadRequest):
|
||||
await with_retries(operation, attempts=5, sleep=no_sleep)
|
||||
assert attempts == 1
|
||||
|
||||
|
||||
async def test_reports_each_retry() -> None:
|
||||
seen: list[tuple[str, int, float]] = []
|
||||
|
||||
async def operation() -> str:
|
||||
raise TimedOut
|
||||
|
||||
def on_retry(error: BaseException, attempt: int, delay: float) -> None:
|
||||
seen.append((type(error).__name__, attempt, delay))
|
||||
|
||||
with pytest.raises(TimedOut):
|
||||
await with_retries(
|
||||
operation, attempts=3, base_delay=1.0, on_retry=on_retry, sleep=no_sleep
|
||||
)
|
||||
assert seen == [("TimedOut", 1, 1.0), ("TimedOut", 2, 2.0)]
|
||||
|
||||
|
||||
async def test_awaits_the_requested_delays() -> None:
|
||||
slept: list[float] = []
|
||||
|
||||
async def record(delay: float) -> None:
|
||||
slept.append(delay)
|
||||
|
||||
async def operation() -> str:
|
||||
raise TimedOut
|
||||
|
||||
with pytest.raises(TimedOut):
|
||||
await with_retries(operation, attempts=3, base_delay=0.5, sleep=record)
|
||||
assert slept == [0.5, 1.0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("error", "expected"),
|
||||
[
|
||||
(TimedOut(), True),
|
||||
(NetworkError("x"), True),
|
||||
(aiohttp.ClientError(), True),
|
||||
(asyncio.TimeoutError(), True),
|
||||
(RetryAfter(3), True),
|
||||
(FetchError("u", 500), True),
|
||||
(FetchError("u", 429), True),
|
||||
(FetchError("u", 404), False),
|
||||
(FetchError("u", 403), False),
|
||||
(BadRequest("nope"), False),
|
||||
(InvalidToken(), False),
|
||||
(ValueError("nope"), False),
|
||||
],
|
||||
)
|
||||
def test_retriability_classification(error: BaseException, expected: bool) -> None:
|
||||
assert is_retriable(error) is expected
|
||||
|
||||
|
||||
def test_retry_after_delay_uses_telegram_hint() -> None:
|
||||
assert retry_delay(RetryAfter(7), attempt=1, base_delay=0.5) == pytest.approx(7.0)
|
||||
|
||||
|
||||
def test_retry_after_delay_accepts_a_timedelta() -> None:
|
||||
"""python-telegram-bot will switch ``retry_after`` to a timedelta in a future
|
||||
major version; handle both shapes."""
|
||||
|
||||
class FutureRetryAfter(RetryAfter):
|
||||
@property
|
||||
def retry_after(self) -> timedelta: # type: ignore[override]
|
||||
return timedelta(seconds=12)
|
||||
|
||||
error = FutureRetryAfter(1)
|
||||
assert retry_delay(error, attempt=1, base_delay=0.5) == pytest.approx(12.0)
|
||||
|
||||
|
||||
def test_backoff_is_exponential() -> None:
|
||||
delays = [retry_delay(TimedOut(), attempt=n, base_delay=0.5) for n in (1, 2, 3)]
|
||||
assert delays == [0.5, 1.0, 2.0]
|
||||
Reference in New Issue
Block a user