def _handle_error_response(self, response: httpclient.HTTPResponse, num_of_retries: int, retries_remaining: List[int]): try: parsed_body = ET.fromstring(response.body) if EbxmlErrorEnvelope.is_ebxml_error(parsed_body): _, parsed_response = ebxml_handler.handle_ebxml_error( response.code, response.headers, response.body) logger.warning( '0007', 'Received ebxml errors from Spine. {HTTPStatus} {Errors}', { 'HTTPStatus': response.code, 'Errors': parsed_response }) elif SOAPFault.is_soap_fault(parsed_body): _, parsed_response, soap_fault_codes = handle_soap_error( response.code, response.headers, response.body) logger.warning( '0008', 'Received soap errors from Spine. {HTTPStatus} {Errors}', { 'HTTPStatus': response.code, 'Errors': parsed_response }) if SOAPFault.is_soap_fault_retriable(soap_fault_codes): logger.warning( "0015", "A retriable error was encountered {error} {retries_remaining} " "{max_retries}", { "error": parsed_response, "retries_remaining": retries_remaining[0], "max_retries": num_of_retries }) if retries_remaining[0] <= 0: # exceeded the number of retries so return the SOAP error response logger.error( "0016", "A request has exceeded the maximum number of retries, {max_retries} " "retries", {"max_retries": num_of_retries}) else: raise _NeedToRetryException() else: logger.warning('0017', "Received an unexpected response from Spine", {'HTTPStatus': response.code}) parsed_response = "Didn't get expected response from Spine" except ET.ParseError as pe: logger.warning('0010', 'Unable to parse response from Spine. {Exception}', {'Exception': repr(pe)}) parsed_response = 'Unable to handle response returned from Spine' return 500, parsed_response, None
def _handle_error_response(self, response: httpclient.HTTPResponse): try: parsed_body = ET.fromstring(response.body) if EbxmlErrorEnvelope.is_ebxml_error(parsed_body): _, parsed_response = ebxml_handler.handle_ebxml_error(response.code, response.headers, response.body) logger.warning('Received ebxml errors from Spine. {HTTPStatus} {Errors}', fparams={'HTTPStatus': response.code, 'Errors': parsed_response}) elif SOAPFault.is_soap_fault(parsed_body): _, parsed_response, _ = handle_soap_error(response.code, response.headers, response.body) logger.warning('Received soap errors from Spine. {HTTPStatus} {Errors}', fparams={'HTTPStatus': response.code, 'Errors': parsed_response}) else: logger.warning("Received an unexpected response from Spine", fparams={'HTTPStatus': response.code}) parsed_response = "Didn't get expected response from Spine" except ET.ParseError: logger.exception('Unable to parse response from Spine.') parsed_response = 'Unable to handle response returned from Spine' return 500, parsed_response, None
def handle_soap_error(code: int, headers: Dict, body: AnyStr) -> Tuple[int, AnyStr, list]: """ Analyzes response from NHS which works in as web service mode and returns result of interpretation to external client :param code: HTTP response code :param headers: HTTP response headers :param body: HTTP response body :return: Response to external client represented as HTTP status code and body """ soap_fault_codes = [] if code != 500: logger.warning('Not HTTP 500 response. {Code} {Body}', fparams={'Code': code, 'Body': body}) return code, body, soap_fault_codes if HttpHeaders.CONTENT_TYPE not in headers: raise ValueError('No Content-Type header in Spine response, response cannot be handled!') if headers[HttpHeaders.CONTENT_TYPE] != 'text/xml': raise ValueError('Unexpected Content-Type {}!'.format(headers['Content-Type'])) try: parsed_body = ElementTree.fromstring(body) except ElementTree.ParseError: raise ValueError('Unable to parse response body') assert SOAPFault.is_soap_fault(parsed_body), 'Not SOAP Fault response!' fault: SOAPFault = SOAPFault.from_parsed(headers, parsed_body) error_data_response = {'error_message': 'Error(s) received from Spine. Contact system administrator.', 'process_key': 'SOAP_ERROR_HANDLER0002', 'errors': []} for idx, error_fields in enumerate(fault.error_list): all_fields = {**error_fields, **ERROR_RESPONSE_DEFAULTS} if all_fields.get('errorCode'): soap_fault_codes.append(int(all_fields['errorCode'])) error_data_response['errors'].append(all_fields) logger.error('SOAP Fault returned: {}'.format(' '.join(f'{{{i}}}' for i in all_fields.keys())), fparams=all_fields) return 500, json.dumps(error_data_response), soap_fault_codes
def handle_soap_error(code: int, headers: Dict, body: AnyStr) -> Tuple[int, AnyStr, list]: """ Analyzes response from NHS which works in as web service mode and returns result of interpretation to external client :param code: HTTP response code :param headers: HTTP response headers :param body: HTTP response body :return: Response to external client represented as HTTP status code and body """ soap_fault_codes = [] if code != 500: logger.warning('0001', 'Not HTTP 500 response. {Code} {Body}', {'Code': code, 'Body': body}) return code, body, soap_fault_codes if 'Content-Type' not in headers: raise ValueError('No Content-Type header in Spine response, response cannot be handled!') if headers['Content-Type'] != 'text/xml': raise ValueError('Unexpected Content-Type {}!'.format(headers['Content-Type'])) try: parsed_body = ElementTree.fromstring(body) except ElementTree.ParseError: raise ValueError('Unable to parse response body') assert SOAPFault.is_soap_fault(parsed_body), 'Not SOAP Fault response!' fault: SOAPFault = SOAPFault.from_parsed(headers, parsed_body) errors_text = '' for idx, error_fields in enumerate(fault.error_list): all_fields = {**error_fields, **ERROR_RESPONSE_DEFAULTS} if all_fields.get('errorCode'): soap_fault_codes.append(int(all_fields['errorCode'])) errors_text += '{}: {}\n'.format(idx, ' '.join([f'{k}={v}' for k, v in all_fields.items()])) logger.error('0002', 'SOAP Fault returned: {}'.format(' '.join(f'{{{i}}}' for i in all_fields.keys())), all_fields) return code, f'Error(s) received from Spine. Contact system administrator.\n{errors_text}', soap_fault_codes
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_soap_error_codes_are_retriable_or_not(self): errors_and_expected = [ ("a retriable failure to process message error code 200", [200], True), ("a retriable routing failure error code 206", [206], True), ("a retriable failure storing memo error code 208", [208], True), ("a NON retriable error code 300", [300], False), ("a NON retriable set of error codes 300, 207", [300, 207], False), ("a mix of retriable and NON retriable error codes 300, 206", [300, 206], False), ("a mix of retriable and NON retriable error codes 206, 300", [206, 300], False), ("a set of retriable error codes 208, 206", [208, 206], True) ] for description, error, expected_result in errors_and_expected: with self.subTest(description): result = SOAPFault.is_soap_fault_retriable(error) self.assertEqual(result, expected_result)
def test_soap_fault_empty(self): self.assertFalse(SOAPFault.is_soap_fault(None))
def test_soap_fault_multiple(self): message = FileUtilities.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 = FileUtilities.get_file_string( Path(self.message_dir) / 'ebxml_header.xml') self.assertFalse( SOAPFault.is_soap_fault(ElementTree.fromstring(message)))
def test_is_soap_empty(self): message = file_utilities.get_file_string( Path(self.message_dir) / 'soapfault_response_empty.xml') self.assertTrue( SOAPFault.is_soap_fault(ElementTree.fromstring(message)))