@@ -810,6 +810,119 @@ def test_delete_sandbox_not_exist(
810810 with pytest .raises (ResourceNotExistError ):
811811 client .delete_sandbox ("nonexistent" )
812812
813+ @patch ("agentrun.sandbox.client.SandboxControlAPI" )
814+ @patch ("agentrun.sandbox.client.SandboxDataAPI" )
815+ def test_delete_sandbox_not_found_in_response_raises_resource_not_exist (
816+ self , mock_data_api_class , mock_control_api_class
817+ ):
818+ """数据面业务层返回 not-found 时,与 HTTP 404 路径统一抛 ResourceNotExistError。
819+
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``).
823+ """
824+ mock_data_api = MagicMock ()
825+ mock_data_api .delete_sandbox .return_value = {
826+ "code" : "FAILED" ,
827+ "message" : "sandbox not found" ,
828+ }
829+ mock_data_api_class .return_value = mock_data_api
830+
831+ client = SandboxClient ()
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" )
885+
886+ @patch ("agentrun.sandbox.client.SandboxControlAPI" )
887+ @patch ("agentrun.sandbox.client.SandboxDataAPI" )
888+ @pytest .mark .asyncio
889+ async def test_delete_sandbox_async_not_found_in_response_raises_resource_not_exist (
890+ self , mock_data_api_class , mock_control_api_class
891+ ):
892+ """数据面业务层返回 not-found 时(async),与 HTTP 404 路径统一抛 ResourceNotExistError。"""
893+ mock_data_api = MagicMock ()
894+ mock_data_api .delete_sandbox_async = AsyncMock (
895+ return_value = {
896+ "code" : "FAILED" ,
897+ "message" : "sandbox not found" ,
898+ }
899+ )
900+ mock_data_api_class .return_value = mock_data_api
901+
902+ client = SandboxClient ()
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" )
925+
813926 @patch ("agentrun.sandbox.client.SandboxControlAPI" )
814927 @patch ("agentrun.sandbox.client.SandboxDataAPI" )
815928 @pytest .mark .asyncio
0 commit comments