Skip to content

Commit 7040849

Browse files
Expand numeric-like URL fallback normalization and wrapper coverage
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 03c4288 commit 7040849

File tree

3 files changed

+147
-4
lines changed

3 files changed

+147
-4
lines changed

hyperbrowser/transport/error_utils.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,24 @@
55
import httpx
66

77
_HTTP_METHOD_TOKEN_PATTERN = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Z]+$")
8+
_NUMERIC_LIKE_URL_PATTERN = re.compile(
9+
r"^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$"
10+
)
811
_MAX_ERROR_MESSAGE_LENGTH = 2000
912
_MAX_REQUEST_URL_DISPLAY_LENGTH = 1000
1013
_MAX_REQUEST_METHOD_LENGTH = 50
11-
_INVALID_URL_SENTINELS = {"none", "null", "undefined", "nan"}
14+
_INVALID_URL_SENTINELS = {
15+
"none",
16+
"null",
17+
"undefined",
18+
"nan",
19+
"inf",
20+
"+inf",
21+
"-inf",
22+
"infinity",
23+
"+infinity",
24+
"-infinity",
25+
}
1226

1327

1428
def _normalize_request_method(method: Any) -> str:
@@ -36,7 +50,9 @@ def _normalize_request_url(url: Any) -> str:
3650
if not normalized_url:
3751
return "unknown URL"
3852
lowered_url = normalized_url.lower()
39-
if lowered_url in _INVALID_URL_SENTINELS or normalized_url.isdigit():
53+
if lowered_url in _INVALID_URL_SENTINELS or _NUMERIC_LIKE_URL_PATTERN.fullmatch(
54+
normalized_url
55+
):
4056
return "unknown URL"
4157
if any(character.isspace() for character in normalized_url):
4258
return "unknown URL"

tests/test_transport_error_utils.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,21 @@ def test_format_request_failure_message_normalizes_numeric_fallback_url_values()
256256
assert message == "Request GET unknown URL failed"
257257

258258

