diff --git a/azure/durable_functions/models/entities/ResponseMessage.py b/azure/durable_functions/models/entities/ResponseMessage.py index 0b8b35dc..17fce6b5 100644 --- a/azure/durable_functions/models/entities/ResponseMessage.py +++ b/azure/durable_functions/models/entities/ResponseMessage.py @@ -1,4 +1,5 @@ from typing import Dict, Any +import json class ResponseMessage: @@ -17,6 +18,13 @@ def __init__(self, result: str, is_exception: bool = False): result: str The result provided by the entity """ + # The time-out case seems to be handled by the Functions-Host, so + # its result is not doubly-serialized. In this branch, we compensate + # for this by re-serializing the payload. + if result.strip().startswith("Timeout value of"): + is_exception = True + result = json.dumps(result) + self.result = result self.is_exception = is_exception # TODO: JS has an additional exceptionType field, but does not use it diff --git a/tests/orchestrator/test_entity.py b/tests/orchestrator/test_entity.py index 83a7d25a..428a8da9 100644 --- a/tests/orchestrator/test_entity.py +++ b/tests/orchestrator/test_entity.py @@ -192,11 +192,11 @@ def add_signal_entity_action(state: OrchestratorState, id_: df.EntityId, op: str state.actions.append([action]) def add_call_entity_completed_events( - context_builder: ContextBuilder, op: str, instance_id=str, input_=None, event_id=0, is_error=False): + context_builder: ContextBuilder, op: str, instance_id=str, input_=None, event_id=0, is_error=False, literal_input=False): context_builder.add_event_sent_event(instance_id, event_id) context_builder.add_orchestrator_completed_event() context_builder.add_orchestrator_started_event() - context_builder.add_event_raised_event(name="0000", id_=0, input_=input_, is_entity=True, is_error=is_error) + context_builder.add_event_raised_event(name="0000", id_=0, input_=input_, is_entity=True, is_error=is_error, literal_input=literal_input) def test_call_entity_sent(): context_builder = ContextBuilder('test_simple_function') @@ -289,4 +289,30 @@ def test_call_entity_catch_exception(): expected_state._is_done = True expected = expected_state.to_json() + assert_orchestration_state_equals(expected, result) + +def test_timeout_entity_catch_exception(): + entityId = df.EntityId("Counter", "myCounter") + context_builder = ContextBuilder('catch timeout exceptions') + add_call_entity_completed_events( + context_builder, + "add", + df.EntityId.get_scheduler_id(entityId), + input_="Timeout value of 00:02:00 was exceeded by function: Functions.SlowEntity.", + event_id=0, + is_error=False, + literal_input=True + ) + + result = get_orchestration_state_result( + context_builder, generator_function_catch_entity_exception) + + expected_state = base_expected_state( + "Exception thrown" + ) + + add_call_entity_action(expected_state, entityId, "add", 3) + expected_state._is_done = True + expected = expected_state.to_json() + assert_orchestration_state_equals(expected, result) \ No newline at end of file diff --git a/tests/test_utils/ContextBuilder.py b/tests/test_utils/ContextBuilder.py index a70f2808..4c4fae00 100644 --- a/tests/test_utils/ContextBuilder.py +++ b/tests/test_utils/ContextBuilder.py @@ -125,14 +125,17 @@ def add_execution_started_event( event.Input = input_ self.history_events.append(event) - def add_event_raised_event(self, name:str, id_: int, input_=None, timestamp=None, is_entity=False, is_error = False): + def add_event_raised_event(self, name:str, id_: int, input_=None, timestamp=None, is_entity=False, is_error = False, literal_input=False): event = self.get_base_event(HistoryEventType.EVENT_RAISED, id_=id_, timestamp=timestamp) event.Name = name if is_entity: if is_error: event.Input = json.dumps({ "result": json.dumps(input_), "exceptionType": "True" }) else: - event.Input = json.dumps({ "result": json.dumps(input_) }) + if literal_input: + event.Input = json.dumps({ "result": input_ }) + else: + event.Input = json.dumps({ "result": json.dumps(input_) }) else: event.Input = input_ # event.timestamp = timestamp