diff --git a/samcli/lib/clients/lambda_client.py b/samcli/lib/clients/lambda_client.py index c3bdc49497..3e52233dda 100644 --- a/samcli/lib/clients/lambda_client.py +++ b/samcli/lib/clients/lambda_client.py @@ -232,18 +232,19 @@ def stop_durable_execution( # Prepare the request parameters params: Dict[str, Any] = {"DurableExecutionArn": durable_execution_arn} - # Add error object if any error fields are provided - if error_message or error_type or error_data or stack_trace: - error_object: Dict[str, Any] = {} - if error_message: - error_object["ErrorMessage"] = error_message - if error_type: - error_object["ErrorType"] = error_type - if error_data: - error_object["ErrorData"] = error_data - if stack_trace: - error_object["StackTrace"] = stack_trace - params["Error"] = error_object + # Build Error dict if any error fields are provided + error_dict: Dict[str, Any] = {} + if error_message: + error_dict["ErrorMessage"] = error_message + if error_type: + error_dict["ErrorType"] = error_type + if error_data: + error_dict["ErrorData"] = error_data + if stack_trace: + error_dict["StackTrace"] = stack_trace + + if error_dict: + params["Error"] = error_dict try: # Call the StopDurableExecution API diff --git a/samcli/local/lambda_service/local_lambda_http_service.py b/samcli/local/lambda_service/local_lambda_http_service.py index 168a54689a..b182587377 100644 --- a/samcli/local/lambda_service/local_lambda_http_service.py +++ b/samcli/local/lambda_service/local_lambda_http_service.py @@ -400,11 +400,15 @@ def _stop_durable_execution_handler(self, durable_execution_arn): try: # Parse request body for error details - handle empty payloads gracefully request_data = request.get_json(silent=True) or {} + error_dict = request_data.get("Error", {}) with DurableContext() as context: response = context.client.stop_durable_execution( durable_execution_arn=decoded_arn, - error=request_data.get("Error"), + error_message=error_dict.get("ErrorMessage"), + error_type=error_dict.get("ErrorType"), + error_data=error_dict.get("ErrorData"), + stack_trace=error_dict.get("StackTrace"), ) return self.service_response( json.dumps(response, cls=DateTimeEncoder), {"Content-Type": "application/json"}, 200 diff --git a/tests/integration/local/start_lambda/test_start_lambda_durable.py b/tests/integration/local/start_lambda/test_start_lambda_durable.py index a8c3830133..152f780ef1 100644 --- a/tests/integration/local/start_lambda/test_start_lambda_durable.py +++ b/tests/integration/local/start_lambda/test_start_lambda_durable.py @@ -358,3 +358,41 @@ def test_local_callback_cli_heartbeat(self): DurableExecutionArn=execution_arn, IncludeExecutionData=True ) self.assert_execution_history(history_response, DurableFunctionExamples.WAIT_FOR_CALLBACK) + + @parameterized.expand( + [ + ( + "all_parameters", + { + "Error": { + "ErrorMessage": "Test error message", + "ErrorType": "TestError", + "ErrorData": '{"detail": "test error"}', + "StackTrace": ["line1", "line2"], + } + }, + ), + ("minimal_parameters", {}), + ("error_message_only", {"Error": {"ErrorMessage": "Simple error message"}}), + ] + ) + @pytest.mark.timeout(timeout=300, method="thread") + def test_local_start_lambda_stop_durable_execution_http(self, name, error_params): + """Test stop_durable_execution via HTTP API with various error parameters.""" + # Start a long-running execution + event_payload = json.dumps({"timeout_seconds": 60, "heartbeat_timeout_seconds": 30}) + execution_arn, callback_id = self.invoke_and_wait_for_callback(payload=event_payload) + + # Stop the execution with error parameters + self.lambda_client.stop_durable_execution(DurableExecutionArn=execution_arn, **error_params) + + # Verify execution is stopped + execution_response = self.wait_for_execution_status(execution_arn, "STOPPED") + self.assertEqual(execution_response.get("Status"), "STOPPED") + + # Verify execution history contains stop event + history_response = self.lambda_client.get_durable_execution_history( + DurableExecutionArn=execution_arn, IncludeExecutionData=True + ) + execution_stopped = self.get_event_from_history(history_response.get("Events", []), "ExecutionStopped") + self.assertIsNotNone(execution_stopped, "Expected ExecutionStopped event in history") diff --git a/tests/unit/local/lambda_service/test_local_lambda_http_service.py b/tests/unit/local/lambda_service/test_local_lambda_http_service.py index 1d2e5d535d..c00bd79424 100644 --- a/tests/unit/local/lambda_service/test_local_lambda_http_service.py +++ b/tests/unit/local/lambda_service/test_local_lambda_http_service.py @@ -978,7 +978,14 @@ def test_stop_durable_execution_handler_success(self, service_response_mock, con service_response_mock.return_value = "success response" request_mock = Mock() - request_mock.get_json.return_value = {"Error": "test error"} + request_mock.get_json.return_value = { + "Error": { + "ErrorMessage": "test error message", + "ErrorType": "TestError", + "ErrorData": "test data", + "StackTrace": ["line1", "line2"], + } + } local_lambda_http_service.request = request_mock lambda_runner_mock = Mock() @@ -990,7 +997,10 @@ def test_stop_durable_execution_handler_success(self, service_response_mock, con context_class_mock.assert_called_once() client_mock.stop_durable_execution.assert_called_once_with( durable_execution_arn="test-arn", - error="test error", + error_message="test error message", + error_type="TestError", + error_data="test data", + stack_trace=["line1", "line2"], ) @patch("samcli.local.lambda_service.local_lambda_http_service.DurableContext") @@ -1040,7 +1050,10 @@ def test_stop_durable_execution_handler_empty_payload(self, service_response_moc context_class_mock.assert_called_once() client_mock.stop_durable_execution.assert_called_once_with( durable_execution_arn="test-arn", - error=None, # Should be None when no payload + error_message=None, + error_type=None, + error_data=None, + stack_trace=None, ) @patch("samcli.local.lambda_service.local_lambda_http_service.DurableContext") @@ -1069,7 +1082,10 @@ def test_stop_durable_execution_handler_url_decoding(self, service_response_mock expected_decoded = "arn:aws:lambda:us-west-2:123456789012:function:test" client_mock.stop_durable_execution.assert_called_once_with( durable_execution_arn=expected_decoded, - error=None, + error_message=None, + error_type=None, + error_data=None, + stack_trace=None, ) self.assertEqual(response, "success response")