diff --git a/packages/http/httpx/kiota_http/httpx_request_adapter.py b/packages/http/httpx/kiota_http/httpx_request_adapter.py index 23f1dfaf..a07c8757 100644 --- a/packages/http/httpx/kiota_http/httpx_request_adapter.py +++ b/packages/http/httpx/kiota_http/httpx_request_adapter.py @@ -549,14 +549,24 @@ async def throw_failed_responses( raise exc _throw_failed_resp_span.set_attribute("status_message", "received_error_response") - error = await self._get_error_from_response( - response, - error_map, - response_status_code_str, - response_status_code, - attribute_span, - _throw_failed_resp_span, - ) + try: + error = await self._get_error_from_response( + response, + error_map, + response_status_code_str, + response_status_code, + attribute_span, + _throw_failed_resp_span, + ) + except Exception as ex: # a body no parse node can read, e.g. an HTML gateway page + attribute_span.record_exception(ex) + exc = APIError( + "The server returned an unexpected status code and the error body could not" + " be parsed", + response_status_code, + response_headers, # type: ignore + ) + raise exc from ex if isinstance(error, APIError): error.response_headers = response_headers # type: ignore error.response_status_code = response_status_code diff --git a/packages/http/httpx/tests/test_httpx_request_adapter.py b/packages/http/httpx/tests/test_httpx_request_adapter.py index ab3e627c..a2e50852 100644 --- a/packages/http/httpx/tests/test_httpx_request_adapter.py +++ b/packages/http/httpx/tests/test_httpx_request_adapter.py @@ -1,3 +1,4 @@ +import asyncio from unittest.mock import AsyncMock, Mock, call, patch from urllib.parse import unquote @@ -217,6 +218,44 @@ async def test_throw_failed_responses_5XX( assert str(e.value.message) == "Custom Internal Server Error" +@pytest.mark.asyncio +async def test_throw_failed_responses_unparsable_error_body( + request_adapter, mock_apierror_XXX_map, mock_otel_span +): + resp = httpx.Response( + status_code=502, + headers={"Content-Type": "text/html"}, + content=b"502 Bad Gateway", + ) + assert resp.status_code == 502 + content_type = request_adapter.get_response_content_type(resp) + assert content_type == "text/html" + + attribute_span = Mock() + with pytest.raises(APIError) as e: + await request_adapter.throw_failed_responses( + resp, mock_apierror_XXX_map, mock_otel_span, attribute_span + ) + assert ("The server returned an unexpected status code and the error body could not be parsed" + ) in str(e.value.message) + assert "text/html" not in str(e.value.message) + assert e.value.response_status_code == 502 + assert "text/html" in str(e.value.__cause__) + attribute_span.record_exception.assert_called_once_with(e.value.__cause__) + + +@pytest.mark.asyncio +async def test_throw_failed_responses_lets_cancellation_through( + request_adapter, mock_apierror_XXX_map, mock_otel_span +): + resp = httpx.Response(status_code=502, headers={"Content-Type": "text/html"}, content=b"") + request_adapter._get_error_from_response = AsyncMock(side_effect=asyncio.CancelledError()) + + with pytest.raises(asyncio.CancelledError): + span = mock_otel_span + await request_adapter.throw_failed_responses(resp, mock_apierror_XXX_map, span, span) + + @pytest.mark.asyncio async def test_throw_failed_responses_XXX( request_adapter, mock_apierror_XXX_map, mock_error_object, mock_otel_span