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 = FileUtilities.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_payload_id_matches_manifest_id(self): with self.subTest("Incorrect manifest occurrences returns 500 error"): dictionary = { 'action': "urn:nhs-itk:services:201005:SendNHS111Report", 'service': "urn:nhs-itk:services:201005:SendNHS111Report", 'manifestCount': "2", 'manifests': [{"id": 'one'}, {'id': 'one'}], 'payloadCount': "2", 'payloads': [{"id": 'one'}, {'id': "two"}] } expected = FileUtilities.get_file_string( str(self.expectedXmlFileDir / 'payloadID_does_not_match_manifestID.xml')) msg = self.builder.build_message(dictionary) message_handler = MessageHandler(msg) self.assertEqual(message_handler.error_flag, True) XmlUtilities.assert_xml_equal_utf_8(expected, message_handler.get_response()) with self.subTest("Incorrect manifest occurrences returns 500 error"): dictionary = { 'action': "urn:nhs-itk:services:201005:SendNHS111Report", 'service': "urn:nhs-itk:services:201005:SendNHS111Report", 'manifestCount': "2", 'manifests': [{"id": 'one'}, {'id': "two"}], 'payloadCount': "2", 'payloads': [{"id": 'one'}, {'id': "two"}] } msg = self.builder.build_message(dictionary) message_handler = MessageHandler(msg) self.assertEqual(message_handler.error_flag, False) XmlUtilities.assert_xml_equal_utf_8(self.success_response, message_handler.get_response())
def test_manifest_payload_count(self): with self.subTest("Mismatched counts: 500 response"): counts = { 'action': "urn:nhs-itk:services:201005:SendNHS111Report", 'service': "urn:nhs-itk:services:201005:SendNHS111Report", 'manifestCount': "1", 'manifests': [{"id": 'one'}], 'payloadCount': "2", 'payloads': [{"id": 'one'}, {'id': "two"}] } expected = FileUtilities.get_file_string( str(self.expectedXmlFileDir / 'manifest_not_equal_to_payload_count.xml')) msg = self.builder.build_message(counts) message_handler = MessageHandler(msg) self.assertEqual(message_handler.error_flag, True) XmlUtilities.assert_xml_equal_utf_8(expected, message_handler.get_response()) with self.subTest("Equal counts: 200 response"): counts = { 'action': "urn:nhs-itk:services:201005:SendNHS111Report", 'service': "urn:nhs-itk:services:201005:SendNHS111Report", 'manifestCount': "2", 'manifests': [{"id": 'one'}, {"id": "two"}], 'payloadCount': "2", 'payloads': [{"id": 'one'}, {'id': "two"}] } msg = self.builder.build_message(counts) message_handler = MessageHandler(msg) self.assertEqual(message_handler.error_flag, False) XmlUtilities.assert_xml_equal_utf_8(self.success_response, message_handler.get_response())
def test_action_not_matching_service(self): with self.subTest("Two differing services result in a 500 error"): service_dict = {'action': "urn:nhs-itk:services:201005:SendNHS111Report-v2-0-ThisDoesNotMatchBelow", 'service': "urn:nhs-itk:services:201005:SendNHS111Report-Bad_Service-ThisDoesNotMatchAbove", 'manifestCount': 0, 'payloadCount': 0 } expected = FileUtilities.get_file_string( str(self.expectedXmlFileDir / 'invalid_action_service_values_response.xml')) msg = self.builder.build_message(service_dict) message_handler = MessageHandler(msg) self.assertEqual(message_handler.error_flag, True) XmlUtilities.assert_xml_equal_utf_8(expected, message_handler.get_response()) with self.subTest("Two services which are the same should return 200 code"): service_dict = {'action': "urn:nhs-itk:services:201005:SendNHS111Report", 'service': "urn:nhs-itk:services:201005:SendNHS111Report", 'manifestCount': 0, 'payloadCount': 0 } msg = self.builder.build_message(service_dict) message_handler = MessageHandler(msg) self.assertEqual(message_handler.error_flag, False) XmlUtilities.assert_xml_equal_utf_8(self.success_response, message_handler.get_response())
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 = FileUtilities.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_parse_message(self): with self.subTest("A valid request containing a payload"): message = FileUtilities.get_file_string( str(message_dir / "ebxml_request.msg")) expected_values_with_payload = expected_values( message=EXPECTED_MESSAGE) extracted_values = self.parser.parse_message( MULTIPART_MIME_HEADERS, message) self.assertEqual(expected_values_with_payload, extracted_values) with self.subTest("An invalid multi-part MIME message"): message = FileUtilities.get_file_string( str(message_dir / "ebxml_request_no_header.msg")) with (self.assertRaises(EbXmlParsingError)): self.parser.parse_message(MULTIPART_MIME_HEADERS, message) with self.subTest( "A valid request that does not contain the optional payload MIME part" ): message = FileUtilities.get_file_string( str(message_dir / "ebxml_request_no_payload.msg")) expected_values_with_no_payload = expected_values() extracted_values = self.parser.parse_message( MULTIPART_MIME_HEADERS, message) self.assertEqual(expected_values_with_no_payload, extracted_values) with self.subTest( "A valid request containing an additional MIME part"): message = FileUtilities.get_file_string( str(message_dir / "ebxml_request_additional_attachment.msg")) expected_values_with_payload = expected_values( message=EXPECTED_MESSAGE) extracted_values = self.parser.parse_message( MULTIPART_MIME_HEADERS, message) self.assertEqual(expected_values_with_payload, extracted_values) with self.subTest("An message that is not a multi-part MIME message"): with (self.assertRaises(EbXmlParsingError)): self.parser.parse_message( {CONTENT_TYPE_HEADER_NAME: "text/plain"}, "A message")
def test_parse_message_with_no_values(self): message = FileUtilities.get_file_string( str(message_dir / "ebxml_header_empty.xml")) extracted_values = self.parser.parse_message(MULTIPART_MIME_HEADERS, message) self.assertEqual({}, extracted_values)
def test_parse_message(self): message = FileUtilities.get_file_string( str(message_dir / "ebxml_header.xml")) expected_values_without_message = expected_values() extracted_values = self.parser.parse_message(MULTIPART_MIME_HEADERS, message) self.assertEqual(expected_values_without_message, extracted_values)
def test_multiple_errors(self): message = FileUtilities.get_file_string( self.message_dir / 'ebxml_response_error_multiple.xml') response = handle_ebxml_error(200, {'Content-Type': 'text/xml'}, message)[1] self.assertIn('501319:Unknown eb:CPAId', response) self.assertIn('501320:Unknown something else', response) self.assertIn('errorType=ebxml_error', response)
def test_should_return_error_when_there_is_a_bad_attribute_format(self): input_xml = FileUtilities.get_file_string( str(self.xmlFileDir / 'badAttributeParse.xml')) parsed_data = self.gp_summary_upload_templator.parse_response( input_xml) self.assertEqual( parsed_data['error'], 'Failed to parse all the necessary elements from xml returned from MHS' )
def test_python_dictionary_example(self): """ Basic test to demonstrate passing a python dict to the interface instead of a json file """ expected_string = FileUtilities.get_file_string( str(Path(ROOT_DIR) / 'scr/tests/test_xmls/cleanSummaryUpdate.xml')) from scr.tests.hashes.basic_dict import input_hash render = self.summaryCareRecord.populate_template(input_hash) XmlUtilities.assert_xml_equal(expected_string, render)
def test_should_return_error_when_required_details_tag_is_missing(self): input_xml = FileUtilities.get_file_string( str(self.xmlFileDir / 'missingAttribute.xml')) parsed_data = self.gp_summary_upload_templator.parse_response( input_xml) self.assertEqual( parsed_data['error'], 'Failed to parse all the necessary elements from xml returned from MHS' )
def test_post(self, mock_get_uuid, mock_get_timestamp): mock_get_uuid.return_value = "5BB171D4-53B2-4986-90CF-428BE6D157F5" mock_get_timestamp.return_value = "2012-03-15T06:51:08Z" expected_ack_response = FileUtilities.get_file_string( str(self.message_dir / EXPECTED_RESPONSE_FILE)) request_body = FileUtilities.get_file_string( str(self.message_dir / REQUEST_FILE)) mock_callback = Mock() self.callbacks[REF_TO_MESSAGE_ID] = mock_callback ack_response = self.fetch("/", method="POST", body=request_body, headers=CONTENT_TYPE_HEADERS) self.assertEqual(ack_response.code, 200) self.assertEqual(ack_response.headers["Content-Type"], "text/xml") XmlUtilities.assert_xml_equal(expected_ack_response, ack_response.body) mock_callback.assert_called_with(EXPECTED_MESSAGE)
def test_empty_hash(self): """ Tests the contents are empty when a completely blank hash is provided """ expected_xml_file_path = str(self.xmlFileDir / 'EmptyHash.xml') hash_file_path = str(self.hashFileDir / 'EmptyHash.json') expected_string = FileUtilities.get_file_string(expected_xml_file_path) render = self.summaryCareRecord.populate_template_with_file( hash_file_path) XmlUtilities.assert_xml_equal(expected_string, render)
def test_json_string_example(self): """ Basic example showing how a json string can be passed to the interface """ expected_string = FileUtilities.get_file_string( str(self.xmlFileDir / 'cleanSummaryUpdate.xml')) json_file = str(self.hashFileDir / 'hash16UK05.json') with open(json_file) as file: data = file.read() # Reads file contents into a string render = self.summaryCareRecord.populate_template_with_json_string( data) XmlUtilities.assert_xml_equal(expected_string, render)
def test_extended_html(self): """ Uses a larger set of Html for the human readable contents """ expected_xml_file_path = str(self.xmlFileDir / 'SummaryUpdateExtendedContents.xml') hash_file_path = str(self.hashFileDir / 'extendedHTMLhash.json') expected_string = FileUtilities.get_file_string(expected_xml_file_path) render = self.summaryCareRecord.populate_template_with_file( hash_file_path) XmlUtilities.assert_xml_equal(expected_string, render)
def test_post_no_callback(self): # If there is no callback registered for the message ID the response is in reference to, an HTTP 500 should be # returned. request_body = FileUtilities.get_file_string( str(self.message_dir / REQUEST_FILE)) response = self.fetch("/", method="POST", body=request_body, headers=CONTENT_TYPE_HEADERS) self.assertEqual(response.code, 500)
def test_empty_html(self): """ A test for an empty human readable content value """ expected_xml_file_path = str(self.xmlFileDir / 'EmptyHtmlGpSummaryUpdate.xml') hash_file_path = str(self.hashFileDir / 'emptyHtmlHash.json') expected_string = FileUtilities.get_file_string(expected_xml_file_path) render = self.summaryCareRecord.populate_template_with_file( hash_file_path) XmlUtilities.assert_xml_equal(expected_string, render)
def test_basic(self): """ A basic test using the clean summary update from the spine tests """ expected_xml_file_path = str(self.xmlFileDir / 'cleanSummaryUpdate.xml') hash_file_path = str(self.hashFileDir / 'hash16UK05.json') expected_string = FileUtilities.get_file_string(expected_xml_file_path) render = self.summaryCareRecord.populate_template_with_file( hash_file_path) XmlUtilities.assert_xml_equal(expected_string, render)
def get_asid(): """ Looks up the asid from the environment settings The asid should be set in the 'Environment variables' section of the Run/Debug Configurations if this is not set, it will read from 'asid.txt' (excluded from the repo) or default to '123456789012' if 'asid.txt' is not found """ try: asid_file = str(Path(ROOT_DIR) / "data/certs/asid.txt") asid = FileUtilities.get_file_string(asid_file) except: asid = None return os.environ.get('INTEGRATION_TEST_ASID', asid)
def test_multipleReplacementOf(self): """ Note: THIS IS NOT A VALID XML INSTANCE This test is build purely for demonstrating having a variable number of occurrences of a partial in mustache, it does not conform to the schema and will not be accepted as a valid message """ expected_xml_file_path = str(self.xmlFileDir / 'multipleReplacementOf.xml') hash_file_path = str(self.hashFileDir / 'multiReplacementOfhash.json') expected_string = FileUtilities.get_file_string(expected_xml_file_path) render = self.summaryCareRecord.populate_template_with_file( hash_file_path) XmlUtilities.assert_xml_equal(expected_string, render)
def test_replacementOf(self): """ Note: this is not a valid xml instance This is to demonstrate the condition aspect of the replacementOf partial, this partial doesnt appear in the previous tests but here a list with a single element is used to show how conditionals are used in mustache """ expected_xml_file_path = str(self.xmlFileDir / 'replacementOf.xml') hash_file_path = str(self.hashFileDir / 'replacementOfhash.json') expected_string = FileUtilities.get_file_string(expected_xml_file_path) render = self.summaryCareRecord.populate_template_with_file( hash_file_path) XmlUtilities.assert_xml_equal(expected_string, render)
def test_convert_to_edifact(self): TEST_DIR = os.path.dirname(os.path.abspath(__file__)) expected_file_path = Path(TEST_DIR) / "edifact.txt" expected_edifact_interchange = FileUtilities.get_file_string(expected_file_path) incoming_file_path = Path(TEST_DIR) / "patient-register-birth.json" patient_register_json = FileUtilities.get_file_dict(incoming_file_path) outgoing_adaptor = OutgoingAdaptor(operation_dict) (sender, recipient, interchange_seq_no, edifact_interchange) = outgoing_adaptor.convert_to_edifact( patient_register_json) pretty_edifact_interchange = "'\n".join(edifact_interchange.split("'")) self.assertEqual(pretty_edifact_interchange, expected_edifact_interchange)
def test_from_string_single(self): message = FileUtilities.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')
def test_should_return_success_response_with_valid_json(self): """ Simple assertion of correct values returned from the response parsing method """ input_xml = FileUtilities.get_file_string( str(self.xmlFileDir / 'parseSuccessResponse.xml')) parsed_data = self.gp_summary_upload_templator.parse_response( input_xml) self.assertEqual(parsed_data['messageRef'], '9C534C19-C587-4463-9AED-B76F715D3EA3') self.assertEqual(parsed_data['messageId'], '2E372546-229A-483F-9B11-EF46ABF3178C') self.assertEqual(parsed_data['creationTime'], '20190923112609') self.assertEqual(parsed_data['messageDetail'], 'GP Summary upload successful')
class Check(ABC): soap_body = "./soap:Body" distribution_envelope = soap_body + "/itk:DistributionEnvelope" manifest_tag = distribution_envelope + "/itk:header/itk:manifest" basic_success_message = FileUtilities.get_file_string( str(XML_PATH / 'basic_success_response.xml')) namespaces = { 'soap': 'http://schemas.xmlsoap.org/soap/envelope/', 'a': 'http://www.etis.fskab.se/v1.0/ETISws', 'wsa': 'http://www.w3.org/2005/08/addressing', 'itk': 'urn:nhs-itk:ns:201005' } def __init__(self, message): self.message_tree = message @abstractmethod def check(self): """ An abstract method called by the validator to run the check :return: fail flag, error message """ pass def get_manifest_count(self): """ Extracts the count on the manifest tag in the message :return: manifest count as a string """ return self.message_tree.find(self.manifest_tag, self.namespaces).attrib['count'] def get_payload_count(self): """ Extracts the count on the payloads tag in the message :return: payloads count as a string """ return self.message_tree.find( self.distribution_envelope + "/itk:payloads", self.namespaces).attrib['count'] def build_error_message(self, error): builder = PystacheMessageBuilder(str(TEMPLATE_PATH), 'base_error_template') return builder.build_message({"errorMessage": error})
def test_covert_to_fhir(self): """ Test when the operation is for a birth registration """ TEST_DIR = os.path.dirname(os.path.abspath(__file__)) expected_file_path = Path(TEST_DIR) / "patient-register-birth-approval.json" patient_register_approval_json = FileUtilities.get_file_dict(expected_file_path) incoming_file_path = Path(TEST_DIR) / "edifact.txt" incoming_interchange_raw = FileUtilities.get_file_string(incoming_file_path) incoming_adaptor = IncomingAdaptor(reference_dict) op_defs = incoming_adaptor.convert_to_fhir(incoming_interchange_raw) op_def_to_compare = op_defs[0][2] compare(op_def_to_compare, patient_register_approval_json)
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, # Set number of retries to 1 inbound_queue_max_retries=1, inbound_queue_retry_delay=INBOUND_QUEUE_RETRY_DELAY, max_request_size=MAX_REQUEST_SIZE, persistence_store_max_retries=3, 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 = FileUtilities.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_build_message(self, mock_get_uuid, mock_get_timestamp): mock_get_uuid.return_value = MOCK_UUID mock_get_timestamp.return_value = "2012-03-15T06:51:08Z" expected_message = FileUtilities.get_file_string(str(self.expected_message_dir / EXPECTED_EBXML)) message_id, message = self.builder.build_message({ FROM_PARTY_ID: "TESTGEN-201324", TO_PARTY_ID: "YEA-0000806", CPA_ID: "S1001A1630", CONVERSATION_ID: "79F49A34-9798-404C-AEC4-FD38DD81C138", RECEIVED_MESSAGE_TIMESTAMP: "2013-04-16T07:52:09Z", RECEIVED_MESSAGE_ID: "0CDBA95F-74DA-47E9-8383-7B8E9167D146", }) expected_message_bytes = expected_message.encode() message_bytes = message.encode() self.assertEqual(MOCK_UUID, message_id) XmlUtilities.assert_xml_equal(expected_message_bytes, message_bytes)
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 = FileUtilities.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()