Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions samcli/lib/clients/lambda_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion samcli/local/lambda_service/local_lambda_http_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")

Expand Down
Loading