def test_create_work_description(self, work_mock, time_mock): time_mock.return_value = '12' persistence = MagicMock() wd.create_new_work_description( persistence, message_id='aaa-aaa', outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED, workflow=workflow.SYNC) work_mock.assert_called_with( persistence, wd.build_store_data( 'aaa-aaa', '12', workflow.SYNC, outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED))
async def handle_unsolicited_inbound_message(self, message_id: str, correlation_id: str, payload: str, attachments: list): logger.info( '0005', 'Entered async forward reliable workflow to handle unsolicited inbound message' ) logger.audit('0101', 'Unsolicited inbound {WorkflowName} workflow invoked.', {'WorkflowName': self.workflow_name}) work_description = wd.create_new_work_description( self.persistence_store, message_id, self.workflow_name, wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_RECEIVED) await work_description.publish() for retry_num in range(self.inbound_queue_max_retries + 1): try: await self._put_message_onto_queue_with( message_id, correlation_id, payload, attachments=attachments) break except Exception as e: logger.warning( '0006', 'Failed to put unsolicited message onto inbound queue due to {Exception}', {'Exception': e}) if retry_num >= self.inbound_queue_max_retries: logger.error( "0020", "Exceeded the maximum number of retries, {max_retries} retries, when putting " "unsolicited message onto inbound queue", {"max_retries": self.inbound_queue_max_retries}) await work_description.set_inbound_status( wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_FAILED) raise MaxRetriesExceeded( 'The max number of retries to put a message onto the inbound queue has ' 'been exceeded') from e logger.info( "0021", "Waiting for {retry_delay} seconds before retrying putting unsolicited message " "onto inbound queue", {"retry_delay": self.inbound_queue_retry_delay}) await asyncio.sleep(self.inbound_queue_retry_delay) logger.audit( '0022', '{WorkflowName} workflow invoked for inbound unsolicited request. ' 'Attempted to place message onto inbound queue with {Acknowledgement}.', { 'Acknowledgement': wd.MessageStatus. UNSOLICITED_INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED, 'WorkflowName': self.workflow_name }) await work_description.set_inbound_status( wd.MessageStatus. UNSOLICITED_INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED)
async def _create_new_work_description_if_required(self, message_id: str, wdo: wd.WorkDescription, workflow_name: str): if not wdo: wdo = wd.create_new_work_description( self.persistence_store, message_id, workflow_name, outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED) await wdo.publish() return wdo
def test_create_work_description(self, work_mock, time_mock): time_mock.return_value = '12' persistence = MagicMock() wd.create_new_work_description( persistence, key='aaa-aaa', outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED, workflow=workflow.SYNC) work_mock.assert_called_with( persistence, { wd.DATA_KEY: 'aaa-aaa', wd.DATA: { wd.CREATED_TIMESTAMP: '12', wd.LATEST_TIMESTAMP: '12', wd.INBOUND_STATUS: None, wd.OUTBOUND_STATUS: wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED, wd.VERSION_KEY: 1, wd.WORKFLOW: workflow.SYNC } })
def test_create_wd_null_parameters(self): persistence = MagicMock() with self.subTest('Null key'): with self.assertRaises(ValueError): wd.create_new_work_description( persistence, message_id=None, outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED, workflow=workflow.SYNC) with self.subTest('Null persistence'): with self.assertRaises(ValueError): wd.create_new_work_description( None, message_id='aaa', outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED, workflow=workflow.SYNC) with self.subTest('Null workflow'): with self.assertRaises(ValueError): wd.create_new_work_description( persistence, message_id='aaa', outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED, workflow=None) with self.subTest('Null statuses'): with self.assertRaises(ValueError): wd.create_new_work_description(persistence, message_id='aaa', outbound_status=None, inbound_status=None, workflow=workflow.SYNC_ASYNC) with self.subTest('Single null status should not raise error'): wd.create_new_work_description( persistence, message_id='aaa', outbound_status=wd.MessageStatus.INBOUND_RESPONSE_FAILED, inbound_status=None, workflow=workflow.SYNC_ASYNC)
async def handle_sync_async_outbound_message(self, from_asid: Optional[str], message_id: str, correlation_id: str, interaction_details: dict, payload: str, async_workflow: common.CommonWorkflow ) -> Tuple[int, str, wd.WorkDescription]: logger.info('Entered sync-async workflow to handle outbound message') wdo = wd.create_new_work_description(self.work_description_store, message_id, workflow.SYNC_ASYNC, outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED) await wdo.publish() status_code, response, _ = await async_workflow.handle_outbound_message(from_asid, message_id, correlation_id, interaction_details, payload, wdo) if not status_code == 202: logger.warning('No ACK received ') return status_code, response, wdo status_code, response = await self._retrieve_async_response(message_id, wdo) return status_code, response, wdo
async def handle_unsolicited_inbound_message(self, message_id: str, correlation_id: str, message_data: MessageData): logger.info('Entered async forward reliable workflow to handle unsolicited inbound message') logger.audit('Unsolicited inbound {WorkflowName} workflow invoked.', fparams={'WorkflowName': self.workflow_name}) work_description = wd.create_new_work_description(self.persistence_store, message_id, self.workflow_name, wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_RECEIVED) await work_description.publish() try: await self._put_message_onto_queue_with(message_id, correlation_id, message_data) except Exception as e: await work_description.set_inbound_status(wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_FAILED) raise e logger.audit('{WorkflowName} workflow invoked for inbound unsolicited request. ' 'Attempted to place message onto inbound queue with {Acknowledgement}.', fparams={ 'Acknowledgement': wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED, 'WorkflowName': self.workflow_name }) await work_description.set_inbound_status(wd.MessageStatus.UNSOLICITED_INBOUND_RESPONSE_SUCCESSFULLY_PROCESSED)
async def handle_outbound_message(self, from_asid: str, message_id: str, correlation_id: str, interaction_details: dict, payload: str, work_description_object: Optional[wd.WorkDescription]) \ -> Tuple[int, str, Optional[wd.WorkDescription]]: logger.info('Entered sync workflow for outbound message') logger.audit('Outbound Synchronous workflow invoked.') wdo = wd.create_new_work_description( self.wd_store, message_id, workflow.SYNC, outbound_status=wd.MessageStatus.OUTBOUND_MESSAGE_RECEIVED) await wdo.publish() if not from_asid: return 400, '`from_asid` header field required for sync messages', None try: endpoint_details = await self._lookup_endpoint_details( interaction_details) url = endpoint_details[self.ENDPOINT_URL] to_asid = endpoint_details[self.ENDPOINT_TO_ASID] except Exception: await wdo.set_outbound_status( wd.MessageStatus.OUTBOUND_MESSAGE_PREPARATION_FAILED) return 500, 'Error obtaining outbound URL', None try: message_id, headers, message = await self._prepare_outbound_message( message_id, to_asid, from_asid=from_asid, interaction_details=interaction_details, message=payload) except Exception: logger.exception('Failed to prepare outbound message') await wdo.set_outbound_status( wd.MessageStatus.OUTBOUND_MESSAGE_PREPARATION_FAILED) return 500, 'Failed message preparation', None if len(message) > self.max_request_size: logger.error( 'Request to send to Spine is too large after serialisation. {RequestSize} {MaxRequestSize}', fparams={ 'RequestSize': len(message), 'MaxRequestSize': self.max_request_size }) await wdo.set_outbound_status( wd.MessageStatus.OUTBOUND_MESSAGE_PREPARATION_FAILED) return 400, f'Request to send to Spine is too large. MaxRequestSize={self.max_request_size} '\ f'RequestSize={len(message)}', None logger.info('Outbound message prepared') try: response = await self.transmission.make_request( url, headers, message) except httpclient.HTTPClientError as e: code, error = await self._handle_http_exception(e, wdo) return code, error, wdo except Exception: logger.exception( 'Error encountered whilst making outbound request.') await wdo.set_outbound_status( wd.MessageStatus.OUTBOUND_MESSAGE_TRANSMISSION_FAILED) return 500, 'Error making outbound request', None logger.audit( 'Outbound Synchronous workflow completed. Message sent to Spine and {Acknowledgment} received.', fparams={ 'Acknowledgment': wd.MessageStatus.OUTBOUND_SYNC_MESSAGE_RESPONSE_RECEIVED }) await wdo.set_outbound_status( wd.MessageStatus.OUTBOUND_SYNC_MESSAGE_RESPONSE_RECEIVED) return response.code, response.body.decode(), wdo