"""Exercises AiohttpFetcher against a real local HTTP server.""" from __future__ import annotations from collections.abc import AsyncIterator import aiohttp import pytest import pytest_asyncio from aiohttp import web from sticker_downloader.errors import FetchError from sticker_downloader.fetcher import AiohttpFetcher, Fetcher async def _handle(request: web.Request) -> web.Response: status = int(request.match_info["status"]) if status == 200: return web.Response(body=b"file-bytes", content_type="image/webp") return web.Response(status=status, text="nope") @pytest_asyncio.fixture async def server() -> AsyncIterator[str]: app = web.Application() app.router.add_get("/{status}", _handle) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, "127.0.0.1", 0) await site.start() port = runner.addresses[0][1] try: yield f"http://127.0.0.1:{port}" finally: await runner.cleanup() @pytest_asyncio.fixture async def session() -> AsyncIterator[aiohttp.ClientSession]: async with aiohttp.ClientSession() as client: yield client async def test_returns_the_body_on_success(server: str, session) -> None: fetcher = AiohttpFetcher(session) assert await fetcher.fetch(f"{server}/200") == b"file-bytes" @pytest.mark.parametrize("status", [404, 429, 500, 503]) async def test_raises_fetch_error_with_the_status( server: str, session, status: int ) -> None: fetcher = AiohttpFetcher(session) with pytest.raises(FetchError) as excinfo: await fetcher.fetch(f"{server}/{status}") assert excinfo.value.status == status assert str(status) in str(excinfo.value) async def test_only_server_side_statuses_are_retriable(server: str, session) -> None: fetcher = AiohttpFetcher(session) with pytest.raises(FetchError) as not_found: await fetcher.fetch(f"{server}/404") with pytest.raises(FetchError) as rate_limited: await fetcher.fetch(f"{server}/429") assert not_found.value.retriable is False assert rate_limited.value.retriable is True async def test_connection_failures_surface_as_client_errors(session) -> None: fetcher = AiohttpFetcher(session) # Port 1 is reserved and refuses connections. with pytest.raises(aiohttp.ClientError): await fetcher.fetch("http://127.0.0.1:1/200") def test_aiohttp_fetcher_satisfies_the_protocol(session) -> None: assert isinstance(AiohttpFetcher(session), Fetcher)