async def test_retry_interval_contract_property_is_invalid(self):
        self.setup_mock_work_description()
        self._setup_routing_mock()

        self.mock_routing_reliability.get_reliability.return_value = test_utilities.awaitable(
            {
                workflow.common_asynchronous.MHS_RETRY_INTERVAL:
                MHS_RETRY_INTERVAL_INVALID_VAL,
                workflow.common_asynchronous.MHS_RETRIES: MHS_RETRY_VAL
            })

        self.mock_ebxml_request_envelope.return_value.serialize.return_value = (
            MESSAGE_ID, {}, SERIALIZED_MESSAGE)

        message = file_utilities.get_file_string(
            Path(self.test_message_dir) /
            'soapfault_response_single_error.xml')

        response = httpclient.HTTPResponse
        response.code = 500
        response.body = message
        response.headers = {'Content-Type': 'text/xml'}

        self.mock_transmission_adaptor.make_request.return_value = test_utilities.awaitable(
            response)

        status, message, _ = await self.workflow.handle_outbound_message(
            None, MESSAGE_ID, CORRELATION_ID, INTERACTION_DETAILS, PAYLOAD,
            None)

        self.assertEqual(500, status)
        self.assertTrue('Error when converting retry interval' in message)
        self.assertEqual(
            [mock.call(MessageStatus.OUTBOUND_MESSAGE_PREPARATION_FAILED)],
            self.mock_work_description.set_outbound_status.call_args_list)
예제 #2
0
    def test_from_string_with_no_values(self):
        message = file_utilities.get_file_string(
            str(self.message_dir / "ebxml_header_empty.xml"))

        with self.assertRaisesRegex(ebxml_envelope.EbXmlParsingError,
                                    "Weren't able to find required element"):
            ebxml_ack_envelope.EbxmlAckEnvelope.from_string({}, message)
    def test_get_file_string(self):
        test_file = os.path.join(self.test_files_dir, TEST_FILE)

        loaded_string = file_utilities.get_file_string(test_file)

        self.assertEqual(EXPECTED_STRING, loaded_string,
                         "The string loaded should match the one expected.")
예제 #4
0
    def test_multiple_errors(self):
        message = file_utilities.get_file_string(Path(self.message_dir) / 'soapfault_response_multiple_errors.xml')
        resp_json = json.loads(handle_soap_error(500, {'Content-Type': 'text/xml'}, message)[1])

        self.assert_json_error_root(resp_json)
        self.assert_json_with_first_error(resp_json)
        self.assert_json_with_second_error(resp_json)
    async def test_soap_error_request_is_non_retriable(self):
        self.setup_mock_work_description()
        self._setup_routing_mock()

        self.mock_ebxml_request_envelope.return_value.serialize.return_value = (
            MESSAGE_ID, {}, SERIALIZED_MESSAGE)

        response = mock.MagicMock()
        response.code = 500
        response.headers = {'Content-Type': 'text/xml'}
        # a non retriable soap 300 error code
        response.body = file_utilities.get_file_string(
            Path(self.test_message_dir) /
            'soapfault_response_single_error_300.xml')
        self.mock_transmission_adaptor.make_request.return_value = test_utilities.awaitable(
            response)

        with mock.patch(
                'utilities.config.get_config',
                return_value='localhost/reliablemessaging/queryrequest'):
            status, message, _ = await self.workflow.handle_outbound_message(
                None, MESSAGE_ID, CORRELATION_ID, INTERACTION_DETAILS, PAYLOAD,
                None)

        self.mock_transmission_adaptor.make_request.assert_called_once()
예제 #6
0
    def test_from_string(self):
        message = file_utilities.get_file_string(
            str(self.message_dir / "ebxml_header.xml"))

        parsed_message = ebxml_ack_envelope.EbxmlAckEnvelope.from_string(
            {}, message)

        self.assertEqual(EXPECTED_VALUES, parsed_message.message_dictionary)
