@@ -626,6 +626,23 @@ def test_a_start_without_a_process_id_is_an_unknown_outcome():
626626 assert error .value .code == "secret_process_outcome_unknown"
627627
628628
629+ @pytest .mark .anyio
630+ @pytest .mark .parametrize ("async_client" , [False , True ])
631+ @pytest .mark .parametrize ("process_id" , [42 , True , " " ])
632+ async def test_invalid_process_ids_are_unknown_outcomes (async_client , process_id ):
633+ manager , http = _sandbox_manager (
634+ {("POST" , "/sandbox/sbx_123/processes" ): (200 , {"process" : {"id" : process_id }})},
635+ async_client = async_client ,
636+ )
637+ with pytest .raises (HyperbrowserError ) as error :
638+ result = manager .start_secret_process ("sbx_123" , {"command" : "x" })
639+ if async_client :
640+ await result
641+ assert error .value .code == "secret_process_outcome_unknown"
642+ assert error .value .retryable is False
643+ assert len (http .calls ) == 1
644+
645+
629646def test_a_timed_out_start_is_an_unknown_outcome_but_a_refused_one_is_not ():
630647 timed_out = httpx .ReadTimeout ("timed out" )
631648 manager , _ = _sandbox_manager ({("POST" , "/sandbox/sbx_123/processes" ): timed_out })
@@ -1238,6 +1255,23 @@ def test_a_name_with_hyphens_in_other_places_goes_straight_to_the_name_search():
12381255CREATE_WITH_SECRETS = {"image_name" : "node" , "secrets" : {"API_KEY" : "provider-key" }}
12391256
12401257
1258+ @pytest .mark .anyio
1259+ @pytest .mark .parametrize ("async_client" , [False , True ])
1260+ @pytest .mark .parametrize ("sandbox_id" , ["" , " " ])
1261+ async def test_empty_sandbox_ids_are_unknown_outcomes (async_client , sandbox_id ):
1262+ manager , http = _sandbox_manager (
1263+ {("POST" , "/sandbox" ): (200 , dict (SANDBOX_DETAIL_PAYLOAD , id = sandbox_id ))},
1264+ async_client = async_client ,
1265+ )
1266+ with pytest .raises (HyperbrowserError ) as error :
1267+ result = manager .create (CREATE_WITH_SECRETS )
1268+ if async_client :
1269+ await result
1270+ assert error .value .code == "secret_process_outcome_unknown"
1271+ assert error .value .retryable is False
1272+ assert len (http .calls ) == 1
1273+
1274+
12411275@pytest .mark .parametrize (
12421276 "outcome" ,
12431277 [
@@ -1493,20 +1527,20 @@ async def test_use_shell_is_rejected_for_starts_with_secrets(api_type):
14931527
14941528
14951529def test_secret_names_env_names_and_versions_are_checked_locally ():
1496- for name in ["" , "-leading" , "has space" , "a" * 129 ]:
1530+ for name in ["" , "-leading" , "has space" , "a" * 129 , "name \n " ]:
14971531 with pytest .raises (ValueError , match = "Invalid secret name" ):
14981532 CreateSecretParams (name = name )
14991533 assert CreateSecretParams (name = "a" * 128 ).name == "a" * 128
15001534
1501- for env in ["1ABC" , "API-KEY" , "" ]:
1535+ for env in ["1ABC" , "API-KEY" , "" , "__proto__" , "KEY \n " ]:
15021536 with pytest .raises (ValueError , match = "environment variable name" ):
15031537 SandboxExecParams (command = "x" , secrets = {env : "provider-key" })
15041538 with pytest .raises (ValueError , match = "environment variable name" ):
15051539 CreateSandboxParams (image_name = "node" , secrets = {env : "provider-key" })
15061540 with pytest .raises (ValueError , match = "Invalid secret name" ):
15071541 SandboxExecParams (command = "x" , secrets = {"API_KEY" : "bad name" })
15081542
1509- for version in [0 , - 1 , 2 ** 31 ]:
1543+ for version in [0 , - 1 , 2 ** 31 , True , 1.0 , "1" ]:
15101544 with pytest .raises (ValueError , match = "Invalid secret version" ):
15111545 SandboxExecParams (
15121546 command = "x" , secrets = {"A" : {"name" : "k" , "version" : version }}
@@ -1524,7 +1558,7 @@ def test_list_pages_and_version_numbers_are_checked_before_any_request():
15241558 for params in [{"limit" : 0 }, {"limit" : 101 }, {"page" : 0 }]:
15251559 with pytest .raises (ValueError ):
15261560 manager .list (params )
1527- for version in [0 , 2 ** 31 , "1" ]:
1561+ for version in [0 , 2 ** 31 , "1" , True , 1.0 , 1.5 ]:
15281562 with pytest .raises (HyperbrowserError ) as error :
15291563 manager .delete_version ("provider-key" , version )
15301564 assert error .value .code == "invalid_secret_version"
@@ -1542,3 +1576,35 @@ async def test_async_version_numbers_are_checked_before_any_request():
15421576 with pytest .raises (ValueError ):
15431577 await manager .list ({"limit" : 101 })
15441578 assert http .calls == []
1579+
1580+
1581+ def test_secret_request_repr_does_not_expose_the_value ():
1582+ params = CreateSecretParams (name = "key" , value = "dummy-sensitive-value" )
1583+ assert "dummy-sensitive-value" not in repr (params )
1584+ assert "dummy-sensitive-value" not in str (params )
1585+ assert "value" not in params .model_dump ()
1586+ assert params .value == "dummy-sensitive-value"
1587+
1588+
1589+ def test_secret_request_validation_message_does_not_expose_the_value ():
1590+ with pytest .raises (ValueError ) as error :
1591+ CreateSecretParams .model_validate ({"value" : "dummy-sensitive-value" })
1592+ assert "dummy-sensitive-value" not in str (error .value )
1593+
1594+
1595+ @pytest .mark .parametrize (
1596+ "model,params" ,
1597+ [
1598+ (CreateSandboxParams , {"image_name" : "node" }),
1599+ (SandboxExecParams , {"command" : "x" }),
1600+ ],
1601+ )
1602+ def test_secret_bindings_enforce_the_limit_and_omit_empty_maps (model , params ):
1603+ from hyperbrowser .client ._request import dump_request
1604+
1605+ assert "secrets" not in dump_request (dict (params , secrets = {}), model )
1606+ assert (
1607+ len (model (** params , secrets = {f"KEY_{ i } " : "k" for i in range (32 )}).secrets ) == 32
1608+ )
1609+ with pytest .raises (ValueError , match = "32" ):
1610+ model (** params , secrets = {f"KEY_{ i } " : "k" for i in range (33 )})
0 commit comments