259-
@pytest.mark.parametrize("sentinel_url", ["none", "null", "undefined", "nan"])
259+
@pytest.mark.parametrize(
260+
"sentinel_url",
261+
[
262+
"none",
263+
"null",
264+
"undefined",
265+
"nan",
266+
"inf",
267+
"+inf",
268+
"-inf",
269+
"infinity",
270+
"+infinity",
271+
"-infinity",
272+
],
273+
)
260274
def test_format_request_failure_message_normalizes_sentinel_fallback_url_values(
261275
sentinel_url: str,
262276
):
@@ -269,6 +283,19 @@ def test_format_request_failure_message_normalizes_sentinel_fallback_url_values(
269283
assert message == "Request GET unknown URL failed"
270284

271285

286+
@pytest.mark.parametrize("numeric_like_url", ["1", "1.5", "-1.25", "+2", ".75", "1e3"])
287+
def test_format_request_failure_message_normalizes_numeric_like_url_strings(
288+
numeric_like_url: str,
289+
):
290+
message = format_request_failure_message(
291+
httpx.RequestError("network down"),
292+
fallback_method="GET",
293+
fallback_url=numeric_like_url,
294+
)
295+
296+
assert message == "Request GET unknown URL failed"
297+
298+
272299
def test_format_request_failure_message_supports_url_like_fallback_values():
273300
message = format_request_failure_message(
274301
httpx.RequestError("network down"),
@@ -297,7 +324,21 @@ def test_format_generic_request_failure_message_normalizes_numeric_url_values():
297324
assert message == "Request GET unknown URL failed"
298325

299326

300-
@pytest.mark.parametrize("sentinel_url", ["none", "null", "undefined", "nan"])
327+
@pytest.mark.parametrize(
328+
"sentinel_url",
329+
[
330+
"none",
331+
"null",
332+
"undefined",
333+
"nan",
334+
"inf",
335+
"+inf",
336+
"-inf",
337+
"infinity",
338+
"+infinity",
339+
"-infinity",
340+
],
341+
)
301342
def test_format_generic_request_failure_message_normalizes_sentinel_url_values(
302343
sentinel_url: str,
303344
):
@@ -309,6 +350,18 @@ def test_format_generic_request_failure_message_normalizes_sentinel_url_values(
309350
assert message == "Request GET unknown URL failed"
310351

311352

353+
@pytest.mark.parametrize("numeric_like_url", ["1", "1.5", "-1.25", "+2", ".75", "1e3"])
354+
def test_format_generic_request_failure_message_normalizes_numeric_like_url_strings(
355+
numeric_like_url: str,
356+
):
357+
message = format_generic_request_failure_message(
358+
method="GET",
359+
url=numeric_like_url,
360+
)
361+
362+
assert message == "Request GET unknown URL failed"
363+
364+
312365
def test_format_generic_request_failure_message_supports_url_like_values():
313366
message = format_generic_request_failure_message(
314367
method="GET",

tests/test_transport_response_handling.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,22 @@ def failing_get(*args, **kwargs):
410410
transport.close()
411411

412412

413+
def test_sync_transport_wraps_unexpected_errors_with_numeric_like_string_url_fallback():
414+
transport = SyncTransport(api_key="test-key")
415+
original_get = transport.client.get
416+
417+
def failing_get(*args, **kwargs):
418+
raise RuntimeError("boom")
419+
420+
transport.client.get = failing_get # type: ignore[assignment]
421+
try:
422+
with pytest.raises(HyperbrowserError, match="Request GET unknown URL failed"):
423+
transport.get("1.5")
424+
finally:
425+
transport.client.get = original_get # type: ignore[assignment]
426+
transport.close()
427+
428+
413429
def test_async_transport_put_wraps_unexpected_errors_with_url_context():
414430
async def run() -> None:
415431
transport = AsyncTransport(api_key="test-key")
@@ -495,6 +511,27 @@ async def failing_put(*args, **kwargs):
495511
asyncio.run(run())
496512

497513

514+
def test_async_transport_wraps_unexpected_errors_with_numeric_like_string_url_fallback():
515+
async def run() -> None:
516+
transport = AsyncTransport(api_key="test-key")
517+
original_put = transport.client.put
518+
519+
async def failing_put(*args, **kwargs):
520+
raise RuntimeError("boom")
521+
522+
transport.client.put = failing_put # type: ignore[assignment]
523+
try:
524+
with pytest.raises(
525+
HyperbrowserError, match="Request PUT unknown URL failed"
526+
):
527+
await transport.put("1e6")
528+
finally:
529+
transport.client.put = original_put # type: ignore[assignment]
530+
await transport.close()
531+
532+
asyncio.run(run())
533+
534+
498535
def test_sync_transport_request_error_without_request_uses_fallback_url():
499536
transport = SyncTransport(api_key="test-key")
500537
original_get = transport.client.get
@@ -579,6 +616,22 @@ def failing_get(*args, **kwargs):
579616
transport.close()
580617

581618

619+
def test_sync_transport_request_error_without_request_uses_unknown_url_for_numeric_like_string_input():
620+
transport = SyncTransport(api_key="test-key")
621+
original_get = transport.client.get
622+
623+
def failing_get(*args, **kwargs):
624+
raise httpx.RequestError("network down")
625+
626+
transport.client.get = failing_get # type: ignore[assignment]
627+
try:
628+
with pytest.raises(HyperbrowserError, match="Request GET unknown URL failed"):
629+
transport.get("1.5")
630+
finally:
631+
transport.client.get = original_get # type: ignore[assignment]
632+
transport.close()
633+
634+
582635
def test_async_transport_request_error_without_request_uses_fallback_url():
583636
async def run() -> None:
584637
transport = AsyncTransport(api_key="test-key")
@@ -684,3 +737,24 @@ async def failing_delete(*args, **kwargs):
684737
await transport.close()
685738

686739
asyncio.run(run())
740+
741+
742+
def test_async_transport_request_error_without_request_uses_unknown_url_for_numeric_like_string_input():
743+
async def run() -> None:
744+
transport = AsyncTransport(api_key="test-key")
745+
original_delete = transport.client.delete
746+
747+
async def failing_delete(*args, **kwargs):
748+
raise httpx.RequestError("network down")
749+
750+
transport.client.delete = failing_delete # type: ignore[assignment]
751+
try:
752+
with pytest.raises(
753+
HyperbrowserError, match="Request DELETE unknown URL failed"
754+
):
755+
await transport.delete("1e6")
756+
finally:
757+
transport.client.delete = original_delete # type: ignore[assignment]
758+
await transport.close()
759+
760+
asyncio.run(run())

0 commit comments

Comments
 (0)