class SummaryCareRecord: summaryCareRecordPath = Path(ROOT_DIR) / "data/templates" def __init__(self): self.builder = PystacheMessageBuilder(str(self.summaryCareRecordPath), "16UK05") def populate_template_with_file(self, json_file): """ Given a file path to a Json file, this method will parse the json and populate the template :param json_file: :return: populated template xml string """ with open(json_file) as file: data = json.load(file) return self.populate_template(data) def populate_template_with_json_string(self, json_string): """ Parses a json string to a python dictionary and renders the template :param json_string: :return: populated template xml string """ data = json.loads(json_string) return self.populate_template(data) def populate_template(self, input_hash): """ Given a python dictionary this method returns a xml string containing the populated template of the GP Summary Update template :param input_hash: :return: xml string containing populated template """ return self.builder.build_message(input_hash)
class TestPystacheMessageBuilder(TestCase): def setUp(self): current_dir = os.path.dirname(__file__) templates_dir = os.path.join(current_dir, TEMPLATES_DIR) self.builder = PystacheMessageBuilder(templates_dir, TEMPLATE_FILENAME) def test_build_message(self): message = self.builder.build_message(dict(to="world")) self.assertEqual( "Hello, world!\r\n", message, "Message returned should be the rendered string returned by Pystache" )
def build_error_message(self, error): builder = PystacheMessageBuilder(str(TEMPLATE_PATH), 'base_error_template') return builder.build_message({"errorMessage": error})
def __init__(self): self.builder = PystacheMessageBuilder(str(self.summaryCareRecordPath), "16UK05")
class MessageHandlerTest(unittest.TestCase): expectedXmlFileDir = Path(ROOT_DIR) / 'reciever' / 'tests' / 'expected_output_xmls' inputXmlFileDir = Path(ROOT_DIR) / 'reciever' / 'tests' / 'input_xmls' builder = PystacheMessageBuilder(str(inputXmlFileDir), 'base_input') success_response = FileUtilities.get_file_string(str(expectedXmlFileDir / 'basic_success_response.xml')) 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()) 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_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())
class MessageCheckTests(unittest.TestCase): inputXmlFileDir = Path(ROOT_DIR) / 'reciever' / 'tests' / 'input_xmls' builder = PystacheMessageBuilder(str(inputXmlFileDir), 'base_input') def generate_message_tree(self, input_hash): msg = self.builder.build_message(input_hash) return ET.fromstring(msg) 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" } fail_flag, response = CheckActionTypes( self.generate_message_tree(service_dict)).check() self.assertTrue(fail_flag) self.assertEqual("Manifest action does not match service action", 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" } fail_flag, response = CheckActionTypes( self.generate_message_tree(service_dict)).check() self.assertFalse(fail_flag) self.assertEqual(response, None) def test_manifest_payload_count(self): with self.subTest("Mismatched counts: 500 response"): counts = {'manifestCount': "2", 'payloadCount': "5"} fail_flag, response = CheckManifestPayloadCounts( self.generate_message_tree(counts)).check() self.assertTrue(fail_flag) self.assertEqual("Manifest count does not match payload count", response) with self.subTest("Equal counts: 200 response"): counts = {'manifestCount': "2", 'payloadCount': "2"} fail_flag, response = CheckManifestPayloadCounts( self.generate_message_tree(counts)).check() self.assertFalse(fail_flag) self.assertEqual(None, response) def test_manifest_count_matches_manifest_instances(self): with self.subTest("Incorrect manifest occurrences returns 500 error"): manifests = {'manifestCount': "2", 'manifests': [{"id": 'one'}]} fail_flag, response = CheckManifestCountInstances( self.generate_message_tree(manifests)).check() self.assertTrue(fail_flag) self.assertEqual( "The number of manifest instances does not match the manifest count specified", response) with self.subTest("Correct manifest occurrences returns 500 error"): manifests = {'manifestCount': "1", 'manifests': [{"id": 'one'}]} fail_flag, response = CheckManifestCountInstances( self.generate_message_tree(manifests)).check() self.assertFalse(fail_flag) self.assertEqual(None, response) def test_payload_count_against_instances(self): with self.subTest("Incorrect manifest occurrences returns 500 error"): manifests = {'payloadCount': "2", 'payloads': [{"id": 'one'}]} fail_flag, response = CheckPayloadCountAgainstActual( self.generate_message_tree(manifests)).check() self.assertTrue(fail_flag) self.assertEqual("Invalid message", response) with self.subTest("Incorrect manifest occurrences returns 500 error"): manifests = {'payloadCount': "1", 'payloads': [{"id": 'one'}]} fail_flag, response = CheckPayloadCountAgainstActual( self.generate_message_tree(manifests)).check() self.assertFalse(fail_flag) self.assertEqual(None, response) def test_payload_id_matches_manifest_id(self): with self.subTest("Incorrect manifest occurrences returns 500 error"): dictionary = { 'payloadCount': "2", 'payloads': [{ "id": 'one' }, { "id": 'three' }], 'manifestCount': "2", 'manifests': [{ "id": 'one' }, { "id": 'two' }] } fail_flag, response = CheckPayloadIdAgainstManifestId( self.generate_message_tree(dictionary)).check() self.assertTrue(fail_flag) self.assertEqual("Payload IDs do not map to Manifest IDs", response) with self.subTest("Incorrect manifest occurrences returns 500 error"): dictionary = { 'payloadCount': "2", 'payloads': [{ "id": 'one' }], 'manifestCount': "2", 'manifests': [{ "id": 'one' }] } fail_flag, response = CheckPayloadIdAgainstManifestId( self.generate_message_tree(dictionary)).check() self.assertFalse(fail_flag) self.assertEqual(None, response)
def setUp(self): current_dir = os.path.dirname(__file__) templates_dir = os.path.join(current_dir, TEMPLATES_DIR) self.builder = PystacheMessageBuilder(templates_dir, TEMPLATE_FILENAME)
def __init__(self): self.builder = PystacheMessageBuilder(str(self.summaryCareRecordPath), self.file_template_name)
class GpSummaryUpload(object): """Class for populating a Gp Summary Upload template""" summaryCareRecordPath = Path(ROOT_DIR) / "data/templates" file_template_name = "16UK05" interaction_id = "REPC_IN150016UK05" def __init__(self): self.builder = PystacheMessageBuilder(str(self.summaryCareRecordPath), self.file_template_name) def populate_template_with_file(self, json_file): """ Given a file path to a Json file, this method will parse the json and populate the template :param json_file: :return: populated template xml string """ with open(json_file) as file: data = json.load(file) return self.populate_template(data) def populate_template_with_json_string(self, json_string): """ Parses a json string to a python dictionary and renders the template :param json_string: :return: populated template xml string """ data = json.loads(json_string) return self.populate_template(data) def populate_template(self, input_hash): """ Given a python dictionary this method returns a xml string containing the populated template of the GP Summary Update template :param input_hash: :return: xml string containing populated template """ return self.builder.build_message(input_hash) def parse_response(self, response_message: str) -> Dict: """ Parses te key values of a given Gp summary Upload response into a dictionary. NOTE: This is purely a success parsing response mechanism, error parsing is currently not supported :param response_message: A Successful Gp Summary Upload response acknowledgement :return: A dictionary containing the key success details of the message """ root = self._get_root(response_message) if not root: return {'error': 'Failed to parse response from xml provided'} message_id = self._find_hl7_element_attribute(root, './/hl7:id', 'root') message_reference = self._find_hl7_element_attribute(root, './/hl7:messageRef//hl7:id', 'root') creation_time = self._find_hl7_element_attribute(root, './/hl7:creationTime', 'value') message_detail = self._find_hl7_element_text(root, './/hl7:ControlActEvent//hl7:requestSuccessDetail//hl7:detail') return self._create_response_dictionary(message_id, message_reference, creation_time, message_detail) def _get_root(self, message: str) -> Optional[ET.Element]: """ Parses an xml string and returns the root element of that element tree :param message: xml input string :return: The root element of the element tree """ try: return ET.ElementTree(ET.fromstring(message)).getroot() except ET.ParseError as e: logger.error('001', 'Exception raised while creating XML object from string {exception}', {'exception': e}) return None def _find_hl7_element_attribute(self, root: ET.Element, element_name: str, attribute: str) -> Optional[str]: """ Searches the tree with the given root node for an element with the element name, then looks for the given attribute of that element :param root: The root of the element tree :param element_name: The element tag to search for :param attribute: the attribute on that given element :return: The value of the attribute on the element, if it exists """ try: return self._get_element(root, element_name, lambda x: x.attrib[attribute]) except KeyError: logger.info('002', 'Failed to find attribute on {element} {attribute}', {'element': element_name, 'attribute': attribute} ) return None def _find_hl7_element_text(self, root: ET.Element, element_name: str): """ :param root: The root of the element tree to search :param element_name: the name of the element to look for :return: the text within the tags of the given element """ return self._get_element(root, element_name, lambda x: x.text) def _get_element(self, root: ET.Element, element_name: str, func: Callable[[ET.Element], str]): """ Searches for an element on the element tree of the given root, the passes the element to the found function :param root: Root element of the element tree :param element_name: element to search for :param func: function to call which processes the result of the element search :return: the result of the func call """ element = root.find(element_name, namespaces={'hl7': 'urn:hl7-org:v3'}) if element is None: return None return func(element) def _create_response_dictionary(self, message_id: str, message_ref: str, creation_time: str, message_detail: str) \ -> Dict: """ Creates a success/error dictionary based on the given parameters :param message_id: :param message_ref: :param creation_time: :param message_detail: :return: A dict containing either the parsed values or the errror response """ if all([message_id, message_ref, creation_time, message_detail]): return { 'messageRef': message_ref, 'messageId': message_id, 'creationTime': creation_time, 'messageDetail': message_detail } else: logger.error('003', 'Failed to parse all necessary elements from xml {message_id} {message_reference}' ' {creation_time} {message_details}', {'message_id': message_id, 'message_reference': message_ref, 'creation_time': creation_time, 'message_details': message_detail } ) return {'error': 'Failed to parse all the necessary elements from xml returned from MHS'}