Skip to content

Commit c60505b

Browse files
Harden response JSON fallback handling in transports
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent a86f1b3 commit c60505b

3 files changed

Lines changed: 92 additions & 6 deletions

File tree

hyperbrowser/transport/async_transport.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import httpx
32
from typing import Mapping, Optional
43

@@ -56,10 +55,14 @@ async def _handle_response(self, response: httpx.Response) -> APIResponse:
5655
if not response.content:
5756
return APIResponse.from_status(response.status_code)
5857
return APIResponse(response.json())
59-
except (httpx.DecodingError, json.JSONDecodeError, ValueError) as e:
58+
except Exception as e:
6059
if response.status_code >= 400:
60+
try:
61+
response_text = response.text
62+
except Exception:
63+
response_text = ""
6164
raise HyperbrowserError(
62-
response.text or "Unknown error occurred",
65+
response_text or "Unknown error occurred",
6366
status_code=response.status_code,
6467
response=response,
6568
original_error=e,

hyperbrowser/transport/sync.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import httpx
32
from typing import Mapping, Optional
43

@@ -44,10 +43,14 @@ def _handle_response(self, response: httpx.Response) -> APIResponse:
4443
if not response.content:
4544
return APIResponse.from_status(response.status_code)
4645
return APIResponse(response.json())
47-
except (httpx.DecodingError, json.JSONDecodeError, ValueError) as e:
46+
except Exception as e:
4847
if response.status_code >= 400:
48+
try:
49+
response_text = response.text
50+
except Exception:
51+
response_text = ""
4952
raise HyperbrowserError(
50-
response.text or "Unknown error occurred",
53+
response_text or "Unknown error occurred",
5154
status_code=response.status_code,
5255
response=response,
5356
original_error=e,

tests/test_transport_response_handling.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,32 @@ def raise_for_status(self) -> None:
2626
raise httpx.RequestError("network down")
2727

2828

29+
class _BrokenJsonSuccessResponse:
30+
status_code = 200
31+
content = b"{broken-json}"
32+
33+
def raise_for_status(self) -> None:
34+
return None
35+
36+
def json(self):
37+
raise RuntimeError("broken json")
38+
39+
40+
class _BrokenJsonErrorResponse:
41+
status_code = 500
42+
content = b"{broken-json}"
43+
44+
def raise_for_status(self) -> None:
45+
return None
46+
47+
@property
48+
def text(self) -> str:
49+
raise RuntimeError("broken response text")
50+
51+
def json(self):
52+
raise RuntimeError("broken json")
53+
54+
2955
def test_sync_handle_response_with_non_json_success_body_returns_status_only():
3056
transport = SyncTransport(api_key="test-key")
3157
try:
@@ -39,6 +65,30 @@ def test_sync_handle_response_with_non_json_success_body_returns_status_only():
3965
transport.close()
4066

4167

68+
def test_sync_handle_response_with_broken_json_success_payload_returns_status_only():
69+
transport = SyncTransport(api_key="test-key")
70+
try:
71+
api_response = transport._handle_response(
72+
_BrokenJsonSuccessResponse() # type: ignore[arg-type]
73+
)
74+
75+
assert api_response.status_code == 200
76+
assert api_response.data is None
77+
finally:
78+
transport.close()
79+
80+
81+
def test_sync_handle_response_with_broken_json_error_payload_uses_default_message():
82+
transport = SyncTransport(api_key="test-key")
83+
try:
84+
with pytest.raises(HyperbrowserError, match="Unknown error occurred"):
85+
transport._handle_response(
86+
_BrokenJsonErrorResponse() # type: ignore[arg-type]
87+
)
88+
finally:
89+
transport.close()
90+
91+
4292
def test_sync_handle_response_with_request_error_includes_method_and_url():
4393
transport = SyncTransport(api_key="test-key")
4494
try:
@@ -111,6 +161,36 @@ async def run() -> None:
111161
asyncio.run(run())
112162

113163

164+
def test_async_handle_response_with_broken_json_success_payload_returns_status_only():
165+
async def run() -> None:
166+
transport = AsyncTransport(api_key="test-key")
167+
try:
168+
api_response = await transport._handle_response(
169+
_BrokenJsonSuccessResponse() # type: ignore[arg-type]
170+
)
171+
172+
assert api_response.status_code == 200
173+
assert api_response.data is None
174+
finally:
175+
await transport.close()
176+
177+
asyncio.run(run())
178+
179+
180+
def test_async_handle_response_with_broken_json_error_payload_uses_default_message():
181+
async def run() -> None:
182+
transport = AsyncTransport(api_key="test-key")
183+
try:
184+
with pytest.raises(HyperbrowserError, match="Unknown error occurred"):
185+
await transport._handle_response(
186+
_BrokenJsonErrorResponse() # type: ignore[arg-type]
187+
)
188+
finally:
189+
await transport.close()
190+
191+
asyncio.run(run())
192+
193+
114194
def test_async_handle_response_with_request_error_includes_method_and_url():
115195
async def run() -> None:
116196
transport = AsyncTransport(api_key="test-key")

0 commit comments

Comments
 (0)