test(router): fake the upstream with respx in the retry policy attempt test

The test-quality gate rejects patching litellm.acompletion, and faking the
HTTP boundary is the stronger test anyway: the 503, 500 and 502 responses now
travel through the real OpenAI SDK and exception mapping before the router
decides how many times to retry. Adds a case showing that a 503 key does not
govern a 502.
This commit is contained in:
ryan-crabbe-berri 2026-09-04 16:23:08 -07:00
parent b29f9a94bc
commit 541ab50c04

View file

@ -12,6 +12,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import openai
import pytest
import respx
@ -567,7 +568,6 @@ async def test_async_router_acancel_batch_does_not_fall_back_across_model_groups
model string, and the fallback provider is then asked to cancel a batch it never
issued, which can only answer not-found. The router re-raises the owner's error after
that wasted round trip, so the pin's observable is the foreign call never happening."""
import respx
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
router = litellm.Router(
@ -716,7 +716,6 @@ async def test_async_router_acreate_file_litellm_proxy_sends_target_model_names_
from io import BytesIO
import httpx
import respx
jsonl_file = BytesIO(
json.dumps({"body": {"model": "chained-batch", "messages": [{"role": "user", "content": "hi"}]}}).encode(
@ -12897,31 +12896,45 @@ async def test_prompt_management_factory_marks_injection_for_every_deployment(mo
@pytest.mark.asyncio
@pytest.mark.parametrize(
"retry_policy,error_type,expected_calls",
"retry_policy,upstream_status,error_type,expected_upstream_calls",
[
({"ServiceUnavailableErrorRetries": 0}, litellm.ServiceUnavailableError, 1),
({"ServiceUnavailableErrorRetries": 1}, litellm.ServiceUnavailableError, 2),
({"InternalServerErrorRetries": 0}, litellm.InternalServerError, 1),
({"DefaultRetries": 0}, litellm.BadGatewayError, 1),
({"DefaultRetries": 0, "ServiceUnavailableErrorRetries": 1}, litellm.ServiceUnavailableError, 2),
({"ServiceUnavailableErrorRetries": 0}, 503, litellm.ServiceUnavailableError, 1),
({"ServiceUnavailableErrorRetries": 1}, 503, litellm.ServiceUnavailableError, 2),
({"InternalServerErrorRetries": 0}, 500, litellm.InternalServerError, 1),
({"DefaultRetries": 0}, 502, litellm.BadGatewayError, 1),
({"DefaultRetries": 0, "ServiceUnavailableErrorRetries": 1}, 503, litellm.ServiceUnavailableError, 2),
({"ServiceUnavailableErrorRetries": 0}, 502, litellm.BadGatewayError, 3),
],
)
async def test_router_retry_policy_controls_attempt_count(retry_policy, error_type, expected_calls):
async def test_router_retry_policy_controls_upstream_attempt_count(
monkeypatch: pytest.MonkeyPatch, retry_policy, upstream_status, error_type, expected_upstream_calls
):
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
router = litellm.Router(
model_list=[
{
"model_name": "gpt-5.6",
"litellm_params": {"model": "openai/gpt-5.6", "api_key": "fake-key"},
"litellm_params": {
"model": "openai/gpt-5.6",
"api_key": "sk-fake",
"api_base": "https://retry-policy.local/v1",
},
}
],
num_retries=2,
retry_policy=retry_policy,
disable_cooldowns=True,
)
error = error_type(message="model is down", llm_provider="openai", model="gpt-5.6")
with patch.object(litellm, "acompletion", AsyncMock(side_effect=error)) as mock_acompletion:
with respx.mock(assert_all_called=True) as respx_mock:
upstream = respx_mock.post("https://retry-policy.local/v1/chat/completions").mock(
return_value=httpx.Response(
upstream_status,
headers={"retry-after": "0"},
json={"error": {"message": "model is down", "type": "server_error"}},
)
)
with pytest.raises(error_type):
await router.acompletion(model="gpt-5.6", messages=[{"role": "user", "content": "hi"}])
assert mock_acompletion.call_count == expected_calls
assert upstream.call_count == expected_upstream_calls