Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/adcp/decisioning/upstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,20 @@ async def _request(
)
if response.status_code == 204 or not response.content:
return {}
parsed: Any = response.json()
try:
parsed: Any = response.json()
except ValueError as exc:
# Server returned a successful status with a non-JSON body (e.g.
# a proxy/CDN HTML error page). Project to SERVICE_UNAVAILABLE so
# adopters get a typed AdcpError rather than a raw JSONDecodeError.
raise AdcpError(
"SERVICE_UNAVAILABLE",
message=(
f"upstream {method} {path} returned non-JSON body "
f"(status {response.status_code}): {exc}"
),
recovery="transient",
) from exc
return parsed

async def get(
Expand Down
16 changes: 16 additions & 0 deletions tests/test_upstream_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,22 @@ async def test_async_context_manager_closes_client() -> None:
assert client._client is None # type: ignore[union-attr]


@respx.mock
async def test_200_with_malformed_json_raises_service_unavailable() -> None:
"""A 2xx response with non-JSON body (e.g. CDN/proxy HTML page) must
surface as SERVICE_UNAVAILABLE, not a raw JSONDecodeError."""
respx.get(f"{BASE}/items").mock(
return_value=httpx.Response(200, content=b"<html>bad gateway</html>")
)
client = create_upstream_http_client(BASE)
with pytest.raises(AdcpError) as exc_info:
await client.get("/items")
assert exc_info.value.code == "SERVICE_UNAVAILABLE"
assert exc_info.value.recovery == "transient"
assert isinstance(exc_info.value.__cause__, json.JSONDecodeError)
await client.aclose()


@respx.mock
async def test_pool_reused_across_calls() -> None:
respx.get(f"{BASE}/a").mock(return_value=httpx.Response(200, json={}))
Expand Down
Loading