async def test_send_message_http_error(self, wd_mock, log_mock): wdo = mock.MagicMock() wdo.publish.return_value = test_utilities.awaitable(None) wd_mock.return_value = wdo self.wf._lookup_endpoint_details = mock.MagicMock() self.wf._lookup_endpoint_details.return_value = test_utilities.awaitable( LOOKUP_RESPONSE) self.wf._prepare_outbound_message = mock.MagicMock() self.wf._prepare_outbound_message.return_value = test_utilities.awaitable( ("123", { "qwe": "qwe" }, "message")) wdo.set_outbound_status.return_value = test_utilities.awaitable(None) future = asyncio.Future() future.set_exception(httpclient.HTTPClientError(code=409)) self.transmission.make_request.return_value = future error, text, work_description_response = await self.wf.handle_outbound_message( from_asid="202020", message_id="123", correlation_id="qwe", interaction_details={'action': ''}, payload="nice message", work_description_object=None) wdo.set_outbound_status.assert_called_with( work_description.MessageStatus. OUTBOUND_SYNC_MESSAGE_RESPONSE_RECEIVED) self.assertEqual(error, 500) self.assertEqual(text, 'Error(s) received from Spine: HTTP 409: Conflict')
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)
async def test_inbound_workflow_should_try_add_to_store_twice_if_max_retries_set_to_one( self, mock_sleep): mock_sleep.return_value = test_utilities.awaitable(None) self.work_description.update.return_value = test_utilities.awaitable( True) max_retries = 1 self.persistence.add.side_effect = ValueError self.work_description.set_inbound_status.return_value = test_utilities.awaitable( True) self.workflow = sync_async.SyncAsyncWorkflow( sync_async_store=self.persistence, persistence_store_max_retries=max_retries, sync_async_store_retry_delay=100) with self.assertRaises(ValueError): await self.workflow.handle_inbound_message('1', 'cor_id', self.work_description, 'wqe') self.work_description.set_inbound_status.assert_called_with( wd.MessageStatus.INBOUND_SYNC_ASYNC_MESSAGE_FAILED_TO_BE_STORED) self.assertEqual(self.persistence.add.call_count, max_retries + 1) self.assertEqual(mock_sleep.call_count, max_retries) mock_sleep.assert_called_with(100 / 1000)
async def test_send_message_failure(self, wd_mock, log_mock): wdo = mock.MagicMock() wdo.publish.return_value = test_utilities.awaitable(None) wd_mock.return_value = wdo self.wf._lookup_endpoint_details = mock.MagicMock() self.wf._lookup_endpoint_details.return_value = test_utilities.awaitable( LOOKUP_RESPONSE) self.wf._prepare_outbound_message = mock.MagicMock() self.wf._prepare_outbound_message.return_value = test_utilities.awaitable( ("123", { "qwe": "qwe" }, "message")) self.wf.transmission.make_request.side_effect = Exception("failed") wdo.set_outbound_status.return_value = test_utilities.awaitable(None) error, text, work_description_response = await self.wf.handle_outbound_message( from_asid="202020", message_id="123", correlation_id="qwe", interaction_details={'action': ''}, payload="nice message", work_description_object=None) wdo.set_outbound_status.assert_called_with( work_description.MessageStatus.OUTBOUND_MESSAGE_TRANSMISSION_FAILED ) self.assertEqual(error, 500) self.assertEqual(text, 'Error making outbound request') log_call = log_mock.error.call_args self.assertEqual(log_call[0][0], '0006') self.assertEqual( log_call[0][1], 'Error encountered whilst making outbound request. {Exception}')
async def test_prepare_message_failure(self, wd_mock, log_mock): wdo = mock.MagicMock() wdo.publish.return_value = test_utilities.awaitable(None) wd_mock.return_value = wdo self.wf._lookup_endpoint_details = mock.MagicMock() self.wf._lookup_endpoint_details.return_value = test_utilities.awaitable( LOOKUP_RESPONSE) self.wf._prepare_outbound_message = mock.MagicMock() self.wf._prepare_outbound_message.side_effect = Exception() wdo.set_outbound_status.return_value = test_utilities.awaitable(None) error, text, work_description_response = await self.wf.handle_outbound_message( from_asid="202020", message_id="123", correlation_id="qwe", interaction_details={}, payload="nice message", work_description_object=None) wdo.set_outbound_status.assert_called_with( work_description.MessageStatus.OUTBOUND_MESSAGE_PREPARATION_FAILED) self.assertEqual(error, 500) self.assertEqual(text, 'Failed message preparation') log_mock.error.assert_called_with( '002', 'Failed to prepare outbound message')
async def test_handle_inbound_message_error_putting_message_onto_queue_then_success( self, mock_sleep): self.setup_mock_work_description() error_future = asyncio.Future() error_future.set_exception(proton_queue_adaptor.MessageSendingError()) self.mock_queue_adaptor.send_async.side_effect = [ error_future, test_utilities.awaitable(None) ] mock_sleep.return_value = test_utilities.awaitable(None) await self.workflow.handle_inbound_message(MESSAGE_ID, CORRELATION_ID, self.mock_work_description, PAYLOAD) self.mock_queue_adaptor.send_async.assert_called_with( { 'payload': PAYLOAD, 'attachments': [] }, properties={ 'message-id': MESSAGE_ID, 'correlation-id': CORRELATION_ID }) self.assertEqual([ mock.call(MessageStatus.INBOUND_RESPONSE_RECEIVED), mock.call(MessageStatus.INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED) ], self.mock_work_description.set_inbound_status.call_args_list) mock_sleep.assert_called_once_with( INBOUND_QUEUE_RETRY_DELAY_IN_SECONDS)
async def test_handle_outbound_doesnt_overwrite_work_description( self, wdo_mock): response = mock.MagicMock() response.code = 202 self.setup_mock_work_description() self._setup_routing_mock() self.mock_ebxml_request_envelope.return_value.serialize.return_value = ( MESSAGE_ID, HTTP_HEADERS, SERIALIZED_MESSAGE) self.mock_transmission_adaptor.make_request.return_value = test_utilities.awaitable( response) expected_interaction_details = { ebxml_envelope.MESSAGE_ID: MESSAGE_ID, ebxml_request_envelope.MESSAGE: PAYLOAD, ebxml_envelope.FROM_PARTY_ID: FROM_PARTY_KEY, ebxml_envelope.CONVERSATION_ID: CORRELATION_ID, ebxml_envelope.TO_PARTY_ID: TO_PARTY_KEY, ebxml_envelope.CPA_ID: CPA_ID } expected_interaction_details.update(INTERACTION_DETAILS) wdo = mock.MagicMock() wdo.workflow = 'This should not change' wdo.set_outbound_status.return_value = test_utilities.awaitable(True) wdo.update.return_value = test_utilities.awaitable(True) status, message, _ = await self.workflow.handle_outbound_message( None, MESSAGE_ID, CORRELATION_ID, INTERACTION_DETAILS, PAYLOAD, wdo) self.assertEqual(202, status) wdo_mock.assert_not_called() self.assertEqual(wdo.workflow, 'This should not change')
async def test_sync_async_happy_path(self, wd_mock): wdo = MagicMock() wdo.publish.return_value = test_utilities.awaitable(None) wdo.set_outbound_status.return_value = test_utilities.awaitable(None) wd_mock.return_value = wdo async_workflow = MagicMock() self.resync.pause_request.return_value = test_utilities.awaitable( {sync_async.MESSAGE_DATA: 'data'}) result = (202, {}, None) async_workflow.handle_outbound_message.return_value = test_utilities.awaitable( result) code, body, actual_wdo = await self.workflow.handle_sync_async_outbound_message( None, 'id123', 'cor123', {}, 'payload', async_workflow) wd_mock.assert_called_with( self.work_description_store, 'id123', workflow.SYNC_ASYNC, outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED) async_workflow.handle_outbound_message.assert_called_once() async_workflow.handle_outbound_message.assert_called_with( None, 'id123', 'cor123', {}, 'payload', wd_mock.return_value) self.assertEqual(code, 200) self.assertEqual(body, 'data') self.assertEqual(actual_wdo, wdo)
async def test_prepare_message_success_but_message_too_large( self, wd_mock, mock_soap_envelope): wdo = mock.MagicMock() wdo.publish.return_value = test_utilities.awaitable(None) wd_mock.return_value = wdo self.wf._lookup_endpoint_details = mock.MagicMock() self.wf._lookup_endpoint_details.return_value = test_utilities.awaitable( LOOKUP_RESPONSE) wdo.set_outbound_status.return_value = test_utilities.awaitable(None) mock_soap_envelope.return_value.serialize.return_value = ( 'message-id', {}, 'e' * (MAX_REQUEST_SIZE + 1)) test_interaction_details = { 'service': 'test-service', 'action': 'test-action' } error, text, work_description_response = await self.wf.handle_outbound_message( from_asid="202020", message_id="123", correlation_id="qwe", interaction_details=test_interaction_details, payload="nice message", work_description_object=None) wdo.set_outbound_status.assert_called_with( work_description.MessageStatus.OUTBOUND_MESSAGE_PREPARATION_FAILED) self.assertEqual(error, 400) self.assertIn('Request to send to Spine is too large', text)
async def test_value_added_to_cache(self): handler = mhs_attribute_lookup.MHSAttributeLookup(mocks.mocked_sds_client(), self.cache) self.cache.retrieve_mhs_attributes_value.return_value = test_utilities.awaitable(None) self.cache.add_cache_value.return_value = test_utilities.awaitable(None) result = await handler.retrieve_mhs_attributes(ODS_CODE, INTERACTION_ID) self.cache.add_cache_value.assert_called_with(ODS_CODE, INTERACTION_ID, result)
async def test_get_endpoint(self): self.cache.retrieve_mhs_attributes_value.return_value = test_utilities.awaitable(None) self.cache.add_cache_value.return_value = test_utilities.awaitable(None) handler = mhs_attribute_lookup.MHSAttributeLookup(mocks.mocked_sds_client(), self.cache) attributes = await handler.retrieve_mhs_attributes(ODS_CODE, INTERACTION_ID) self.assertEqual(expected_mhs_attributes, attributes)
async def test_failure_response(self): wdo = mock.MagicMock() wdo.publish.return_value = test_utilities.awaitable(None) wdo.set_outbound_status.return_value = test_utilities.awaitable(None) await self.wf.set_failure_message_response(wdo) wdo.set_outbound_status.assert_called_once_with( work_description.MessageStatus.SYNC_RESPONSE_FAILED)
def _configure_routing_and_reliability(): cache = mock.Mock() cache.add_cache_value.return_value = test_utilities.awaitable(None) cache.retrieve_mhs_attributes_value.return_value = test_utilities.awaitable( None) handler = mhs_attribute_lookup.MHSAttributeLookup( mocks.mocked_sds_client(), cache) router = rar.RoutingAndReliability(handler) return router
async def test_update_success(self): wdo = MagicMock() wdo.update.return_value = test_utilities.awaitable(True) wdo.set_outbound_status.return_value = test_utilities.awaitable(True) await wd.update_status_with_retries( wdo, wdo.set_outbound_status, wd.MessageStatus.OUTBOUND_MESSAGE_ACKD, 20) self.assertEqual(wdo.update.call_count, 1) self.assertEqual(wdo.set_outbound_status.call_count, 1)
async def test_failure_response(self): wdo = MagicMock() wdo.update.return_value = test_utilities.awaitable(None) wdo.publish.return_value = test_utilities.awaitable(None) wdo.set_outbound_status.return_value = test_utilities.awaitable(None) await self.workflow.set_failure_message_response(wdo) wdo.set_outbound_status.assert_called_once_with( wd.MessageStatus.OUTBOUND_SYNC_ASYNC_MESSAGE_FAILED_TO_RESPOND)
def setup_workflows(self): expected_response = "Hello world!" wdo = unittest.mock.MagicMock() wdo.set_outbound_status.return_value = test_utilities.awaitable(True) result = test_utilities.awaitable((200, expected_response, wdo)) self.sync_async_workflow.handle_sync_async_outbound_message.return_value = result self.workflow.handle_outbound_message.return_value = test_utilities.awaitable( (200, "Success"))
def setup_mock_work_description(self): self.mock_work_description = self.mock_create_new_work_description.return_value self.mock_work_description.publish.return_value = test_utilities.awaitable( None) self.mock_work_description.set_outbound_status.return_value = test_utilities.awaitable( None) self.mock_work_description.set_inbound_status.return_value = test_utilities.awaitable( None) self.mock_work_description.update.return_value = test_utilities.awaitable( None)
async def test_should_initially_wait_before_polling_store(self, sleep_mock): # Arrange store = MagicMock() sleep_mock.return_value = test_utilities.awaitable(1) store.get.return_value = test_utilities.awaitable(True) resynchroniser = resync.SyncAsyncResynchroniser(store, 20, 1, 5) # Act await resynchroniser.pause_request('Message') # Assert self.assertEqual(sleep_mock.call_count, 1) self.assertEqual(sleep_mock.call_args[0][0], 5)
async def test_should_return_correct_result_if_retry_succeeds(self, sleep_mock): # Arrange store = MagicMock() sleep_mock.return_value = test_utilities.awaitable(1) store.get.side_effect = [test_utilities.awaitable(None), test_utilities.awaitable(True)] resynchroniser = resync.SyncAsyncResynchroniser(store, 20, 1, 0) # Act response = await resynchroniser.pause_request('Message') # Assert self.assertTrue(response) self.assertEqual(store.get.call_count, 2)
def _setup_success_workflow(self): self.wf._lookup_endpoint_details = mock.MagicMock() self.wf._lookup_endpoint_details.return_value = test_utilities.awaitable( LOOKUP_RESPONSE) self.wf._prepare_outbound_message = mock.MagicMock() self.wf._prepare_outbound_message.return_value = test_utilities.awaitable( ("123", { "qwe": "qwe" }, "message")) response = mock.MagicMock() response.code = 200 response.body = b'response body' self.transmission.make_request.return_value = test_utilities.awaitable( response)
def _setup_routing_mock(self): self.mock_routing_reliability.get_end_point.return_value = test_utilities.awaitable( { MHS_END_POINT_KEY: [URL], MHS_TO_PARTY_KEY_KEY: TO_PARTY_KEY, MHS_CPA_ID_KEY: CPA_ID, MHS_ASID: [ASID] }) self.mock_routing_reliability.get_reliability.return_value = test_utilities.awaitable( { workflow.common_asynchronous.MHS_RETRY_INTERVAL: MHS_RETRY_INTERVAL_VAL, workflow.common_asynchronous.MHS_RETRIES: MHS_RETRY_VAL })
def test_get(self): self.routing.get_end_point.return_value = test_utilities.awaitable( END_POINT_DETAILS) self.routing.get_reliability.return_value = test_utilities.awaitable( RELIABILITY_DETAILS) response = self.fetch(test_request_handler.build_url(), method="GET") self.assertEqual(response.code, 200) self.assertEqual(COMBINED_DETAILS, json.loads(response.body)) self.routing.get_end_point.assert_called_with( test_request_handler.ORG_CODE, test_request_handler.SERVICE_ID) self.routing.get_reliability.assert_called_with( test_request_handler.ORG_CODE, test_request_handler.SERVICE_ID)
async def test_should_perform_correct_number_of_sleeps_between_retries(self, sleep_mock): # Arrange store = MagicMock() sleep_mock.return_value = test_utilities.awaitable(1) store.get.return_value = test_utilities.awaitable(None) max_retries = 20 resynchroniser = resync.SyncAsyncResynchroniser(store, max_retries, 1, 0) # Act with self.assertRaises(resync.SyncAsyncResponseException): await resynchroniser.pause_request('Message') # Assert self.assertEqual(1 + max_retries, sleep_mock.call_count)
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'})
async def test_async_workflow_return_error_code(self, wd_mock): wdo = MagicMock() wdo.publish.return_value = test_utilities.awaitable(None) wdo.set_outbound_status.return_value = test_utilities.awaitable(None) wd_mock.return_value = wdo async_workflow = MagicMock() result = (500, "Failed to reach spine", None) async_workflow.handle_outbound_message.return_value = test_utilities.awaitable( result) status, response, _ = await self.workflow.handle_sync_async_outbound_message( None, 'id123', 'cor123', {}, 'payload', async_workflow) self.assertEqual(status, 500) self.assertEqual("Failed to reach spine", response)
async def test_should_respect_max_retries_while_attempting_to_retry(self, sleep_mock): # Arrange store = MagicMock() sleep_mock.return_value = test_utilities.awaitable(1) store.get.return_value = test_utilities.awaitable(None) max_retries = 20 resynchroniser = resync.SyncAsyncResynchroniser(store, max_retries, 1, 0) # Act with self.assertRaises(resync.SyncAsyncResponseException): await resynchroniser.pause_request('Message') # Assert self.assertEqual(1 + max_retries, store.get.call_count, f"Retrieving the message should be tried once and then retried {max_retries} times.")
async def test_should_make_HTTP_request_with_default_parameters(self): with patch.object(httpclient.AsyncHTTPClient(), "fetch") as mock_fetch: sentinel.result.code = 200 mock_fetch.return_value = awaitable(sentinel.result) actual_response = await self.transmission.make_request( URL_VALUE, HEADERS, MESSAGE) mock_fetch.assert_called_with( URL_VALUE, method="POST", raise_error=True, body=MESSAGE, headers=HEADERS, client_cert=CLIENT_CERT_PATH, client_key=CLIENT_KEY_PATH, ca_certs=CA_CERTS_PATH, # **************************************************************************** # This SHOULD be true, but we must temporarily set it to false due to Opentest # limitations. # **************************************************************************** validate_cert=False, proxy_host=None, proxy_port=None) self.assertIs(actual_response, sentinel.result, "Expected content should be returned.")
async def test_should_use_proxy_details_if_provided(self): self.transmission = outbound_transmission.OutboundTransmission( CLIENT_CERT_PATH, CLIENT_KEY_PATH, CA_CERTS_PATH, MAX_RETRIES, RETRY_DELAY, HTTP_PROXY_HOST, HTTP_PROXY_PORT) with patch.object(httpclient.AsyncHTTPClient(), "fetch") as mock_fetch: sentinel.result.code = 200 mock_fetch.return_value = awaitable(sentinel.result) actual_response = await self.transmission.make_request( URL_VALUE, HEADERS, MESSAGE) mock_fetch.assert_called_with( URL_VALUE, method="POST", raise_error=True, body=MESSAGE, headers=HEADERS, client_cert=CLIENT_CERT_PATH, client_key=CLIENT_KEY_PATH, ca_certs=CA_CERTS_PATH, # **************************************************************************** # This SHOULD be true, but we must temporarily set it to false due to Opentest # limitations. # **************************************************************************** validate_cert=False, proxy_host=HTTP_PROXY_HOST, proxy_port=HTTP_PROXY_PORT) self.assertIs(actual_response, sentinel.result, "Expected content should be returned.")
async def test_successful_handle_inbound_message(self, audit_log_mock): self.setup_mock_work_description() self.mock_queue_adaptor.send_async.return_value = test_utilities.awaitable( None) await self.workflow.handle_inbound_message(MESSAGE_ID, CORRELATION_ID, self.mock_work_description, INBOUND_MESSAGE_DATA) self.mock_queue_adaptor.send_async.assert_called_once_with( { 'ebXML': EBXML, 'payload': PAYLOAD, 'attachments': ATTACHMENTS }, properties={ 'message-id': MESSAGE_ID, 'correlation-id': CORRELATION_ID }) self.assertEqual( [mock.call(MessageStatus.INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED)], self.mock_work_description.set_inbound_status.call_args_list) audit_log_mock.assert_called_with( '{WorkflowName} inbound workflow completed. Message placed on queue, returning {Acknowledgement} to spine', fparams={ 'Acknowledgement': 'INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED', 'WorkflowName': 'async-express' })
async def test_unhandled_response_from_spine(self, audit_log_mock): 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 = 400 response.headers = {'Content-Type': 'text/xml'} response.body = '<a></a>' 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.assertEqual("Didn't get expected response from Spine", message) 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) # This should be called at the start, regardless of error scenario audit_log_mock.assert_called_with( '{WorkflowName} outbound workflow invoked.', fparams={'WorkflowName': 'async-express'})