Skip to content

Commit 2d0f445

Browse files
author
Alex Wang
committed
examples: add callback unhappy tests
1 parent 2cda53b commit 2d0f445

File tree

5 files changed

+199
-6
lines changed

5 files changed

+199
-6
lines changed

examples/examples-catalog.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,50 @@
444444
"ExecutionTimeout": 300
445445
},
446446
"path": "./src/none_results/none_results.py"
447+
},
448+
{
449+
"name": "Create Callback Failures Uncaught",
450+
"description": "Demonstrates callback failure scenarios where the error propagates and is handled by framework",
451+
"handler": "callback_failure.handler",
452+
"integration": true,
453+
"durableConfig": {
454+
"RetentionPeriodInDays": 7,
455+
"ExecutionTimeout": 300
456+
},
457+
"path": "./src/callback/callback_failure.py"
458+
},
459+
{
460+
"name": "Create Callback Failures Caught Error",
461+
"description": "Demonstrates callback failure scenarios where the error is caught in the code",
462+
"handler": "callback_failure.handler",
463+
"integration": true,
464+
"durableConfig": {
465+
"RetentionPeriodInDays": 7,
466+
"ExecutionTimeout": 300
467+
},
468+
"path": "./src/callback/callback_failure.py"
469+
},
470+
{
471+
"name": "Callback With Heartbeat Timeout",
472+
"description": "Demonstrates callback timeout scenarios (heartbeat timeout and general timeout)",
473+
"handler": "callback_with_timeout.handler",
474+
"integration": true,
475+
"durableConfig": {
476+
"RetentionPeriodInDays": 7,
477+
"ExecutionTimeout": 300
478+
},
479+
"path": "./src/callback/callback_with_timeout.py"
480+
},
481+
{
482+
"name": "Callback With General Timeout",
483+
"description": "Demonstrates callback timeout scenarios (heartbeat timeout and general timeout)",
484+
"handler": "callback_with_timeout.handler",
485+
"integration": true,
486+
"durableConfig": {
487+
"RetentionPeriodInDays": 7,
488+
"ExecutionTimeout": 300
489+
},
490+
"path": "./src/callback/callback_with_timeout.py"
447491
}
448492
]
449493
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Demonstrates callback failure scenarios where the error propagates and is handled by framework."""
2+
3+
from typing import Any
4+
5+
from aws_durable_execution_sdk_python.config import CallbackConfig, Duration
6+
from aws_durable_execution_sdk_python.context import DurableContext
7+
from aws_durable_execution_sdk_python.execution import durable_execution
8+
9+
10+
@durable_execution
11+
def handler(event: dict[str, Any], context: DurableContext) -> dict[str, Any]:
12+
"""Handler demonstrating callback failure scenarios."""
13+
should_catch_error = event.get("shouldCatchError", False)
14+
callback_config = CallbackConfig(timeout=Duration.from_seconds(60))
15+
16+
if should_catch_error:
17+
# Pattern where error is caught and returned in result
18+
try:
19+
callback = context.create_callback(
20+
name="failing-operation",
21+
config=callback_config,
22+
)
23+
return callback.result()
24+
except Exception as error:
25+
return {
26+
"success": False,
27+
"error": str(error),
28+
}
29+
else:
30+
# Pattern where error propagates to framework (for basic failure case)
31+
callback = context.create_callback(
32+
name="failing-operation",
33+
config=callback_config,
34+
)
35+
return callback.result()

examples/src/callback/callback_with_timeout.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@
1010

1111

1212
@durable_execution
13-
def handler(_event: Any, context: DurableContext) -> str:
14-
# Callback with custom timeout configuration
15-
config = CallbackConfig(
16-
timeout=Duration.from_seconds(60), heartbeat_timeout=Duration.from_seconds(30)
17-
)
13+
def handler(event: Any, context: DurableContext) -> str:
14+
timeout_type = event.get("timeoutType", "general")
15+
if timeout_type == "heartbeat":
16+
config = CallbackConfig(
17+
timeout=Duration.from_seconds(10),
18+
heartbeat_timeout=Duration.from_seconds(1),
19+
)
20+
else:
21+
config = CallbackConfig(timeout=Duration.from_seconds(1))
1822

1923
callback: Callback[str] = context.create_callback(
2024
name="timeout_callback", config=config
2125
)
2226

23-
return f"Callback created with 60s timeout: {callback.callback_id}"
27+
return callback.result()
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Tests for create_callback_failures."""
2+
3+
import pytest
4+
from aws_durable_execution_sdk_python.execution import InvocationStatus
5+
from aws_durable_execution_sdk_python.lambda_service import ErrorObject
6+
7+
from src.callback import callback_failure
8+
9+
10+
@pytest.mark.example
11+
@pytest.mark.durable_execution(
12+
handler=callback_failure.handler,
13+
lambda_function_name="Create Callback Failures Uncaught",
14+
)
15+
def test_handle_callback_operations_with_failure_uncaught(durable_runner):
16+
"""Test handling callback operations with failure."""
17+
test_payload = {"shouldCatchError": False}
18+
19+
with durable_runner:
20+
execution_arn = durable_runner.run_async(input=test_payload, timeout=30)
21+
22+
callback_id = durable_runner.wait_for_callback(execution_arn=execution_arn)
23+
24+
durable_runner.send_callback_failure(
25+
callback_id=callback_id,
26+
error=ErrorObject.from_message("External API failure"),
27+
)
28+
29+
result = durable_runner.wait_for_result(execution_arn=execution_arn)
30+
31+
assert result.status is InvocationStatus.FAILED
32+
33+
error = result.error
34+
assert error is not None
35+
assert "External API failure" in error.message
36+
assert error.type == "CallbackError"
37+
assert error.stack_trace is None
38+
39+
40+
@pytest.mark.example
41+
@pytest.mark.durable_execution(
42+
handler=callback_failure.handler,
43+
lambda_function_name="Create Callback Failures Caught Error",
44+
)
45+
def test_handle_callback_operations_with_caught_error(durable_runner):
46+
"""Test handling callback operations with caught error."""
47+
test_payload = {"shouldCatchError": True}
48+
49+
with durable_runner:
50+
execution_arn = durable_runner.run_async(input=test_payload, timeout=30)
51+
callback_id = durable_runner.wait_for_callback(execution_arn=execution_arn)
52+
durable_runner.send_callback_failure(
53+
callback_id=callback_id,
54+
error=ErrorObject.from_message("External API failure"),
55+
)
56+
result = durable_runner.wait_for_result(execution_arn=execution_arn)
57+
58+
assert result.status is InvocationStatus.SUCCEEDED
59+
60+
from test.conftest import deserialize_operation_payload
61+
62+
result_data = deserialize_operation_payload(result.result)
63+
assert result_data["success"] is False
64+
assert "External API failure" in result_data["error"]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Tests for callback operation permutations."""
2+
3+
import pytest
4+
from aws_durable_execution_sdk_python.execution import InvocationStatus
5+
6+
from src.callback import callback_with_timeout
7+
8+
9+
@pytest.mark.example
10+
@pytest.mark.durable_execution(
11+
handler=callback_with_timeout.handler,
12+
lambda_function_name="Callback With Heartbeat Timeout",
13+
)
14+
def test_callback_with_heartbeat_timeout(durable_runner):
15+
"""Test callback with custom timeout configuration."""
16+
test_payload = {"timeoutType": "heartbeat"}
17+
with durable_runner:
18+
result = durable_runner.run(input=test_payload, timeout=20)
19+
20+
assert result.status is InvocationStatus.FAILED
21+
error = result.error
22+
assert error is not None
23+
assert error.message == "Callback timed out on heartbeat"
24+
assert error.type == "CallbackError"
25+
assert error.data is None
26+
assert error.stack_trace is None
27+
28+
29+
@pytest.mark.example
30+
@pytest.mark.durable_execution(
31+
handler=callback_with_timeout.handler,
32+
lambda_function_name="Callback With General Timeout",
33+
)
34+
def test_callback_with_general_timeout(durable_runner):
35+
"""Test callback with custom timeout configuration."""
36+
test_payload = {"timeoutType": "general"}
37+
with durable_runner:
38+
result = durable_runner.run(input=test_payload, timeout=20)
39+
40+
assert result.status is InvocationStatus.FAILED
41+
error = result.error
42+
assert error is not None
43+
assert error.message == "Callback timed out"
44+
assert error.type == "CallbackError"
45+
assert error.data is None
46+
assert error.stack_trace is None

0 commit comments

Comments
 (0)