From 091beb60caff00ac970f3e44ea26fa5eace589d5 Mon Sep 17 00:00:00 2001 From: RishavTiwari25 Date: Mon, 26 Oct 2026 14:41:33 +0530 Subject: [PATCH] fix(response): keep status code and url when an error body is not JSON verify_response decoded every error body as JSON. When it was not JSON (an HTML 502/503 page from a load balancer or proxy, or an empty body), the decoding error escaped instead: a LagoApiError with status_code 500, url None and the orjson message as detail, so callers could not tell a 502 from a 404 or see which request failed. The raw body is now kept as the error's response and the real status code and url are reported. --- lago_python_client/services/response.py | 8 +++++- tests/test_response_services.py | 37 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lago_python_client/services/response.py b/lago_python_client/services/response.py index a699b5ba..1e6d9720 100644 --- a/lago_python_client/services/response.py +++ b/lago_python_client/services/response.py @@ -54,7 +54,13 @@ def verify_response(response: Response) -> Optional[Response]: headers=response.headers, ) - response_data: Any = from_json(response) # type: ignore + response_data: Any + try: + response_data = from_json(response) # type: ignore + except LagoApiError: + # The error body is not JSON, e.g. an HTML 502 page from a proxy or an + # empty body: keep the raw body and still report the real status code. + response_data = response.text or None raise LagoApiError( status_code=response.status_code, url=str(response.request.url), diff --git a/tests/test_response_services.py b/tests/test_response_services.py index 26e47351..d25ead4b 100644 --- a/tests/test_response_services.py +++ b/tests/test_response_services.py @@ -85,6 +85,43 @@ def test_verify_response(): verify_response(response404) +@pytest.mark.parametrize( + ("status_code", "content"), + [ + (502, b"

502 Bad Gateway

"), + (503, b"upstream connect error"), + (404, b""), + ], +) +def test_verify_response_with_non_json_error_body(status_code, content): + """Keep the real status code and url when an error body is not JSON.""" + # Given an error response whose body is not JSON (e.g. from a proxy) + url = "https://api.getlago.com/api/v1/customers/123" + response = Response(content=content, status_code=status_code, request=Request(method="GET", url=url)) + + # When service is applied + # Then the raised error carries the response status, url and raw body + with pytest.raises(LagoApiError) as exc_info: + verify_response(response) + assert exc_info.value.status_code == status_code + assert exc_info.value.url == url + assert exc_info.value.response == (content.decode() or None) + + +def test_verify_response_with_json_error_body(): + """Keep the decoded body of a JSON error response.""" + # Given an error response with a JSON body + content = b'{"status":422,"error":"Unprocessable Entity","code":"validation_errors"}' + response = Response(content=content, status_code=422, request=Request(method="POST", url="")) + + # When service is applied + # Then the raised error carries the decoded body + with pytest.raises(LagoApiError) as exc_info: + verify_response(response) + assert exc_info.value.status_code == 422 + assert exc_info.value.response == {"status": 422, "error": "Unprocessable Entity", "code": "validation_errors"} + + def test_get_response_data(): """Get response data.""" # Given instanse of ``httpx.Response`` class with successful status code and content