1414from kernel import (
1515 Kernel ,
1616 AsyncKernel ,
17+ APITimeoutError ,
1718 APIConnectionError ,
1819 AuthenticationError ,
1920 InternalServerError ,
@@ -1269,15 +1270,21 @@ def test_indexed_multipart_body_flattens_only_given_values() -> None:
12691270class _FailingSyncStream (httpx .SyncByteStream ):
12701271 """A response body that fails while it is being read."""
12711272
1273+ def __init__ (self , error_type : type [httpx .TransportError ] = httpx .ReadError ) -> None :
1274+ self .error_type = error_type
1275+
12721276 @override
12731277 def __iter__ (self ) -> Iterator [bytes ]:
1274- raise httpx . ReadError ("connection reset while reading the error body" )
1278+ raise self . error_type ("connection failed while reading the error body" )
12751279
12761280
12771281class _FailingAsyncStream (httpx .AsyncByteStream ):
1282+ def __init__ (self , error_type : type [httpx .TransportError ] = httpx .ReadError ) -> None :
1283+ self .error_type = error_type
1284+
12781285 @override
12791286 async def __aiter__ (self ) -> AsyncIterator [bytes ]:
1280- raise httpx . ReadError ("connection reset while reading the error body" )
1287+ raise self . error_type ("connection failed while reading the error body" )
12811288 yield b"" # pragma: no cover - unreachable, keeps this an async generator
12821289
12831290
@@ -1351,8 +1358,14 @@ async def handle_request(request: httpx.Request) -> httpx.Response:
13511358 assert requests [1 ].headers .get ("Authorization" ) == f"Bearer { api_key } "
13521359
13531360
1361+ @pytest .mark .parametrize (
1362+ ("error_type" , "expected_error" ),
1363+ [(httpx .ReadError , APIConnectionError ), (httpx .ReadTimeout , APITimeoutError )],
1364+ )
13541365def test_stale_direct_vm_auth_body_read_failure_does_not_retry_unreplayable_write (
13551366 monkeypatch : pytest .MonkeyPatch ,
1367+ error_type : type [httpx .TransportError ],
1368+ expected_error : type [Exception ],
13561369) -> None :
13571370 monkeypatch .delenv ("KERNEL_BROWSER_ROUTING_SUBRESOURCES" , raising = False )
13581371 monkeypatch .setattr ("kernel._base_client.SyncAPIClient._sleep_for_retry" , _skip_retry_sleep )
@@ -1362,23 +1375,26 @@ def handle_request(request: httpx.Request) -> httpx.Response:
13621375 body = b"" .join (cast (Iterator [bytes ], request .stream ))
13631376 requests .append ((request .url , body ))
13641377 if "browser-session.test" in str (request .url ):
1365- return httpx .Response (401 , stream = _FailingSyncStream (), headers = {"content-type" : "text/plain" })
1378+ return httpx .Response (401 , stream = _FailingSyncStream (error_type ), headers = {"content-type" : "text/plain" })
13661379 return httpx .Response (201 )
13671380
1381+ def read_request (request : httpx .Request ) -> None :
1382+ request .read ()
1383+
13681384 class Transport (httpx .BaseTransport ):
13691385 @override
13701386 def handle_request (self , request : httpx .Request ) -> httpx .Response :
13711387 return handle_request (request )
13721388
1373- http_client = httpx .Client (transport = Transport ())
1389+ http_client = httpx .Client (transport = Transport (), event_hooks = { "request" : [ read_request ]} )
13741390 with Kernel (
13751391 base_url = base_url ,
13761392 api_key = api_key ,
13771393 http_client = http_client ,
13781394 _strict_response_validation = True ,
13791395 ) as client :
13801396 _cache_browser (client )
1381- with pytest .raises (APIConnectionError ):
1397+ with pytest .raises (expected_error ):
13821398 client .browsers .fs .write_file ("sess-1" , _UnseekableFile (b"payload" ), path = "/tmp/x" )
13831399
13841400 assert requests == [
@@ -1395,8 +1411,14 @@ def handle_request(self, request: httpx.Request) -> httpx.Response:
13951411
13961412
13971413@pytest .mark .asyncio
1414+ @pytest .mark .parametrize (
1415+ ("error_type" , "expected_error" ),
1416+ [(httpx .ReadError , APIConnectionError ), (httpx .ReadTimeout , APITimeoutError )],
1417+ )
13981418async def test_async_stale_direct_vm_auth_body_read_failure_does_not_retry_unreplayable_write (
13991419 monkeypatch : pytest .MonkeyPatch ,
1420+ error_type : type [httpx .TransportError ],
1421+ expected_error : type [Exception ],
14001422) -> None :
14011423 monkeypatch .delenv ("KERNEL_BROWSER_ROUTING_SUBRESOURCES" , raising = False )
14021424 monkeypatch .setattr ("kernel._base_client.AsyncAPIClient._sleep_for_retry" , _skip_async_retry_sleep )
@@ -1406,9 +1428,12 @@ async def handle_request(request: httpx.Request) -> httpx.Response:
14061428 body = b"" .join ([chunk async for chunk in cast (AsyncIterator [bytes ], request .stream )])
14071429 requests .append ((request .url , body ))
14081430 if "browser-session.test" in str (request .url ):
1409- return httpx .Response (403 , stream = _FailingAsyncStream (), headers = {"content-type" : "text/plain" })
1431+ return httpx .Response (403 , stream = _FailingAsyncStream (error_type ), headers = {"content-type" : "text/plain" })
14101432 return httpx .Response (201 )
14111433
1434+ async def read_request (request : httpx .Request ) -> None :
1435+ await request .aread ()
1436+
14121437 async def payload () -> AsyncIterator [bytes ]:
14131438 yield b"payload"
14141439
@@ -1417,7 +1442,7 @@ class Transport(httpx.AsyncBaseTransport):
14171442 async def handle_async_request (self , request : httpx .Request ) -> httpx .Response :
14181443 return await handle_request (request )
14191444
1420- http_client = httpx .AsyncClient (transport = Transport ())
1445+ http_client = httpx .AsyncClient (transport = Transport (), event_hooks = { "request" : [ read_request ]} )
14211446 async with AsyncKernel (
14221447 base_url = base_url ,
14231448 api_key = api_key ,
@@ -1427,7 +1452,7 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
14271452 route = browser_route_from_browser (_fake_browser ())
14281453 assert route is not None
14291454 client .browser_route_cache .set (route )
1430- with pytest .raises (APIConnectionError ):
1455+ with pytest .raises (expected_error ):
14311456 await client .browsers .fs .write_file ("sess-1" , cast (Any , payload ()), path = "/tmp/x" )
14321457
14331458 assert requests == [
0 commit comments