def test_create_complaint_callback_data( notify_db, notify_db_session, sample_email_template, ): notification = create_sample_notification( notify_db, notify_db_session, template=sample_email_template, status="delivered", sent_at=datetime.utcnow(), ) complaint = create_complaint(notification=notification, service=notification.service) callback_api = create_service_callback_api( service=sample_email_template.service, url="https://original_url.com") assert encryption.decrypt( create_complaint_callback_data( complaint, notification, callback_api, "*****@*****.**")) == { "complaint_id": str(complaint.id), "notification_id": str(notification.id), "reference": notification.client_reference, "to": "*****@*****.**", "complaint_date": complaint.complaint_date.strftime(DATETIME_FORMAT), "service_callback_api_url": callback_api.url, "service_callback_api_bearer_token": callback_api.bearer_token, }
def test_save_service_inbound_api(sample_service): service_inbound_api = ServiceInboundApi( service_id=sample_service.id, url="https://some_service/inbound_messages", bearer_token="some_unique_string", updated_by_id=sample_service.users[0].id ) save_service_inbound_api(service_inbound_api) results = ServiceInboundApi.query.all() assert len(results) == 1 inbound_api = results[0] assert inbound_api.id is not None assert inbound_api.service_id == sample_service.id assert inbound_api.updated_by_id == sample_service.users[0].id assert inbound_api.url == "https://some_service/inbound_messages" assert inbound_api.bearer_token == "some_unique_string" assert inbound_api._bearer_token != "some_unique_string" assert inbound_api.updated_at is None versioned = ServiceInboundApi.get_history_model().query.filter_by(id=inbound_api.id).one() assert versioned.id == inbound_api.id assert versioned.service_id == sample_service.id assert versioned.updated_by_id == sample_service.users[0].id assert versioned.url == "https://some_service/inbound_messages" assert encryption.decrypt(versioned._bearer_token) == "some_unique_string" assert versioned.updated_at is None assert versioned.version == 1
def test_ses_callback_should_send_on_complaint_to_user_callback_api( sample_email_template, mocker): send_mock = mocker.patch( "app.celery.service_callback_tasks.send_complaint_to_service.apply_async" ) create_service_callback_api( service=sample_email_template.service, url="https://original_url.com", callback_type="complaint", ) notification = create_notification( template=sample_email_template, reference="ref1", sent_at=datetime.utcnow(), status="sending", ) response = ses_complaint_callback() assert process_ses_results(response) assert send_mock.call_count == 1 assert encryption.decrypt(send_mock.call_args[0][0][0]) == { "complaint_date": "2018-06-05T13:59:58.000000Z", "complaint_id": str(Complaint.query.one().id), "notification_id": str(notification.id), "reference": None, "service_callback_api_bearer_token": "some_super_secret", "service_callback_api_url": "https://original_url.com", "to": "*****@*****.**", }
def send_delivery_status_to_service( self, notification_id, encrypted_status_update ): status_update = encryption.decrypt(encrypted_status_update) data = { "id": str(notification_id), "reference": status_update['notification_client_reference'], "to": status_update['notification_to'], "status": status_update['notification_status'], "created_at": status_update['notification_created_at'], "completed_at": status_update['notification_updated_at'], "sent_at": status_update['notification_sent_at'], "notification_type": status_update['notification_type'], "template_id": status_update['template_id'], "template_version": status_update['template_version'] } _send_data_to_service_callback_api( self, data, status_update['service_callback_api_url'], status_update['service_callback_api_bearer_token'], 'send_delivery_status_to_service' )
def test_save_service_callback_api(sample_service): notification_statuses = [NOTIFICATION_FAILED] service_callback_api = ServiceCallbackApi( # nosec service_id=sample_service.id, url="https://some_service/callback_endpoint", bearer_token="some_unique_string", updated_by_id=sample_service.users[0].id, notification_statuses=notification_statuses ) save_service_callback_api(service_callback_api) results = ServiceCallbackApi.query.all() assert len(results) == 1 callback_api = results[0] assert callback_api.id is not None assert callback_api.service_id == sample_service.id assert callback_api.updated_by_id == sample_service.users[0].id assert callback_api.url == "https://some_service/callback_endpoint" assert callback_api.bearer_token == "some_unique_string" assert callback_api._bearer_token != "some_unique_string" assert callback_api.updated_at is None assert callback_api.notification_statuses == notification_statuses versioned = ServiceCallbackApi.get_history_model().query.filter_by(id=callback_api.id).one() assert versioned.id == callback_api.id assert versioned.service_id == sample_service.id assert versioned.updated_by_id == sample_service.users[0].id assert versioned.url == "https://some_service/callback_endpoint" assert encryption.decrypt(versioned._bearer_token) == "some_unique_string" assert versioned.updated_at is None assert versioned.version == 1
def test_ses_callback_should_send_on_complaint_to_user_callback_api( sample_email_template, mocker): send_mock = mocker.patch( 'app.celery.service_callback_tasks.send_complaint_to_service.apply_async' ) create_service_callback_api(service=sample_email_template.service, url="https://original_url.com", callback_type="complaint") notification = create_notification(template=sample_email_template, reference='ref1', sent_at=datetime.utcnow(), status='sending') response = ses_complaint_callback() assert process_ses_results(response) assert send_mock.call_count == 1 assert encryption.decrypt(send_mock.call_args[0][0][0]) == { 'complaint_date': '2018-06-05T13:59:58.000000Z', 'complaint_id': str(Complaint.query.one().id), 'notification_id': str(notification.id), 'reference': None, 'service_callback_api_bearer_token': 'some_super_secret', 'service_callback_api_url': 'https://original_url.com', 'to': '*****@*****.**' }
def save_letter( self, service_id, notification_id, encrypted_notification, ): notification = encryption.decrypt(encrypted_notification) postal_address = PostalAddress.from_personalisation( Columns(notification['personalisation'])) service = SerialisedService.from_id(service_id) template = SerialisedTemplate.from_id_and_service_id( notification['template'], service_id=service.id, version=notification['template_version'], ) try: # if we don't want to actually send the letter, then start it off in SENDING so we don't pick it up status = NOTIFICATION_CREATED if not service.research_mode else NOTIFICATION_SENDING saved_notification = persist_notification( template_id=notification['template'], template_version=notification['template_version'], postage=postal_address.postage if postal_address.international else template.postage, recipient=postal_address.normalised, service=service, personalisation=notification['personalisation'], notification_type=LETTER_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL, created_at=datetime.utcnow(), job_id=notification['job'], job_row_number=notification['row_number'], notification_id=notification_id, reference=create_random_identifier(), reply_to_text=template.reply_to_text, status=status) if not service.research_mode: letters_pdf_tasks.get_pdf_for_templated_letter.apply_async( [str(saved_notification.id)], queue=QueueNames.CREATE_LETTERS_PDF) elif current_app.config['NOTIFY_ENVIRONMENT'] in [ 'preview', 'development' ]: research_mode_tasks.create_fake_letter_response_file.apply_async( (saved_notification.reference, ), queue=QueueNames.RESEARCH_MODE) else: update_notification_status_by_reference( saved_notification.reference, 'delivered') current_app.logger.debug("Letter {} created at {}".format( saved_notification.id, saved_notification.created_at)) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def save_sms(self, service_id, notification_id, encrypted_notification, sender_id=None): notification = encryption.decrypt(encrypted_notification) service = dao_fetch_service_by_id(service_id) template = dao_get_template_by_id(notification["template"], version=notification["template_version"]) if sender_id: reply_to_text = dao_get_service_sms_senders_by_id( service_id, sender_id).sms_sender else: reply_to_text = template.get_reply_to_text() if not service_allowed_to_send_to(notification["to"], service, KEY_TYPE_NORMAL): current_app.logger.debug( "SMS {} failed as restricted service".format(notification_id)) return check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service) try: # this task is used by two main things... process_job and process_sms_or_email_notification # if the data is not present in the encrypted data then fallback on whats needed for process_job saved_notification = persist_notification( notification_id=notification.get("id", notification_id), template_id=notification["template"], template_version=notification["template_version"], recipient=notification["to"], service=service, personalisation=notification.get("personalisation"), notification_type=SMS_TYPE, simulated=notification.get("simulated", None), api_key_id=notification.get("api_key", None), key_type=notification.get("key_type", KEY_TYPE_NORMAL), created_at=datetime.utcnow(), job_id=notification.get("job", None), job_row_number=notification.get("row_number", None), reply_to_text=reply_to_text, ) send_notification_to_queue( saved_notification, service.research_mode, queue=notification.get("queue") or template.queue_to_use(), ) current_app.logger.debug("SMS {} created at {} for job {}".format( saved_notification.id, saved_notification.created_at, notification.get("job", None), )) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def save_letter( self, service_id, notification_id, encrypted_notification, ): notification = encryption.decrypt(encrypted_notification) # we store the recipient as just the first item of the person's address recipient = notification["personalisation"]["addressline1"] service = dao_fetch_service_by_id(service_id) template = dao_get_template_by_id(notification["template"], version=notification["template_version"]) check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service) try: # if we don't want to actually send the letter, then start it off in SENDING so we don't pick it up status = NOTIFICATION_CREATED if not service.research_mode else NOTIFICATION_SENDING saved_notification = persist_notification( template_id=notification["template"], template_version=notification["template_version"], template_postage=template.postage, recipient=recipient, service=service, personalisation=notification["personalisation"], notification_type=LETTER_TYPE, api_key_id=notification.get("api_key", None), key_type=KEY_TYPE_NORMAL, created_at=datetime.utcnow(), job_id=notification["job"], job_row_number=notification["row_number"], notification_id=notification_id, reference=create_random_identifier(), reply_to_text=template.get_reply_to_text(), status=status, ) if not service.research_mode: send_notification_to_queue(saved_notification, service.research_mode) elif current_app.config["NOTIFY_ENVIRONMENT"] in [ "preview", "development" ]: research_mode_tasks.create_fake_letter_response_file.apply_async( (saved_notification.reference, ), queue=QueueNames.RESEARCH_MODE) else: update_notification_status_by_reference( saved_notification.reference, "delivered") current_app.logger.debug("Letter {} created at {}".format( saved_notification.id, saved_notification.created_at)) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def save_sms(self, service_id, notification_id, encrypted_notification, sender_id=None): notification = encryption.decrypt(encrypted_notification) service = SerialisedService.from_id(service_id) template = SerialisedTemplate.from_id_and_service_id( notification['template'], service_id=service.id, version=notification['template_version'], ) if sender_id: reply_to_text = dao_get_service_sms_senders_by_id(service_id, sender_id).sms_sender else: reply_to_text = template.reply_to_text if not service_allowed_to_send_to(notification['to'], service, KEY_TYPE_NORMAL): current_app.logger.debug( "SMS {} failed as restricted service".format(notification_id) ) return try: saved_notification = persist_notification( template_id=notification['template'], template_version=notification['template_version'], recipient=notification['to'], service=service, personalisation=notification.get('personalisation'), notification_type=SMS_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL, created_at=datetime.utcnow(), job_id=notification.get('job', None), job_row_number=notification.get('row_number', None), notification_id=notification_id, reply_to_text=reply_to_text ) provider_tasks.deliver_sms.apply_async( [str(saved_notification.id)], queue=QueueNames.SEND_SMS if not service.research_mode else QueueNames.RESEARCH_MODE ) current_app.logger.debug( "SMS {} created at {} for job {}".format( saved_notification.id, saved_notification.created_at, notification.get('job', None)) ) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def save_letter( self, service_id, notification_id, encrypted_notification, ): notification = encryption.decrypt(encrypted_notification) # we store the recipient as just the first item of the person's address recipient = notification['personalisation']['addressline1'] service = dao_fetch_service_by_id(service_id) template = dao_get_template_by_id(notification['template'], version=notification['template_version']) try: # if we don't want to actually send the letter, then start it off in SENDING so we don't pick it up status = NOTIFICATION_CREATED if not service.research_mode else NOTIFICATION_SENDING saved_notification = persist_notification( template_id=notification['template'], template_version=notification['template_version'], recipient=recipient, service=service, personalisation=notification['personalisation'], notification_type=LETTER_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL, created_at=datetime.utcnow(), job_id=notification['job'], job_row_number=notification['row_number'], notification_id=notification_id, reference=create_random_identifier(), reply_to_text=template.get_reply_to_text(), status=status) if not service.research_mode: letters_pdf_tasks.create_letters_pdf.apply_async( [str(saved_notification.id)], queue=QueueNames.CREATE_LETTERS_PDF) elif current_app.config['NOTIFY_ENVIRONMENT'] in [ 'preview', 'development' ]: research_mode_tasks.create_fake_letter_response_file.apply_async( (saved_notification.reference, ), queue=QueueNames.RESEARCH_MODE) else: update_notification_status_by_reference( saved_notification.reference, 'delivered') current_app.logger.debug("Letter {} created at {}".format( saved_notification.id, saved_notification.created_at)) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def send_sms( self, service_id, notification_id, encrypted_notification, created_at, api_key_id=None, key_type=KEY_TYPE_NORMAL ): notification = encryption.decrypt(encrypted_notification) service = dao_fetch_service_by_id(service_id) if not service_allowed_to_send_to(notification["to"], service, key_type): current_app.logger.info("SMS {} failed as restricted service".format(notification_id)) return try: saved_notification = persist_notification( template_id=notification["template"], template_version=notification["template_version"], recipient=notification["to"], service_id=service.id, personalisation=notification.get("personalisation"), notification_type=SMS_TYPE, api_key_id=api_key_id, key_type=key_type, created_at=created_at, job_id=notification.get("job", None), job_row_number=notification.get("row_number", None), notification_id=notification_id, ) provider_tasks.deliver_sms.apply_async( [str(saved_notification.id)], queue="send-sms" if not service.research_mode else "research-mode" ) current_app.logger.info( "SMS {} created at {} for job {}".format(saved_notification.id, created_at, notification.get("job", None)) ) except SQLAlchemyError as e: if not get_notification_by_id(notification_id): # Sometimes, SQS plays the same message twice. We should be able to catch an IntegrityError, but it seems # SQLAlchemy is throwing a FlushError. So we check if the notification id already exists then do not # send to the retry queue. current_app.logger.error( "RETRY: send_sms notification for job {} row number {} and notification id {}".format( notification.get("job", None), notification.get("row_number", None), notification_id ) ) current_app.logger.exception(e) try: raise self.retry(queue="retry", exc=e) except self.MaxRetriesExceededError: current_app.logger.error( "RETRY FAILED: send_sms notification for job {} row number {} and notification id {}".format( notification.get("job", None), notification.get("row_number", None), notification_id ) ) current_app.logger.exception(e)
def send_complaint_to_service(self, complaint_data): complaint = encryption.decrypt(complaint_data) data = { "notification_id": complaint['notification_id'], "complaint_id": complaint['complaint_id'], "reference": complaint['reference'], "to": complaint['to'], "complaint_date": complaint['complaint_date'] } _send_data_to_service_callback_api( self, data, complaint['service_callback_api_url'], complaint['service_callback_api_bearer_token'], 'send_complaint_to_service')
def save_sms(self, service_id, notification_id, encrypted_notification, sender_id=None): notification = encryption.decrypt(encrypted_notification) service = dao_fetch_service_by_id(service_id) template = dao_get_template_by_id(notification['template'], version=notification['template_version']) if sender_id: reply_to_text = dao_get_service_sms_senders_by_id( service_id, sender_id).sms_sender else: reply_to_text = template.get_reply_to_text() if not service_allowed_to_send_to(notification['to'], service, KEY_TYPE_NORMAL): current_app.logger.debug( "SMS {} failed as restricted service".format(notification_id)) return try: saved_notification = persist_notification( template_id=notification['template'], template_version=notification['template_version'], recipient=notification['to'], service=service, personalisation=notification.get('personalisation'), notification_type=SMS_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL, created_at=datetime.utcnow(), job_id=notification.get('job', None), job_row_number=notification.get('row_number', None), notification_id=notification_id, reply_to_text=reply_to_text) send_notification_to_queue(saved_notification, service.research_mode) current_app.logger.debug("SMS {} created at {} for job {}".format( saved_notification.id, saved_notification.created_at, notification.get('job', None))) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def test_update_service_callback_api(sample_service): service_callback_api = ServiceCallbackApi( service_id=sample_service.id, url="https://some_service/callback_endpoint", bearer_token="some_unique_string", updated_by_id=sample_service.users[0].id, ) save_service_callback_api(service_callback_api) results = ServiceCallbackApi.query.all() assert len(results) == 1 saved_callback_api = results[0] reset_service_callback_api( saved_callback_api, updated_by_id=sample_service.users[0].id, url="https://some_service/changed_url", ) updated_results = ServiceCallbackApi.query.all() assert len(updated_results) == 1 updated = updated_results[0] assert updated.id is not None assert updated.service_id == sample_service.id assert updated.updated_by_id == sample_service.users[0].id assert updated.url == "https://some_service/changed_url" assert updated.bearer_token == "some_unique_string" assert updated._bearer_token != "some_unique_string" assert updated.updated_at is not None versioned_results = ServiceCallbackApi.get_history_model().query.filter_by( id=saved_callback_api.id).all() assert len(versioned_results) == 2 for x in versioned_results: if x.version == 1: assert x.url == "https://some_service/callback_endpoint" assert not x.updated_at elif x.version == 2: assert x.url == "https://some_service/changed_url" assert x.updated_at else: pytest.fail("version should not exist") assert x.id is not None assert x.service_id == sample_service.id assert x.updated_by_id == sample_service.users[0].id assert encryption.decrypt(x._bearer_token) == "some_unique_string"
def send_delivery_status_to_service(self, notification_id, encrypted_status_update): try: status_update = encryption.decrypt(encrypted_status_update) data = { "id": str(notification_id), "reference": status_update['notification_client_reference'], "to": status_update['notification_to'], "status": status_update['notification_status'], "created_at": status_update['notification_created_at'], "completed_at": status_update['notification_updated_at'], "sent_at": status_update['notification_sent_at'], "notification_type": status_update['notification_type'] } response = request( method="POST", url=status_update['service_callback_api_url'], data=json.dumps(data), headers={ 'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format( status_update['service_callback_api_bearer_token']) }, timeout=60) current_app.logger.info( 'send_delivery_status_to_service sending {} to {}, response {}'. format(notification_id, status_update['service_callback_api_url'], response.status_code)) response.raise_for_status() except RequestException as e: current_app.logger.warning( "send_delivery_status_to_service request failed for notification_id: {} and url: {}. exc: {}" .format(notification_id, status_update['service_callback_api_url'], e)) if not isinstance(e, HTTPError) or e.response.status_code >= 500: try: self.retry(queue=QueueNames.RETRY) except self.MaxRetriesExceededError: current_app.logger.exception( """Retry: send_delivery_status_to_service has retried the max num of times for notification: {}""".format(notification_id))
def send_complaint_to_service(self, complaint_data): complaint = encryption.decrypt(complaint_data) data = { "notification_id": complaint["notification_id"], "complaint_id": complaint["complaint_id"], "reference": complaint["reference"], "to": complaint["to"], "complaint_date": complaint["complaint_date"], } _send_data_to_service_callback_api( self, data, complaint["service_callback_api_url"], complaint["service_callback_api_bearer_token"], "send_complaint_to_service", )
def save_api_email_or_sms(self, encrypted_notification): notification = encryption.decrypt(encrypted_notification) service = SerialisedService.from_id(notification['service_id']) q = QueueNames.SEND_EMAIL if notification[ 'notification_type'] == EMAIL_TYPE else QueueNames.SEND_SMS provider_task = provider_tasks.deliver_email if notification['notification_type'] == EMAIL_TYPE \ else provider_tasks.deliver_sms try: persist_notification( notification_id=notification["id"], template_id=notification['template_id'], template_version=notification['template_version'], recipient=notification['to'], service=service, personalisation=notification.get('personalisation'), notification_type=notification['notification_type'], client_reference=notification['client_reference'], api_key_id=notification.get('api_key_id'), key_type=KEY_TYPE_NORMAL, created_at=notification['created_at'], reply_to_text=notification['reply_to_text'], status=notification['status'], document_download_count=notification['document_download_count']) q = q if not service.research_mode else QueueNames.RESEARCH_MODE provider_task.apply_async([notification['id']], queue=q) current_app.logger.debug( f"{notification['notification_type']} {notification['id']} has been persisted and sent to delivery queue." ) except IntegrityError: current_app.logger.info( f"{notification['notification_type']} {notification['id']} already exists." ) except SQLAlchemyError: try: self.retry(queue=QueueNames.RETRY) except self.MaxRetriesExceededError: current_app.logger.error( f"Max retry failed Failed to persist notification {notification['id']}" )
def save_email(self, service_id, notification_id, encrypted_notification, api_key_id=None, key_type=KEY_TYPE_NORMAL): notification = encryption.decrypt(encrypted_notification) service = dao_fetch_service_by_id(service_id) template = dao_get_template_by_id(notification['template'], version=notification['template_version']) if not service_allowed_to_send_to(notification['to'], service, key_type): current_app.logger.info( "Email {} failed as restricted service".format(notification_id)) return try: saved_notification = persist_notification( template_id=notification['template'], template_version=notification['template_version'], recipient=notification['to'], service=service, personalisation=notification.get('personalisation'), notification_type=EMAIL_TYPE, api_key_id=api_key_id, key_type=key_type, created_at=datetime.utcnow(), job_id=notification.get('job', None), job_row_number=notification.get('row_number', None), notification_id=notification_id, reply_to_text=template.get_reply_to_text()) provider_tasks.deliver_email.apply_async( [str(saved_notification.id)], queue=QueueNames.SEND_EMAIL if not service.research_mode else QueueNames.RESEARCH_MODE) current_app.logger.debug("Email {} created at {}".format( saved_notification.id, saved_notification.created_at)) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def send_delivery_status_to_service(self, notification_id, encrypted_status_update): status_update = encryption.decrypt(encrypted_status_update) payload = { "id": str(notification_id), "reference": status_update['notification_client_reference'], "to": status_update['notification_to'], "status": status_update['notification_status'], "created_at": status_update['notification_created_at'], "completed_at": status_update['notification_updated_at'], "sent_at": status_update['notification_sent_at'], "notification_type": status_update['notification_type'] } _send_to_service_callback_api( self, payload, status_update['service_callback_api_url'], status_update['service_callback_api_bearer_token'], logging_tags={'notification_id': str(notification_id)})
def send_complaint_to_service(self, complaint_data): complaint = encryption.decrypt(complaint_data) payload = { "notification_id": complaint['notification_id'], "complaint_id": complaint['complaint_id'], "reference": complaint['reference'], "to": complaint['to'], "complaint_date": complaint['complaint_date'] } _send_to_service_callback_api( self, payload, complaint['service_callback_api_url'], complaint['service_callback_api_bearer_token'], logging_tags={ 'notification_id': complaint['notification_id'], 'complaint_id': complaint['complaint_id'] })
def send_delivery_status_to_service(self, notification_id, encrypted_status_update): status_update = encryption.decrypt(encrypted_status_update) data = { "id": str(notification_id), "reference": status_update["notification_client_reference"], "to": status_update["notification_to"], "status": status_update["notification_status"], "provider_response": status_update["notification_provider_response"], "created_at": status_update["notification_created_at"], "completed_at": status_update["notification_updated_at"], "sent_at": status_update["notification_sent_at"], "notification_type": status_update["notification_type"], } _send_data_to_service_callback_api( self, data, status_update["service_callback_api_url"], status_update["service_callback_api_bearer_token"], "send_delivery_status_to_service", )
def test_create_delivery_status_callback_data( notify_db, notify_db_session, sample_email_template, ): notification = create_sample_notification( notify_db, notify_db_session, template=sample_email_template, status="sending", sent_at=datetime.utcnow(), ) callback_api = create_service_callback_api( service=sample_email_template.service, url="https://original_url.com") assert encryption.decrypt( create_delivery_status_callback_data(notification, callback_api)) == { "notification_client_reference": notification.client_reference, "notification_created_at": notification.created_at.strftime(DATETIME_FORMAT), "notification_id": str(notification.id), "notification_provider_response": notification.provider_response, "notification_sent_at": notification.sent_at.strftime(DATETIME_FORMAT), "notification_status": notification.status, "notification_to": notification.to, "notification_type": notification.notification_type, "notification_updated_at": notification.updated_at, "service_callback_api_bearer_token": callback_api.bearer_token, "service_callback_api_url": callback_api.url, }
def process_sanitised_letter(self, sanitise_data): letter_details = encryption.decrypt(sanitise_data) filename = letter_details['filename'] notification_id = letter_details['notification_id'] current_app.logger.info('Processing sanitised letter with id {}'.format(notification_id)) notification = get_notification_by_id(notification_id, _raise=True) if notification.status != NOTIFICATION_PENDING_VIRUS_CHECK: current_app.logger.info( 'process-sanitised-letter task called for notification {} which is in {} state'.format( notification.id, notification.status) ) return try: original_pdf_object = s3.get_s3_object(current_app.config['LETTERS_SCAN_BUCKET_NAME'], filename) if letter_details['validation_status'] == 'failed': current_app.logger.info('Processing invalid precompiled pdf with id {} (file {})'.format( notification_id, filename)) _move_invalid_letter_and_update_status( notification=notification, filename=filename, scan_pdf_object=original_pdf_object, message=letter_details['message'], invalid_pages=letter_details['invalid_pages'], page_count=letter_details['page_count'], ) return current_app.logger.info('Processing valid precompiled pdf with id {} (file {})'.format( notification_id, filename)) billable_units = get_billable_units_for_letter_page_count(letter_details['page_count']) is_test_key = notification.key_type == KEY_TYPE_TEST # Updating the notification needs to happen before the file is moved. This is so that if updating the # notification fails, the task can retry because the file is in the same place. update_letter_pdf_status( reference=notification.reference, status=NOTIFICATION_DELIVERED if is_test_key else NOTIFICATION_CREATED, billable_units=billable_units, recipient_address=letter_details['address'] ) # The original filename could be wrong because we didn't know the postage. # Now we know if the letter is international, we can check what the filename should be. upload_file_name = get_letter_pdf_filename( reference=notification.reference, crown=notification.service.crown, created_at=notification.created_at, ignore_folder=True, postage=notification.postage ) move_sanitised_letter_to_test_or_live_pdf_bucket( filename, is_test_key, notification.created_at, upload_file_name, ) # We've moved the sanitised PDF from the sanitise bucket, but still need to delete the original file: original_pdf_object.delete() except BotoClientError: # Boto exceptions are likely to be caused by the file(s) being in the wrong place, so retrying won't help - # we'll need to manually investigate current_app.logger.exception( f"Boto error when processing sanitised letter for notification {notification.id} (file {filename})" ) update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE) raise NotificationTechnicalFailureException except Exception: try: current_app.logger.exception( "RETRY: calling process_sanitised_letter task for notification {} failed".format(notification.id) ) self.retry(queue=QueueNames.RETRY) except self.MaxRetriesExceededError: message = "RETRY FAILED: Max retries reached. " \ "The task process_sanitised_letter failed for notification {}. " \ "Notification has been updated to technical-failure".format(notification.id) update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE) raise NotificationTechnicalFailureException(message)
def send_delivery_status_to_service(self, notification_id, encrypted_status_update ): start_time = datetime.utcnow() status_update = encryption.decrypt(encrypted_status_update) try: data = { "id": str(notification_id), "reference": status_update['notification_client_reference'], "to": status_update['notification_to'], "status": status_update['notification_status'], "created_at": status_update['notification_created_at'], "completed_at": status_update['notification_updated_at'], "sent_at": status_update['notification_sent_at'], "notification_type": status_update['notification_type'] } response = request( method="POST", url=status_update['service_callback_api_url'], data=json.dumps(data), headers={ 'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(status_update['service_callback_api_bearer_token']) }, timeout=10 ) current_app.logger.info('send_delivery_status_to_service sending {} to {}, response {}'.format( notification_id, status_update['service_callback_api_url'], response.status_code )) response.raise_for_status() except RequestException as e: end_time = datetime.utcnow() current_app.logger.warning( "send_delivery_status_to_service request failed for notification_id: {} and url: {}. exc: {}".format( notification_id, status_update['service_callback_api_url'], e ) ) record_failed_status_callback.apply_async([], dict( notification_id=notification_id, service_id=status_update['service_id'], service_callback_url=status_update['service_callback_api_url'], notification_api_key_id=status_update['notification_api_key_id'], notification_api_key_type=status_update['notification_api_key_type'], callback_attempt_number=self.request.retries, callback_attempt_started=start_time, callback_attempt_ended=end_time, callback_failure_type=type(e).__name__, service_callback_type='send_delivery_status_to_service', ), queue=QueueNames.NOTIFY) if not isinstance(e, HTTPError) or e.response.status_code >= 500: try: self.retry(queue=QueueNames.RETRY) except self.MaxRetriesExceededError: current_app.logger.exception( """Retry: send_delivery_status_to_service has retried the max num of times for notification: {}""".format(notification_id) )
def personalisation(self): if self._personalisation: return encryption.decrypt(self._personalisation) return None