예제 #7
0
    def test_single_error(self):
        message = file_utilities.get_file_string(
            self.message_dir / 'ebxml_response_error_single.xml')
        resp_json = json.loads(
            handle_ebxml_error(200, {'Content-Type': 'text/xml'}, message)[1])

        self.assert_json_error_root(resp_json)
        self.assert_json_with_first_error(resp_json)
    def setUp(self):
        self.expected_message_dir = Path(ROOT_DIR) / EXPECTED_MESSAGE_DIR
        self.test_message_dir = Path(ROOT_DIR) / TEST_MESSAGE_DIR

        expected_message = file_utilities.get_file_string(
            str(self.expected_message_dir / EXPECTED_SOAP))
        # Pystache does not convert line endings to LF in the same way as Python does when loading the example from
        # file, so normalize the line endings of the strings being compared
        self.normalized_expected_serialized_message = file_utilities.normalize_line_endings(
            expected_message)
    def test_cant_find_optional_text_value_during_parsing(self):
        message = file_utilities.get_file_string(
            str(self.message_dir / "ebxml_header.xml"))

        xml_tree = ElementTree.fromstring(message)
        values_dict = {}
        ebxml_envelope.EbxmlEnvelope._add_if_present(
            values_dict, 'key',
            ebxml_envelope.EbxmlEnvelope._extract_ebxml_text_value(
                xml_tree, 'nonExistentElement'))

        self.assertEqual({}, values_dict)
    def test_from_string(self):
        with self.subTest("A valid request containing a payload"):
            message = file_utilities.get_file_string(
                str(self.expected_message_dir / EXPECTED_SOAP))
            expected_values_with_payload = expected_values(
                message=EXPECTED_MESSAGE)

            parsed_message = soap_envelope.SoapEnvelope.from_string(
                SOAP_HEADERS, message)

            self.assertEqual(expected_values_with_payload,
                             parsed_message.message_dictionary)

        with self.subTest("A soap message with missing message id"):
            message = file_utilities.get_file_string(
                str(self.test_message_dir / "soap_request_with_defect.msg"))

            with self.assertRaisesRegex(
                    soap_envelope.SoapParsingError,
                    "Weren't able to find required element message_id "
                    "during parsing of SOAP message"):
                soap_envelope.SoapEnvelope.from_string(SOAP_HEADERS, message)
예제 #11
0
    def test_from_string_single(self):
        message = file_utilities.get_file_string(
            self.message_dir / 'ebxml_response_error_single.xml')
        ebxml_error: EbxmlErrorEnvelope = EbxmlErrorEnvelope.from_string(
            message)
        self.assertEqual(len(ebxml_error.errors), 1)

        self.assertEqual(ebxml_error.errors[0]['codeContext'],
                         'urn:oasis:names:tc:ebxml-msg:service:errors')
        self.assertEqual(ebxml_error.errors[0]['errorCode'],
                         'ValueNotRecognized')
        self.assertEqual(ebxml_error.errors[0]['severity'], 'Error')
        self.assertEqual(ebxml_error.errors[0]['Description'],
                         '501319:Unknown eb:CPAId')
    async def test_well_formed_soap_error_response_from_spine(
            self, audit_log_mock, mock_sleep):
        self.setup_mock_work_description()
        self._setup_routing_mock()
        mock_sleep.return_value = test_utilities.awaitable(None)

        self.mock_ebxml_request_envelope.return_value.serialize.return_value = (
            MESSAGE_ID, {}, SERIALIZED_MESSAGE)

        message = file_utilities.get_file_string(
            Path(self.test_message_dir) /
            'soapfault_response_single_error.xml')

        response = httpclient.HTTPResponse
        response.code = 500
        response.body = message
        response.headers = {'Content-Type': 'text/xml'}

        self.mock_transmission_adaptor.make_request.return_value = test_utilities.awaitable(
            response)

        with mock.patch(
                'utilities.config.get_config',
                return_value='localhost/reliablemessaging/queryrequest'):
            status, message, _ = await self.workflow.handle_outbound_message(
                None, MESSAGE_ID, CORRELATION_ID, INTERACTION_DETAILS, PAYLOAD,
                None)
        resp_json = json.loads(message)

        self.assertEqual(500, status)
        self.assertEqual(
            resp_json['error_message'],
            "Error(s) received from Spine. Contact system administrator.")
        self.assertEqual(resp_json['process_key'], "SOAP_ERROR_HANDLER0002")
        self.assertEqual(resp_json['errors'][0]['codeContext'],
                         "urn:nhs:names:error:tms")
        self.assertEqual(resp_json['errors'][0]['description'],
                         "System failure to process message - default")
        self.assertEqual(resp_json['errors'][0]['errorCode'], "200")
        self.assertEqual(resp_json['errors'][0]['errorType'], "soap_fault")
        self.assertEqual(resp_json['errors'][0]['severity'], "Error")

        self.mock_work_description.publish.assert_called_once()
        self.assertEqual(
            [mock.call(MessageStatus.OUTBOUND_MESSAGE_NACKD)],
            self.mock_work_description.set_outbound_status.call_args_list)
        audit_log_mock.assert_called_once_with(
            'Outbound {WorkflowName} workflow invoked.',
            fparams={'WorkflowName': 'forward-reliable'})
