Skip to content

Commit 6a36209

Browse files
Wrap transport response status code processing failures
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent e75d424 commit 6a36209

3 files changed

Lines changed: 71 additions & 6 deletions

File tree

hyperbrowser/transport/async_transport.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,28 @@ async def _handle_response(self, response: httpx.Response) -> APIResponse:
5656
return APIResponse.from_status(response.status_code)
5757
return APIResponse(response.json())
5858
except Exception as e:
59-
if response.status_code >= 400:
59+
try:
60+
status_code = response.status_code
61+
if isinstance(status_code, bool):
62+
raise TypeError("boolean status code is invalid")
63+
normalized_status_code = int(status_code)
64+
except Exception as status_exc:
65+
raise HyperbrowserError(
66+
"Failed to process response status code",
67+
original_error=status_exc,
68+
) from status_exc
69+
if normalized_status_code >= 400:
6070
try:
6171
response_text = response.text
6272
except Exception:
6373
response_text = ""
6474
raise HyperbrowserError(
6575
response_text or "Unknown error occurred",
66-
status_code=response.status_code,
76+
status_code=normalized_status_code,
6777
response=response,
6878
original_error=e,
6979
)
70-
return APIResponse.from_status(response.status_code)
80+
return APIResponse.from_status(normalized_status_code)
7181
except httpx.HTTPStatusError as e:
7282
message = extract_error_message(response, fallback_error=e)
7383
raise HyperbrowserError(

hyperbrowser/transport/sync.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,28 @@ def _handle_response(self, response: httpx.Response) -> APIResponse:
4444
return APIResponse.from_status(response.status_code)
4545
return APIResponse(response.json())
4646
except Exception as e:
47-
if response.status_code >= 400:
47+
try:
48+
status_code = response.status_code
49+
if isinstance(status_code, bool):
50+
raise TypeError("boolean status code is invalid")
51+
normalized_status_code = int(status_code)
52+
except Exception as status_exc:
53+
raise HyperbrowserError(
54+
"Failed to process response status code",
55+
original_error=status_exc,
56+
) from status_exc
57+
if normalized_status_code >= 400:
4858
try:
4959
response_text = response.text
5060
except Exception:
5161
response_text = ""
5262
raise HyperbrowserError(
5363
response_text or "Unknown error occurred",
54-
status_code=response.status_code,
64+
status_code=normalized_status_code,
5565
response=response,
5666
original_error=e,
5767
)
58-
return APIResponse.from_status(response.status_code)
68+
return APIResponse.from_status(normalized_status_code)
5969
except httpx.HTTPStatusError as e:
6070
message = extract_error_message(response, fallback_error=e)
6171
raise HyperbrowserError(

tests/test_transport_response_handling.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ def json(self):
5252
raise RuntimeError("broken json")
5353

5454

55+
class _BrokenStatusCodeJsonResponse:
56+
content = b"{broken-json}"
57+
58+
def raise_for_status(self) -> None:
59+
return None
60+
61+
@property
62+
def status_code(self) -> int:
63+
raise RuntimeError("broken status code")
64+
65+
def json(self):
66+
raise RuntimeError("broken json")
67+
68+
5569
def test_sync_handle_response_with_non_json_success_body_returns_status_only():
5670
transport = SyncTransport(api_key="test-key")
5771
try:
@@ -89,6 +103,20 @@ def test_sync_handle_response_with_broken_json_error_payload_uses_default_messag
89103
transport.close()
90104

91105

106+
def test_sync_handle_response_with_broken_status_code_raises_hyperbrowser_error():
107+
transport = SyncTransport(api_key="test-key")
108+
try:
109+
with pytest.raises(
110+
HyperbrowserError, match="Failed to process response status code"
111+
) as exc_info:
112+
transport._handle_response(
113+
_BrokenStatusCodeJsonResponse() # type: ignore[arg-type]
114+
)
115+
assert exc_info.value.original_error is not None
116+
finally:
117+
transport.close()
118+
119+
92120
def test_sync_handle_response_with_request_error_includes_method_and_url():
93121
transport = SyncTransport(api_key="test-key")
94122
try:
@@ -191,6 +219,23 @@ async def run() -> None:
191219
asyncio.run(run())
192220

193221

222+
def test_async_handle_response_with_broken_status_code_raises_hyperbrowser_error():
223+
async def run() -> None:
224+
transport = AsyncTransport(api_key="test-key")
225+
try:
226+
with pytest.raises(
227+
HyperbrowserError, match="Failed to process response status code"
228+
) as exc_info:
229+
await transport._handle_response(
230+
_BrokenStatusCodeJsonResponse() # type: ignore[arg-type]
231+
)
232+
assert exc_info.value.original_error is not None
233+
finally:
234+
await transport.close()
235+
236+
asyncio.run(run())
237+
238+
194239
def test_async_handle_response_with_request_error_includes_method_and_url():
195240
async def run() -> None:
196241
transport = AsyncTransport(api_key="test-key")

0 commit comments

Comments
 (0)