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)
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.")
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()
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)
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)
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'})
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)
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)
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)))
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)))
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)))
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)))