예제 #13
0
    def test_from_string_single(self):
        message = file_utilities.get_file_string(
            Path(self.message_dir) / 'soapfault_response_single_error.xml')
        fault: SOAPFault = SOAPFault.from_string({}, message)
        self.assertEqual(fault.fault_code, 'SOAP:Server')
        self.assertEqual(fault.fault_string, 'Application Exception')
        self.assertEqual(len(fault.error_list), 1)

        self.assertEqual(fault.error_list[0]['codeContext'],
                         'urn:nhs:names:error:tms')
        self.assertEqual(fault.error_list[0]['errorCode'], '200')
        self.assertEqual(fault.error_list[0]['severity'], 'Error')
        self.assertEqual(fault.error_list[0]['location'], 'Not Supported')
        self.assertEqual(fault.error_list[0]['description'],
                         'System failure to process message - default')
    async def test_soap_error_request_is_retriable(self, mock_sleep):
        self.setup_mock_work_description()
        self._setup_routing_mock()
        mock_sleep.return_value = test_utilities.awaitable(None)

        self.mock_ebxml_request_envelope.return_value.serialize.return_value = (
            MESSAGE_ID, {}, SERIALIZED_MESSAGE)

        sub_tests = [("a retriable soap 200 error code",
                      'soapfault_response_single_error.xml'),
                     ("a retriable soap 206 error code",
                      'soapfault_response_single_error_206.xml'),
                     ("a retriable soap 208 error code",
                      'soapfault_response_single_error_208.xml')]
        for description, soap_fault_file_path in sub_tests:
            with self.subTest(description):
                try:
                    response = mock.MagicMock()
                    response.code = 500
                    response.headers = {'Content-Type': 'text/xml'}
                    response.body = file_utilities.get_file_string(
                        Path(self.test_message_dir) / soap_fault_file_path)
                    self.mock_transmission_adaptor.make_request.return_value = test_utilities.awaitable(
                        response)

                    with mock.patch(
                            'utilities.config.get_config',
                            return_value=
                            'localhost/reliablemessaging/queryrequest'):
                        await self.workflow.handle_outbound_message(
                            None, MESSAGE_ID, CORRELATION_ID,
                            INTERACTION_DETAILS, PAYLOAD, None)

                    self.assertEqual(
                        self.mock_transmission_adaptor.make_request.call_count,
                        4)
                    self.assertEqual(mock_sleep.call_count, 3)
                    mock_sleep.assert_called_with(
                        MHS_RETRY_INTERVAL_VAL_IN_SECONDS)
                finally:
                    self.mock_transmission_adaptor.make_request.reset_mock()
                    mock_sleep.reset_mock()
    def test_from_string_errors_on_invalid_request(self):
        with self.subTest("A message that is not a multi-part MIME message"):
            with self.assertRaises(ebxml_envelope.EbXmlParsingError):
                ebxml_request_envelope.EbxmlRequestEnvelope.from_string({CONTENT_TYPE_HEADER_NAME: "text/plain"},
                                                                        "A message")

        sub_tests = [
            ("An invalid multi-part MIME message", "ebxml_request_no_header.msg"),
            ("A message with an invalid binary ebXML header", "ebxml_request_invalid_binary_ebxml_header.msg"),
            ("A message with an invalid binary HL7 payload", "ebxml_request_invalid_binary_hl7_payload.msg"),
            ("A message with an invalid application/xml binary HL7 payload",
             "ebxml_request_invalid_application_xml_binary_hl7_payload.msg")
        ]
        for sub_test_name, filename in sub_tests:
            with self.subTest(sub_test_name):
                message = file_utilities.get_file_string(
                    str(self.message_dir / filename))

                with self.assertRaises(ebxml_envelope.EbXmlParsingError):
                    ebxml_request_envelope.EbxmlRequestEnvelope.from_string(MULTIPART_MIME_HEADERS, message)
    async def test_soap_error_request_retry_logic_makes_two_requests_if_retry_is_set_to_one(
            self, mock_sleep):
        self.workflow = forward_reliable.AsynchronousForwardReliableWorkflow(
            party_key=FROM_PARTY_KEY,
            persistence_store=self.mock_persistence_store,
            transmission=self.mock_transmission_adaptor,
            queue_adaptor=self.mock_queue_adaptor,
            max_request_size=MAX_REQUEST_SIZE,
            routing=self.mock_routing_reliability)

        self.setup_mock_work_description()
        self._setup_routing_mock()
        mock_sleep.return_value = test_utilities.awaitable(None)

        self.mock_ebxml_request_envelope.return_value.serialize.return_value = (
            MESSAGE_ID, {}, SERIALIZED_MESSAGE)

        error_response = mock.MagicMock()
        error_response.code = 500
        error_response.headers = {'Content-Type': 'text/xml'}
        error_response.body = file_utilities.get_file_string(
            Path(self.test_message_dir) /
            'soapfault_response_single_error.xml')

        success_response = mock.MagicMock()
        success_response.code = 202

        self.mock_transmission_adaptor.make_request.side_effect = [
            test_utilities.awaitable(error_response),
            test_utilities.awaitable(success_response)
        ]
        with mock.patch(
                'utilities.config.get_config',
                return_value='localhost/reliablemessaging/queryrequest'):
            await self.workflow.handle_outbound_message(
                None, MESSAGE_ID, CORRELATION_ID, INTERACTION_DETAILS, PAYLOAD,
                None)

        self.assertEqual(
            self.mock_transmission_adaptor.make_request.call_count, 2)
        mock_sleep.assert_called_once_with(MHS_RETRY_INTERVAL_VAL_IN_SECONDS)
