def test_get_input_json_str(): builder = ContextBuilder('test_function_context') builder.input_ = json.dumps({'city': 'Seattle'}) context = DurableOrchestrationContext.from_json(builder.to_json_string()) result = context.get_input() assert 'Seattle' == result['city']
def add_hello_failed_events( context_builder: ContextBuilder, id_: int, reason: str, details: str): context_builder.add_task_scheduled_event(name='Hello', id_=id_) context_builder.add_orchestrator_completed_event() context_builder.add_orchestrator_started_event() context_builder.add_task_failed_event( id_=id_, reason=reason, details=details)
def add_completed_event(context_builder: ContextBuilder, id_: int, name: str, result): context_builder.add_task_scheduled_event(name=name, id_=id_) context_builder.add_orchestrator_completed_event() context_builder.add_orchestrator_started_event() context_builder.add_task_completed_event(id_=id_, result=json.dumps(result))
def add_failed_http_events(context_builder: ContextBuilder, id_: int, reason: str, details: str): context_builder.add_task_scheduled_event(name=HTTP_ACTION_NAME, id_=id_) context_builder.add_orchestrator_completed_event() context_builder.add_orchestrator_started_event() context_builder.add_task_failed_event(id_=id_, reason=reason, details=details)
def add_hello_suborch_completed_events(context_builder: ContextBuilder, id_: int, result: str): context_builder.add_sub_orchestrator_started_event( name="HelloSubOrchestrator", id_=id_, input_="") context_builder.add_orchestrator_completed_event() context_builder.add_orchestrator_started_event() context_builder.add_sub_orchestrator_completed_event(result=result, id_=id_)
def test_failed_parrot_value(): failed_reason = 'Reasons' failed_details = 'Stuff and Things' activity_count = 5 context_builder = ContextBuilder('test_fan_out_fan_in_function') add_completed_event(context_builder, 0, 'GetActivityCount', activity_count) add_completed_task_set_events(context_builder, 1, 'ParrotValue', activity_count, 2, failed_reason, failed_details) try: result = get_orchestration_state_result( context_builder, generator_function) # we expected an exception assert False except Exception as e: error_label = "\n\n$OutOfProcData$:" error_str = str(e) expected_state = base_expected_state(error=f'{failed_reason} \n {failed_details}') add_single_action(expected_state, function_name='GetActivityCount', input_=None) add_multi_actions(expected_state, function_name='ParrotValue', volume=activity_count) error_msg = f'{failed_reason} \n {failed_details}' expected_state._error = error_msg state_str = expected_state.to_json_string() expected_error_str = f"{error_msg}{error_label}{state_str}" assert expected_error_str == error_str
def test_failed_state(): failed_reason = 'Reasons' failed_details = 'Stuff and Things' context_builder = ContextBuilder('test_simple_function') add_failed_http_events(context_builder, 0, failed_reason, failed_details) try: result = get_orchestration_state_result(context_builder, simple_get_generator_function) # We expected an exception assert False except Exception as e: error_label = "\n\n$OutOfProcData$:" error_str = str(e) expected_state = base_expected_state() request = get_request() add_http_action(expected_state, request) error_msg = f'{failed_reason} \n {failed_details}' expected_state._error = error_msg state_str = expected_state.to_json_string() expected_error_str = f"{error_msg}{error_label}{state_str}" assert expected_error_str == error_str
def test_failed_tokyo_hit_max_attempts(): failed_reason = 'Reasons' failed_details = 'Stuff and Things' context_builder = ContextBuilder('test_simple_function') add_hello_failed_events(context_builder, 0, failed_reason, failed_details) add_retry_timer_events(context_builder, 1) add_hello_failed_events(context_builder, 2, failed_reason, failed_details) add_retry_timer_events(context_builder, 3) add_hello_failed_events(context_builder, 4, failed_reason, failed_details) add_retry_timer_events(context_builder, 5) try: result = get_orchestration_state_result(context_builder, generator_function) # expected an exception assert False except Exception as e: error_label = "\n\n$OutOfProcData$:" error_str = str(e) expected_state = base_expected_state() add_hello_action(expected_state, 'Tokyo') error_msg = f'{failed_reason} \n {failed_details}' expected_state._error = error_msg state_str = expected_state.to_json_string() expected_error_str = f"{error_msg}{error_label}{state_str}" assert expected_error_str == error_str
def test_tokyo_and_seattle_and_london_state_all_failed(): failed_reason = 'Reasons' failed_details = 'Stuff and Things' context_builder = ContextBuilder('test_simple_function') add_hello_suborch_failed_events(context_builder, 0, failed_reason, failed_details) add_retry_timer_events(context_builder, 1) add_hello_suborch_failed_events(context_builder, 2, failed_reason, failed_details) add_retry_timer_events(context_builder, 3) add_hello_suborch_failed_events(context_builder, 4, failed_reason, failed_details) add_retry_timer_events(context_builder, 5) result = get_orchestration_state_result(context_builder, generator_function) expected_state = base_expected_state() add_hello_suborch_action(expected_state, 'Tokyo') expected_state._error = f'{failed_reason} \n {failed_details}' expected = expected_state.to_json() expected_state._is_done = True #assert_valid_schema(result) assert_orchestration_state_equals(expected, result)
def test_timers_comparison_with_relaxed_precision(): """Test if that two `datetime` different but equivalent serializations of timer deadlines are found to be equivalent. The Durable Extension may sometimes drop redundant zeroes on a datetime object. For instance, the date 2020-07-23T21:56:54.936700Z may get transformed into 2020-07-23T21:56:54.9367Z This test ensures that dropping redundant zeroes does not affect our ability to recognize that a timer has been fired. """ # equivalent to 2020-07-23T21:56:54.936700Z relaxed_timestamp = "2020-07-23T21:56:54.9367Z" fire_at = datetime.strptime(relaxed_timestamp, DATETIME_STRING_FORMAT) context_builder = ContextBuilder("relaxed precision") add_timer_fired_events(context_builder, 0, relaxed_timestamp) result = get_orchestration_state_result(context_builder, generator_function) expected_state = base_expected_state(output='Done!') add_timer_action(expected_state, fire_at) expected_state._is_done = True expected = expected_state.to_json() #assert_valid_schema(result) # TODO: getting the following error when validating the schema # "Additional properties are not allowed ('fireAt', 'isCanceled' were unexpected)"> assert_orchestration_state_equals(expected, result)
def test_show_me_the_sum_success(): activity_count = 5 sum_ = 0 for i in range(activity_count): sum_ += i sum_results = f"Well that's nice {sum_}!" context_builder = ContextBuilder('test_fan_out_fan_in_function') add_completed_event(context_builder, 0, 'GetActivityCount', activity_count) add_completed_task_set_events(context_builder, 1, 'ParrotValue', activity_count) add_completed_event(context_builder, activity_count + 1, 'ShowMeTheSum', sum_results) result = get_orchestration_state_result(context_builder, generator_function) expected_state = base_expected_state(sum_results) add_single_action(expected_state, function_name='GetActivityCount', input_=None) add_multi_actions(expected_state, function_name='ParrotValue', volume=activity_count) results = [] for i in range(activity_count): results.append(i) add_single_action(expected_state, function_name='ShowMeTheSum', input_=results) expected_state._is_done = True expected = expected_state.to_json() assert_valid_schema(result) assert_orchestration_state_equals(expected, result)
def test_failed_parrot_value(): failed_reason = 'Reasons' failed_details = 'Stuff and Things' activity_count = 5 context_builder = ContextBuilder('test_fan_out_fan_in_function') add_completed_event(context_builder, 0, 'GetActivityCount', activity_count) add_completed_task_set_events(context_builder, 1, 'ParrotValue', activity_count, 2, failed_reason, failed_details) result = get_orchestration_state_result(context_builder, generator_function) expected_state = base_expected_state( error=f'{failed_reason} \n {failed_details}') add_single_action(expected_state, function_name='GetActivityCount', input_=None) add_multi_actions(expected_state, function_name='ParrotValue', volume=activity_count) expected = expected_state.to_json() assert_valid_schema(result) assert_orchestration_state_equals(expected, result)
def test_tokyo_and_seattle_and_london_with_serialization_state(): """Tests the sequential function pattern with custom object serialization. This simple test validates that a sequential function pattern returns the expected state when the input to activities is a user-provided serializable class. """ context_builder = ContextBuilder('test_simple_function') add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"") add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"") add_hello_completed_events(context_builder, 2, "\"Hello London!\"") result = get_orchestration_state_result( context_builder, generator_function_with_serialization) expected_state = base_expected_state( ['Hello Tokyo!', 'Hello Seattle!', 'Hello London!']) add_hello_action(expected_state, SerializableClass("Tokyo")) add_hello_action(expected_state, SerializableClass("Seattle")) add_hello_action(expected_state, SerializableClass("London")) expected_state._is_done = True expected = expected_state.to_json() assert_valid_schema(result) assert_orchestration_state_equals(expected, result)
def test_is_replaying_initial_value(): context_builder = ContextBuilder("") result = get_orchestration_property( context_builder, generator_function, "durable_context") assert result.is_replaying == False
def test_tokyo_and_seattle_and_london_state_partial_failure(): failed_reason = 'Reasons' failed_details = 'Stuff and Things' context_builder = ContextBuilder('test_simple_function') add_hello_suborch_completed_events(context_builder, 0, "\"Hello Tokyo!\"") add_hello_suborch_failed_events(context_builder, 1, failed_reason, failed_details) add_retry_timer_events(context_builder, 3) add_hello_suborch_completed_events(context_builder, 4, "\"Hello Seattle!\"") add_hello_suborch_completed_events(context_builder, 5, "\"Hello London!\"") result = get_orchestration_state_result(context_builder, generator_function) expected_state = base_expected_state( ['Hello Tokyo!', 'Hello Seattle!', 'Hello London!']) add_hello_suborch_action(expected_state, 'Tokyo') add_hello_suborch_action(expected_state, 'Seattle') add_hello_suborch_action(expected_state, 'London') expected_state._is_done = True expected = expected_state.to_json() #assert_valid_schema(result) assert_orchestration_state_equals(expected, result)
def test_user_code_raises_exception(): context_builder = ContextBuilder('test_simple_function') add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"") add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"") add_hello_completed_events(context_builder, 2, "\"Hello London!\"") try: result = get_orchestration_state_result(context_builder, generator_function_rasing_ex) # expected an exception assert False except Exception as e: error_label = "\n\n$OutOfProcData$:" error_str = str(e) expected_state = base_expected_state() add_hello_action(expected_state, 'Tokyo') add_hello_action(expected_state, 'Seattle') add_hello_action(expected_state, 'London') error_msg = 'Oops!' expected_state._error = error_msg state_str = expected_state.to_json_string() expected_error_str = f"{error_msg}{error_label}{state_str}" assert expected_error_str == error_str
def add_hello_suborch_failed_events(context_builder: ContextBuilder, id_: int, reason: str, details: str): context_builder.add_sub_orchestrator_started_event( name="HelloSubOrchestrator", id_=id_, input_="") context_builder.add_orchestrator_completed_event() context_builder.add_orchestrator_started_event() context_builder.add_sub_orchestrator_failed_event(id_=id_, reason=reason, details=details)
def test_added_function_context_args(): context_builder = ContextBuilder('test_function_context') additional_attributes = { "attrib1": 1, "attrib2": "two", "attrib3": { "randomDictionary": "random" } } context_as_string = context_builder.to_json_string(**additional_attributes) durable_context = DurableOrchestrationContext.from_json(context_as_string) assert durable_context.function_context is not None for key in additional_attributes: assert additional_attributes[key] == getattr( durable_context.function_context, key)
def test_event_raised_return_completed_task(): timestamp = datetime.now() json_input = '{"test":"somecontent"}' expected_action = WaitForExternalEventAction("A") context_builder = ContextBuilder('test_simple_function') context_builder.add_event_raised_event(name="A", input_=json_input, timestamp=timestamp, id_=1) returned_task = wait_for_external_event_task( context_builder.history_events, "A") expected_task = Task(is_completed=True, is_faulted=False, action=expected_action, result=json.loads(json_input), timestamp=timestamp.replace(tzinfo=tzutc()), id_=1) assert_tasks_equal(expected_task, returned_task)
def test_event_not_raised_return_incompleted_task(): context_builder = ContextBuilder('test_simple_function') expected_action = WaitForExternalEventAction("A") returned_task = wait_for_external_event_task( context_builder.history_events, "A") expected_task = Task(is_completed=False, is_faulted=False, action=expected_action) assert_tasks_equal(expected_task, returned_task)
def test_initial_call(): context_builder = ContextBuilder('test_fan_out_fan_in_function') result = get_orchestration_state_result( context_builder, generator_function) expected_state = base_expected_state() add_single_action(expected_state, function_name='GetActivityCount', input_=None) expected = expected_state.to_json() assert_valid_schema(result) assert_orchestration_state_equals(expected, result)
def _complete_event(context: ContextBuilder, id_counter: int) -> Tuple[ContextBuilder, int]: """Add event / task completions to the context. Parameters ---------- context: ContextBuilder Orchestration context mock, to which we'll add the event completion events id_counter: int The current event counter Returns ------- Tuple[ContextBuilder, int] The updated context, the updated id_counter """ for id_, city in zip(scheduled_ids, CITIES): result = f"\"{RESULT_PREFIX}{city}\"" context.add_task_completed_event(id_=id_, result=result) id_counter += 1 return context, id_counter
def test_initial_orchestration_state(): context_builder = ContextBuilder('test_simple_function') result = get_orchestration_state_result(context_builder, generator_function) expected_state = base_expected_state() add_hello_action(expected_state, 'Tokyo') expected = expected_state.to_json() assert_valid_schema(result) assert_orchestration_state_equals(expected, result)
def test_call_entity_sent(): context_builder = ContextBuilder('test_simple_function') entityId = df.EntityId("Counter", "myCounter") result = get_orchestration_state_result( context_builder, generator_function_call_entity) expected_state = base_expected_state() add_call_entity_action(expected_state, entityId, "add", 3) expected = expected_state.to_json() #assert_valid_schema(result) assert_orchestration_state_equals(expected, result)
def test_is_replaying_one_replayed_event(): timestamp = "2020-07-23T21:56:54.9367Z" fire_at = datetime.strptime(timestamp, DATETIME_STRING_FORMAT) + timedelta(seconds=30) fire_at_str = fire_at.strftime(DATETIME_STRING_FORMAT) context_builder = ContextBuilder("") add_timer_fired_events(context_builder, 0, fire_at_str, is_played=True) result = get_orchestration_property( context_builder, generator_function, "durable_context") assert result.is_replaying == True
def _fire_timer(context: ContextBuilder, id_counter: int, deadlines: List[datetime]) -> Tuple[ContextBuilder, int]: """Add timer fired events to the context. Parameters ---------- context: ContextBuilder Orchestration context mock, to which we'll add the event completion events id_counter: int The current event counter deadlines: List[datetime] List of dates at which to fire the timers Returns ------- Tuple[ContextBuilder, int]: The updated context, the updated id_counter """ for id_, fire_at in deadlines: context.add_timer_fired_event(id_=id_, fire_at=fire_at) id_counter += 1 return context, id_counter
def _fail_events(context: ContextBuilder, id_counter: int) -> Tuple[ContextBuilder, int]: """Add event failed to the context. Parameters ---------- context: ContextBuilder Orchestration context mock, to which we'll add the event completion events id_counter: int The current event counter Returns ------- Tuple[ContextBuilder, int]: The updated context, the updated id_counter """ context.add_orchestrator_started_event() for id_ in scheduled_ids: context.add_task_failed_event(id_=id_, reason=REASONS, details=DETAILS) id_counter += 1 return context, id_counter
def test_initial_post_state(): context_builder = ContextBuilder('test_simple_function') result = get_orchestration_state_result(context_builder, complete_generator_function) expected_state = base_expected_state() request = post_request() add_http_action(expected_state, request) expected = expected_state.to_json() assert_valid_schema(result) assert_orchestration_state_equals(expected, result) validate_result_http_request(result)
def test_tokyo_state(): context_builder = ContextBuilder('test_simple_function') add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"") result = get_orchestration_state_result(context_builder, generator_function) expected_state = base_expected_state() add_hello_action(expected_state, 'Tokyo') add_hello_action(expected_state, 'Seattle') expected = expected_state.to_json() assert_valid_schema(result) assert_orchestration_state_equals(expected, result)
def _schedule_events( context: ContextBuilder, id_counter: int) -> Tuple[ContextBuilder, int, List[int]]: """Add scheduled events to the context. Parameters ---------- context: ContextBuilder Orchestration context mock, to which we'll add the event completion events id_counter: int The current event counter Returns ------- Tuple[ContextBuilder, int, List[int]]: The updated context, the updated counter, a list of event IDs for each scheduled event """ scheduled_ids: List[int] = [] for id_ in range(num_activities): scheduled_ids.append(id_) context.add_task_scheduled_event(name='Hello', id_=id_) id_counter += 1 return context, id_counter, scheduled_ids