diff --git a/examples/examples-catalog.json b/examples/examples-catalog.json index d763497..561323a 100644 --- a/examples/examples-catalog.json +++ b/examples/examples-catalog.json @@ -334,6 +334,17 @@ "ExecutionTimeout": 300 }, "path": "./src/handler_error/handler_error.py" + }, + { + "name": "None Results", + "description": "Test handling of step operations with undefined result after replay.", + "handler": "none_results.handler", + "integration": true, + "durableConfig": { + "RetentionPeriodInDays": 7, + "ExecutionTimeout": 300 + }, + "path": "./src/none_results/none_results.py" } ] } diff --git a/examples/src/none_results/none_results.py b/examples/src/none_results/none_results.py new file mode 100644 index 0000000..9cf3260 --- /dev/null +++ b/examples/src/none_results/none_results.py @@ -0,0 +1,31 @@ +"""Demonstrates handling of operations that return undefined values during replay.""" + +from typing import Any + +from aws_durable_execution_sdk_python.context import ( + DurableContext, + durable_with_child_context, +) +from aws_durable_execution_sdk_python.execution import durable_execution +from aws_durable_execution_sdk_python.config import Duration + + +@durable_with_child_context +def parent_context(ctx: DurableContext) -> None: + """Parent context that returns None.""" + return None + + +@durable_execution +def handler(_event: Any, context: DurableContext) -> str: + """Handler demonstrating operations with undefined/None results.""" + context.step( + lambda _: None, + name="fetch-user", + ) + + context.run_in_child_context(parent_context(), name="parent") + + context.wait(Duration.from_seconds(1), name="wait") + + return "result" diff --git a/examples/template.yaml b/examples/template.yaml index 4e1a7ed..545481c 100644 --- a/examples/template.yaml +++ b/examples/template.yaml @@ -418,3 +418,16 @@ Resources: DurableConfig: RetentionPeriodInDays: 7 ExecutionTimeout: 300 + NoneResults: + Type: AWS::Serverless::Function + Properties: + CodeUri: build/ + Handler: none_results.handler + Description: Test handling of step operations with undefined result after replay. + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 diff --git a/examples/test/none_results/test_none_results.py b/examples/test/none_results/test_none_results.py new file mode 100644 index 0000000..75ae69f --- /dev/null +++ b/examples/test/none_results/test_none_results.py @@ -0,0 +1,51 @@ +"""Tests for undefined_results.""" + +import pytest +from aws_durable_execution_sdk_python.execution import InvocationStatus + +from src.none_results import none_results +from test.conftest import deserialize_operation_payload + + +@pytest.mark.example +@pytest.mark.durable_execution( + handler=none_results.handler, + lambda_function_name="None Results", +) +def test_handle_step_operations_with_undefined_result_after_replay(durable_runner): + """Test handling of step operations with undefined result after replay.""" + with durable_runner: + result = durable_runner.run(input=None, timeout=10) + + assert result.status is InvocationStatus.SUCCEEDED + + # Verify execution completed successfully despite undefined operation results + assert deserialize_operation_payload(result.result) == "result" + + # Verify all operations were tracked even with undefined results + operations = result.operations + assert len(operations) == 3 # step + context + wait + + # Verify step operation with undefined result + step_ops = [ + op + for op in operations + if op.operation_type.value == "STEP" and op.name == "fetch-user" + ] + assert len(step_ops) == 1 + step_op = step_ops[0] + assert deserialize_operation_payload(step_op.result) is None + + # Verify child context operation with undefined result + context_ops = [ + op + for op in operations + if op.operation_type.value == "CONTEXT" and op.name == "parent" + ] + assert len(context_ops) == 1 + context_op = context_ops[0] + assert deserialize_operation_payload(context_op.result) is None + + # Verify wait operation completed normally + wait_op = operations[2] + assert wait_op.operation_type.value == "WAIT"