Exemple #1
0
    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, [])
Exemple #2
0
    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()
Exemple #3
0
    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'
        )
Exemple #4
0
    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()
Exemple #5
0
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")
Exemple #6
0
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', '')
Exemple #7
0
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', '')