@@ -812,16 +812,14 @@ def test_delete_sandbox_not_exist(
812812
813813 @patch ("agentrun.sandbox.client.SandboxControlAPI" )
814814 @patch ("agentrun.sandbox.client.SandboxDataAPI" )
815- def test_delete_sandbox_not_found_in_response_is_idempotent (
815+ def test_delete_sandbox_not_found_in_response_raises_resource_not_exist (
816816 self , mock_data_api_class , mock_control_api_class
817817 ):
818- """数据面返回 not found 时,delete_sandbox 应幂等成功
818+ """数据面业务层返回 not- found 时,与 HTTP 404 路径统一抛 ResourceNotExistError。
819819
820- When the data plane returns a non-SUCCESS response whose message
821- contains "not found", the SDK should treat the delete as a success
822- rather than raising an error. This handles the case where the
823- control-plane list API still shows a TERMINATED sandbox, but the
824- data plane has already removed it.
820+ Callers can catch ResourceNotExistError for idempotent deletion when the
821+ control plane still lists a TERMINATED sandbox but the data plane has
822+ already removed it (e.g. ``except ResourceNotExistError: pass``).
825823 """
826824 mock_data_api = MagicMock ()
827825 mock_data_api .delete_sandbox .return_value = {
@@ -831,16 +829,67 @@ def test_delete_sandbox_not_found_in_response_is_idempotent(
831829 mock_data_api_class .return_value = mock_data_api
832830
833831 client = SandboxClient ()
834- result = client .delete_sandbox ("sandbox-123" )
835- assert result .sandbox_id == "sandbox-123"
832+ with pytest .raises (ResourceNotExistError ):
833+ client .delete_sandbox ("sandbox-123" )
834+
835+ @patch ("agentrun.sandbox.client.SandboxControlAPI" )
836+ @patch ("agentrun.sandbox.client.SandboxDataAPI" )
837+ def test_delete_sandbox_not_found_case_insensitive (
838+ self , mock_data_api_class , mock_control_api_class
839+ ):
840+ """大小写变体(如 'Sandbox NOT FOUND')也应触发 ResourceNotExistError。"""
841+ mock_data_api = MagicMock ()
842+ mock_data_api .delete_sandbox .return_value = {
843+ "code" : "FAILED" ,
844+ "message" : "Sandbox NOT FOUND" ,
845+ }
846+ mock_data_api_class .return_value = mock_data_api
847+
848+ client = SandboxClient ()
849+ with pytest .raises (ResourceNotExistError ):
850+ client .delete_sandbox ("sandbox-123" )
851+
852+ @patch ("agentrun.sandbox.client.SandboxControlAPI" )
853+ @patch ("agentrun.sandbox.client.SandboxDataAPI" )
854+ def test_delete_sandbox_other_failure_message_raises_client_error (
855+ self , mock_data_api_class , mock_control_api_class
856+ ):
857+ """无关 not-found 的失败消息(如 'sandbox is busy')应仍抛 ClientError。"""
858+ mock_data_api = MagicMock ()
859+ mock_data_api .delete_sandbox .return_value = {
860+ "code" : "FAILED" ,
861+ "message" : "sandbox is busy" ,
862+ }
863+ mock_data_api_class .return_value = mock_data_api
864+
865+ client = SandboxClient ()
866+ with pytest .raises (ClientError , match = "Failed to stop sandbox" ):
867+ client .delete_sandbox ("sandbox-123" )
868+
869+ @patch ("agentrun.sandbox.client.SandboxControlAPI" )
870+ @patch ("agentrun.sandbox.client.SandboxDataAPI" )
871+ def test_delete_sandbox_empty_message_raises_client_error (
872+ self , mock_data_api_class , mock_control_api_class
873+ ):
874+ """message 为空时不应误触 not-found 逻辑,应抛 ClientError。"""
875+ mock_data_api = MagicMock ()
876+ mock_data_api .delete_sandbox .return_value = {
877+ "code" : "FAILED" ,
878+ "message" : "" ,
879+ }
880+ mock_data_api_class .return_value = mock_data_api
881+
882+ client = SandboxClient ()
883+ with pytest .raises (ClientError , match = "Failed to stop sandbox" ):
884+ client .delete_sandbox ("sandbox-123" )
836885
837886 @patch ("agentrun.sandbox.client.SandboxControlAPI" )
838887 @patch ("agentrun.sandbox.client.SandboxDataAPI" )
839888 @pytest .mark .asyncio
840- async def test_delete_sandbox_async_not_found_in_response_is_idempotent (
889+ async def test_delete_sandbox_async_not_found_in_response_raises_resource_not_exist (
841890 self , mock_data_api_class , mock_control_api_class
842891 ):
843- """数据面返回 not found 时,delete_sandbox_async 应幂等成功 """
892+ """数据面业务层返回 not- found 时(async),与 HTTP 404 路径统一抛 ResourceNotExistError。 """
844893 mock_data_api = MagicMock ()
845894 mock_data_api .delete_sandbox_async = AsyncMock (
846895 return_value = {
@@ -851,8 +900,28 @@ async def test_delete_sandbox_async_not_found_in_response_is_idempotent(
851900 mock_data_api_class .return_value = mock_data_api
852901
853902 client = SandboxClient ()
854- result = await client .delete_sandbox_async ("sandbox-123" )
855- assert result .sandbox_id == "sandbox-123"
903+ with pytest .raises (ResourceNotExistError ):
904+ await client .delete_sandbox_async ("sandbox-123" )
905+
906+ @patch ("agentrun.sandbox.client.SandboxControlAPI" )
907+ @patch ("agentrun.sandbox.client.SandboxDataAPI" )
908+ @pytest .mark .asyncio
909+ async def test_delete_sandbox_async_other_failure_raises_client_error (
910+ self , mock_data_api_class , mock_control_api_class
911+ ):
912+ """无关 not-found 的失败消息(async)应仍抛 ClientError。"""
913+ mock_data_api = MagicMock ()
914+ mock_data_api .delete_sandbox_async = AsyncMock (
915+ return_value = {
916+ "code" : "FAILED" ,
917+ "message" : "sandbox is busy" ,
918+ }
919+ )
920+ mock_data_api_class .return_value = mock_data_api
921+
922+ client = SandboxClient ()
923+ with pytest .raises (ClientError , match = "Failed to stop sandbox" ):
924+ await client .delete_sandbox_async ("sandbox-123" )
856925
857926 @patch ("agentrun.sandbox.client.SandboxControlAPI" )
858927 @patch ("agentrun.sandbox.client.SandboxDataAPI" )
0 commit comments