@@ -313,9 +313,9 @@ def test_browser_route_from_browser_requires_base_url_and_jwt() -> None:
313313 assert browser_route_from_browser ({** _fake_browser (), "cdp_ws_url" : None }) is None
314314
315315
316- def test_browser_routing_config_from_env_defaults_to_curl (monkeypatch : pytest .MonkeyPatch ) -> None :
316+ def test_browser_routing_config_from_env_defaults_to_curl_and_telemetry (monkeypatch : pytest .MonkeyPatch ) -> None :
317317 monkeypatch .delenv ("KERNEL_BROWSER_ROUTING_SUBRESOURCES" , raising = False )
318- assert browser_routing_config_from_env ().subresources == ("curl" ,)
318+ assert browser_routing_config_from_env ().subresources == ("curl" , "telemetry" )
319319
320320
321321def test_browser_routing_config_from_env_empty_string_disables_routing (monkeypatch : pytest .MonkeyPatch ) -> None :
@@ -495,3 +495,63 @@ async def test_async_dead_vm_502_falls_back_to_control_plane(monkeypatch: pytest
495495 cp_request = cast (httpx .Request , cast (Any , cp_route .calls [0 ]).request )
496496 assert cp_request .headers .get ("Authorization" ) == f"Bearer { api_key } "
497497 assert cp_request .url .params .get ("jwt" ) is None
498+
499+
500+ @pytest .mark .parametrize ("status" , [503 , 504 ])
501+ @respx .mock
502+ def test_dead_vm_503_504_fall_back_to_control_plane (monkeypatch : pytest .MonkeyPatch , status : int ) -> None :
503+ # 503/504 share the unreachable-status path with 502; lock in both explicitly.
504+ monkeypatch .setenv ("KERNEL_BROWSER_ROUTING_SUBRESOURCES" , "process" )
505+ vm_route = respx .get (_VM_STATUS ).mock (return_value = httpx .Response (status , text = "unavailable" ))
506+ cp_route = respx .get (_CP_STATUS ).mock (return_value = httpx .Response (200 , json = {"state" : "exited" }))
507+ with Kernel (base_url = base_url , api_key = api_key , max_retries = 0 , _strict_response_validation = True ) as client :
508+ _cache_browser (client )
509+ out = client .browsers .process .status ("p-1" , id = "sess-1" )
510+
511+ assert vm_route .called
512+ assert cp_route .called
513+ assert out .state == "exited"
514+
515+
516+ @pytest .mark .asyncio
517+ @respx .mock
518+ async def test_async_gone_signal_404_falls_back_to_control_plane (monkeypatch : pytest .MonkeyPatch ) -> None :
519+ monkeypatch .setenv ("KERNEL_BROWSER_ROUTING_SUBRESOURCES" , "process" )
520+ vm_route = respx .get (_VM_STATUS ).mock (return_value = httpx .Response (404 , json = _GONE_BODY , headers = _GONE_HEADERS ))
521+ cp_route = respx .get (_CP_STATUS ).mock (return_value = httpx .Response (200 , json = {"state" : "exited" }))
522+ async with AsyncKernel (base_url = base_url , api_key = api_key , max_retries = 0 , _strict_response_validation = True ) as client :
523+ route = browser_route_from_browser (_fake_browser ())
524+ assert route is not None
525+ client .browser_route_cache .set (route )
526+ out = await client .browsers .process .status ("p-1" , id = "sess-1" )
527+
528+ assert vm_route .called
529+ assert cp_route .called
530+ assert out .state == "exited"
531+ cp_request = cast (httpx .Request , cast (Any , cp_route .calls [0 ]).request )
532+ assert cp_request .headers .get ("Authorization" ) == f"Bearer { api_key } "
533+ assert cp_request .url .params .get ("jwt" ) is None
534+
535+
536+ @pytest .mark .asyncio
537+ @respx .mock
538+ async def test_async_post_to_dead_vm_does_not_fall_back (monkeypatch : pytest .MonkeyPatch ) -> None :
539+ from kernel import APIStatusError
540+
541+ monkeypatch .setenv ("KERNEL_BROWSER_ROUTING_SUBRESOURCES" , "process" )
542+ vm_route = respx .post ("http://browser-session.test/browser/kernel/process/exec" ).mock (
543+ return_value = httpx .Response (502 , text = "Upstream not available" )
544+ )
545+ cp_route = respx .post (f"{ base_url } /browsers/sess-1/process/exec" ).mock (
546+ return_value = httpx .Response (200 , json = {"exit_code" : 0 , "stdout_b64" : "" , "stderr_b64" : "" })
547+ )
548+ async with AsyncKernel (base_url = base_url , api_key = api_key , max_retries = 0 , _strict_response_validation = True ) as client :
549+ route = browser_route_from_browser (_fake_browser ())
550+ assert route is not None
551+ client .browser_route_cache .set (route )
552+ with pytest .raises (APIStatusError ):
553+ await client .browsers .process .exec ("sess-1" , command = "echo" , args = ["hi" ])
554+
555+ assert vm_route .called
556+ # POST is side-effecting; must never be re-issued to the control plane (async parity).
557+ assert not cp_route .called
0 commit comments