예제 #17
0
    def test_serialize(self, mock_get_uuid, mock_get_timestamp):
        mock_get_uuid.return_value = test_ebxml_envelope.MOCK_UUID
        mock_get_timestamp.return_value = test_ebxml_envelope.MOCK_TIMESTAMP
        expected_message = file_utilities.get_file_string(
            str(self.expected_message_dir / EXPECTED_EBXML))
        expected_http_headers = {
            'charset': 'UTF-8',
            'SOAPAction':
            'urn:oasis:names:tc:ebxml-msg:service/Acknowledgment',
            'Content-Type': 'text/xml'
        }

        envelope = ebxml_ack_envelope.EbxmlAckEnvelope(
            get_test_message_dictionary())

        message_id, http_headers, message = envelope.serialize()

        expected_message_bytes = expected_message.encode()
        message_bytes = message.encode()

        self.assertEqual(test_ebxml_envelope.MOCK_UUID, message_id)
        self.assertEqual(expected_http_headers, http_headers)
        xml_utilities.XmlUtilities.assert_xml_equal(expected_message_bytes,
                                                    message_bytes)
예제 #18
0
 def test_is_ebxml_negative(self):
     message = file_utilities.get_file_string(self.message_dir /
                                              'ebxml_header.xml')
     self.assertFalse(
         EbxmlErrorEnvelope.is_ebxml_error(ElementTree.fromstring(message)))
예제 #19
0
 def test_soap_fault_multiple(self):
     message = file_utilities.get_file_string(
         Path(self.message_dir) / 'soapfault_response_multiple_errors.xml')
     self.assertTrue(
         SOAPFault.is_soap_fault(ElementTree.fromstring(message)))
예제 #20
0
 def test_is_soap_negative(self):
     message = file_utilities.get_file_string(
         Path(self.message_dir) / 'ebxml_header.xml')
     self.assertFalse(
         SOAPFault.is_soap_fault(ElementTree.fromstring(message)))
 def _get_expected_file_string(self, filename: str):
     # Pystache does not convert line endings to LF in the same way as Python does when loading the example from
     # file, so normalize the line endings of the strings being compared
     return file_utilities.normalize_line_endings(
         file_utilities.get_file_string(str(self.expected_message_dir / filename)))
예제 #22
0
 def test_ebxml_error_multiple(self):
     message = file_utilities.get_file_string(
         self.message_dir / 'ebxml_response_error_multiple.xml')
     self.assertTrue(
         EbxmlErrorEnvelope.is_ebxml_error(ElementTree.fromstring(message)))