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)
Пример #3
0
    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')
Пример #6
0
    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')
Пример #8
0
    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)
Пример #13
0
 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
Пример #14
0
 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)
Пример #15
0
    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
         })
Пример #22
0
    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'})
Пример #25
0
    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.")
Пример #27
0
    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.")
Пример #28
0
    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'})