from __future__ import annotations import asyncio import httpx import pytest from cerbero_mcp.common.http import async_client, call_with_retry def test_async_client_uses_retry_transport(): c = async_client(retries=5) assert isinstance(c._transport, httpx.AsyncHTTPTransport) # internal _retries on transport assert c._transport._pool._retries == 5 @pytest.mark.asyncio async def test_call_with_retry_succeeds_first_try(): calls = 0 async def fn(): nonlocal calls calls += 1 return "ok" result = await call_with_retry(fn) assert result == "ok" assert calls == 1 @pytest.mark.asyncio async def test_call_with_retry_recovers_after_transient(monkeypatch): monkeypatch.setattr(asyncio, "sleep", asyncio.coroutine(lambda *_: None) if False else _no_sleep) calls = 0 async def fn(): nonlocal calls calls += 1 if calls < 3: raise httpx.ConnectError("boom") return "ok" result = await call_with_retry(fn, max_attempts=5, base_delay=0.0) assert result == "ok" assert calls == 3 async def _no_sleep(_): return None @pytest.mark.asyncio async def test_call_with_retry_gives_up_after_max(): calls = 0 async def fn(): nonlocal calls calls += 1 raise httpx.TimeoutException("slow") with pytest.raises(httpx.TimeoutException): await call_with_retry(fn, max_attempts=3, base_delay=0.0) assert calls == 3 @pytest.mark.asyncio async def test_call_with_retry_does_not_catch_unexpected(): async def fn(): raise ValueError("not transient") with pytest.raises(ValueError): await call_with_retry(fn, max_attempts=5, base_delay=0.0)