def test_workflow_runs(self): """ If no errors occur, a workflow should be executed to completion """ func1 = Mock() func2 = Mock() class Task1(WorkflowTask): def run(self): func1() class Task2(WorkflowTask): def run(self): func2() workflow = [ Task1(), Task2(), ] errors = execute_workflow(workflow) self.assertEqual(errors, []) func1.assert_called() func2.assert_called() self.assertEqual(workflow, [])
def test_rollback_runs(self): """ If an error is encountered, the workflow should stop, and the rollback should run to completion """ func1 = Mock() rollback1 = Mock() black_knight_error = ValueError("'Tis but a flesh wound") black_knight = Mock(side_effect=black_knight_error) black_knight_rollback_error = ValueError( "Come back here and take what's comin' ta ya!") black_knight_rollback = Mock(side_effect=black_knight_rollback_error) func3 = Mock(return_value=None) class Task1(WorkflowTask): def run(self): func1() def rollback(self): rollback1() class BlackKnightTask(WorkflowTask): def run(self): black_knight() def rollback(self): black_knight_rollback() class Task3(WorkflowTask): def run(self): func3() black_knight_task = BlackKnightTask() workflow = [ Task1(), black_knight_task, Task3(), ] errors = execute_workflow(workflow) self.assertEqual(errors, [ WorkflowError(black_knight_task, black_knight_error, is_rollback_error=False), WorkflowError(black_knight_task, black_knight_rollback_error, is_rollback_error=True), ]) # Check workflow halted on failure func1.assert_called() black_knight.assert_called() func3.assert_not_called() # Check rollback continued after error black_knight_rollback.assert_called() func1.assert_called()
def test_str(self): class AirspeedVelocityTask(WorkflowTask): def run(self): raise TypeError('Missing argument: african_or_european') workflow = [AirspeedVelocityTask()] errors = execute_workflow(workflow) self.assertEqual( str(errors[0]), 'AirspeedVelocityTask.run() failed: TypeError: Missing argument: african_or_european' )
def test_rollback_runs(self): """ If an error is encountered, the workflow should stop, and the rollback should run to completion """ func1 = Mock() rollback1 = Mock() black_knight_error = ValueError("'Tis but a flesh wound") black_knight = Mock(side_effect=black_knight_error) black_knight_rollback_error = ValueError("Come back here and take what's comin' ta ya!") black_knight_rollback = Mock(side_effect=black_knight_rollback_error) func3 = Mock(return_value=None) class Task1(WorkflowTask): def run(self): func1() def rollback(self): rollback1() class BlackKnightTask(WorkflowTask): def run(self): black_knight() def rollback(self): black_knight_rollback() class Task3(WorkflowTask): def run(self): func3() black_knight_task = BlackKnightTask() workflow = [ Task1(), black_knight_task, Task3(), ] errors = execute_workflow(workflow) self.assertEqual(errors, [ WorkflowError(black_knight_task, black_knight_error, is_rollback_error=False), WorkflowError(black_knight_task, black_knight_rollback_error, is_rollback_error=True), ]) # Check workflow halted on failure func1.assert_called() black_knight.assert_called() func3.assert_not_called() # Check rollback continued after error black_knight_rollback.assert_called() func1.assert_called()
def send_openmrs_data(requests, domain, form_json, openmrs_config, case_trigger_infos): """ Updates an OpenMRS patient and (optionally) creates visits. This involves several requests to the `OpenMRS REST Web Services`_. If any of those requests fail, we want to roll back previous changes to avoid inconsistencies in OpenMRS. To do this we define a workflow of tasks we want to do. Each workflow task has a rollback task. If a task fails, all previous tasks are rolled back in reverse order. :return: A response-like object that can be used by Repeater.handle_response(), RepeatRecord.handle_success() and RepeatRecord.handle_failure() .. _OpenMRS REST Web Services: https://wiki.openmrs.org/display/docs/REST+Web+Services+API+For+Clients """ warnings = [] errors = [] for info in case_trigger_infos: assert isinstance(info, CaseTriggerInfo) try: patient = get_patient(requests, domain, info, openmrs_config) except (RequestException, HTTPError) as err: errors.append( _("Unable to create an OpenMRS patient for case " f"{info.case_id!r}: {err}")) continue if patient is None: warnings.append( f"CommCare case '{info.case_id}' was not matched to a " f"patient in OpenMRS instance '{requests.base_url}'.") continue # case_trigger_infos are info about all of the cases # created/updated by the form. Execute a separate workflow to # update each patient. workflow = [ # Update name first. If the current name in OpenMRS fails # validation, other API requests will be rejected. UpdatePersonNameTask(requests, info, openmrs_config, patient['person']), # Update identifiers second. If a current identifier fails # validation, other API requests will be rejected. SyncPatientIdentifiersTask(requests, info, openmrs_config, patient), # Now we should be able to update the rest. UpdatePersonPropertiesTask(requests, info, openmrs_config, patient['person']), SyncPersonAttributesTask(requests, info, openmrs_config, patient['person']['uuid'], patient['person']['attributes']), ] if patient['person']['preferredAddress']: workflow.append( UpdatePersonAddressTask(requests, info, openmrs_config, patient['person'])) else: workflow.append( CreatePersonAddressTask(requests, info, openmrs_config, patient['person'])) workflow.append( CreateVisitsEncountersObsTask(requests, domain, info, form_json, openmrs_config, patient['person']['uuid']), ) errors.extend(execute_workflow(workflow)) if errors: requests.notify_error( f'Errors encountered sending OpenMRS data: {errors}') # If the form included multiple patients, some workflows may # have succeeded, but don't say everything was OK if any # workflows failed. (Of course most forms will only involve one # case, so one workflow.) return OpenmrsResponse( 400, 'Bad Request', "Errors: " + pformat_json([str(e) for e in errors])) if warnings: return OpenmrsResponse( 201, "Accepted", "Warnings: " + pformat_json([str(e) for e in warnings])) return OpenmrsResponse(200, "OK")
def send_openmrs_data(requests, domain, form_json, openmrs_config, case_trigger_infos, form_question_values): """ Updates an OpenMRS patient and (optionally) creates visits. This involves several requests to the `OpenMRS REST Web Services`_. If any of those requests fail, we want to roll back previous changes to avoid inconsistencies in OpenMRS. To do this we define a workflow of tasks we want to do. Each workflow task has a rollback task. If a task fails, all previous tasks are rolled back in reverse order. :return: A response-like object that can be used by Repeater.handle_response .. _OpenMRS REST Web Services: https://wiki.openmrs.org/display/docs/REST+Web+Services+API+For+Clients """ errors = [] for info in case_trigger_infos: assert isinstance(info, CaseTriggerInfo) patient = get_patient(requests, domain, info, openmrs_config) if patient is None: errors.append('Warning: CommCare case "{}" was not found in OpenMRS'.format(info.case_id)) continue # case_trigger_infos are info about all of the cases # created/updated by the form. Execute a separate workflow to # update each patient. workflow = [ # Update name first. If the current name in OpenMRS fails # validation, other API requests will be rejected. UpdatePersonNameTask(requests, info, openmrs_config, patient['person']), # Update identifiers second. If a current identifier fails # validation, other API requests will be rejected. SyncPatientIdentifiersTask(requests, info, openmrs_config, patient), # Now we should be able to update the rest. UpdatePersonPropertiesTask(requests, info, openmrs_config, patient['person']), SyncPersonAttributesTask( requests, info, openmrs_config, patient['person']['uuid'], patient['person']['attributes'] ), ] if patient['person']['preferredAddress']: workflow.append( UpdatePersonAddressTask(requests, info, openmrs_config, patient['person']) ) else: workflow.append( CreatePersonAddressTask(requests, info, openmrs_config, patient['person']) ) workflow.append( CreateVisitsEncountersObsTask( requests, domain, info, form_json, form_question_values, openmrs_config, patient['person']['uuid'] ), ) errors.extend( execute_workflow(workflow) ) if errors: logger.error('Errors encountered sending OpenMRS data: %s', errors) # If the form included multiple patients, some workflows may # have succeeded, but don't say everything was OK if any # workflows failed. (Of course most forms will only involve one # case, so one workflow.) return OpenmrsResponse(400, 'Bad Request', pformat_json([str(e) for e in errors])) else: return OpenmrsResponse(200, 'OK', '')
def send_openmrs_data(requests, domain, form_json, openmrs_config, case_trigger_infos, form_question_values): """ Updates an OpenMRS patient and (optionally) creates visits. This involves several requests to the `OpenMRS REST Web Services`_. If any of those requests fail, we want to roll back previous changes to avoid inconsistencies in OpenMRS. To do this we define a workflow of tasks we want to do. Each workflow task has a rollback task. If a task fails, all previous tasks are rolled back in reverse order. :return: A response-like object that can be used by Repeater.handle_response .. _OpenMRS REST Web Services: https://wiki.openmrs.org/display/docs/REST+Web+Services+API+For+Clients """ errors = [] for info in case_trigger_infos: assert isinstance(info, CaseTriggerInfo) patient = get_patient(requests, domain, info, openmrs_config) if patient is None: errors.append('Warning: CommCare case "{}" was not found in OpenMRS'.format(info.case_id)) continue # case_trigger_infos are info about all of the cases # created/updated by the form. Execute a separate workflow to # update each patient. workflow = [ # Update name first. If the current name in OpenMRS fails # validation, other API requests will be rejected. UpdatePersonNameTask(requests, info, openmrs_config, patient['person']), # Update identifiers second. If a current identifier fails # validation, other API requests will be rejected. SyncPatientIdentifiersTask(requests, info, openmrs_config, patient), # Now we should be able to update the rest. UpdatePersonPropertiesTask(requests, info, openmrs_config, patient['person']), SyncPersonAttributesTask( requests, info, openmrs_config, patient['person']['uuid'], patient['person']['attributes'] ), ] if patient['person']['preferredAddress']: workflow.append( UpdatePersonAddressTask(requests, info, openmrs_config, patient['person']) ) else: workflow.append( CreatePersonAddressTask(requests, info, openmrs_config, patient['person']) ) workflow.append( CreateVisitsEncountersObsTask( requests, domain, info, form_json, form_question_values, openmrs_config, patient['person']['uuid'] ), ) errors.extend( execute_workflow(workflow) ) if errors: logger.error('Errors encountered sending OpenMRS data: %s', errors) # If the form included multiple patients, some workflows may # have succeeded, but don't say everything was OK if any # workflows failed. (Of course most forms will only involve one # case, so one workflow.) return OpenmrsResponse(400, 'Bad Request', pformat_json([str(e) for e in errors])) else: return OpenmrsResponse(200, 'OK', '')