diff --git a/app/celery/provider_tasks.py b/app/celery/provider_tasks.py index ffc5787f9..14c3ba937 100644 --- a/app/celery/provider_tasks.py +++ b/app/celery/provider_tasks.py @@ -31,6 +31,47 @@ def retry_iteration_to_delay(retry=0): return delays.get(retry, 10) +@notify_celery.task(bind=True, name="deliver_sms", max_retries=5, default_retry_delay=5) +@statsd(namespace="tasks") +def deliver_sms(self, notification_id): + try: + send_to_providers.send_sms_to_provider(notification_id) + except Exception as e: + try: + current_app.logger.error( + "RETRY: SMS notification {} failed".format(notification_id) + ) + current_app.logger.exception(e) + self.retry(queue="retry", countdown=retry_iteration_to_delay(self.request.retries)) + except self.MaxRetriesExceededError: + current_app.logger.error( + "RETRY FAILED: task send_sms_to_provider failed for notification {}".format(notification_id), + e + ) + update_notification_status_by_id(notification_id, 'technical-failure') + + +@notify_celery.task(bind=True, name="deliver_email", max_retries=5, default_retry_delay=5) +@statsd(namespace="tasks") +def deliver_email(self, notification_id): + try: + send_to_providers.send_email_response(notification_id) + except Exception as e: + try: + current_app.logger.error( + "RETRY: Email notification {} failed".format(notification_id) + ) + current_app.logger.exception(e) + self.retry(queue="retry", countdown=retry_iteration_to_delay(self.request.retries)) + except self.MaxRetriesExceededError: + current_app.logger.error( + "RETRY FAILED: task send_email_to_provider failed for notification {}".format(notification_id), + e + ) + update_notification_status_by_id(notification_id, 'technical-failure') + + + @notify_celery.task(bind=True, name="send-sms-to-provider", max_retries=5, default_retry_delay=5) @statsd(namespace="tasks") def send_sms_to_provider(self, service_id, notification_id): diff --git a/app/delivery/rest.py b/app/delivery/rest.py new file mode 100644 index 000000000..67cca534b --- /dev/null +++ b/app/delivery/rest.py @@ -0,0 +1,34 @@ +from flask import Blueprint, jsonify + +from app.delivery import send_to_providers +from app.models import EMAIL_TYPE +from app.celery import provider_tasks +from sqlalchemy.orm.exc import NoResultFound + +delivery = Blueprint('delivery', __name__) + +from app.errors import ( + register_errors, + InvalidRequest +) + +register_errors(delivery) + + +@delivery.route('/deliver/notification/', methods=['POST']) +def send_notification_to_provider(notification_type, notification_id): + + if notification_type == EMAIL_TYPE: + send_response(send_to_providers.send_email_response, provider_tasks.deliver_email, notification_id, 'send-email') + else: + send_response(send_to_providers.send_sms_response, provider_tasks.deliver_sms, notification_id, 'send-sms') + return jsonify({}), 204 + + +def send_response(send_call, task_call, notification_id, queue): + try: + send_call(notification_id) + except NoResultFound as e: + raise InvalidRequest(e, status_code=404) + except Exception as e: + task_call.apply_async((str(notification_id)), queue=queue) diff --git a/app/delivery/send_to_providers.py b/app/delivery/send_to_providers.py index 73d7fc150..8b43ecec0 100644 --- a/app/delivery/send_to_providers.py +++ b/app/delivery/send_to_providers.py @@ -20,10 +20,9 @@ from app.dao.templates_dao import dao_get_template_by_id from app.models import SMS_TYPE, KEY_TYPE_TEST, BRANDING_ORG, EMAIL_TYPE -def send_sms_to_provider(notification_id): - notification = get_notification_by_id(notification_id) +def send_sms_to_provider(notification): service = dao_fetch_service_by_id(notification.service_id) - provider = provider_to_use(SMS_TYPE, notification_id) + provider = provider_to_use(SMS_TYPE, notification.id) if notification.status == 'created': template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = Template( @@ -33,7 +32,7 @@ def send_sms_to_provider(notification_id): ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: send_sms_response.apply_async( - (provider.get_name(), str(notification_id), notification.to), queue='research-mode' + (provider.get_name(), str(notification.id), notification.to), queue='research-mode' ) notification.billable_units = 0 else: diff --git a/tests/app/celery/test_provider_tasks.py b/tests/app/celery/test_provider_tasks.py index 310184629..982bf612b 100644 --- a/tests/app/celery/test_provider_tasks.py +++ b/tests/app/celery/test_provider_tasks.py @@ -1,31 +1,8 @@ -import uuid -from datetime import datetime - -import pytest from celery.exceptions import MaxRetriesExceededError -from unittest.mock import ANY, call -from notifications_utils.recipients import validate_phone_number, format_phone_number - -import app -from app import statsd_client, mmg_client from app.celery import provider_tasks from app.celery.provider_tasks import send_sms_to_provider, send_email_to_provider -from app.celery.research_mode_tasks import send_sms_response, send_email_response from app.clients.email import EmailClientException -from app.clients.sms import SmsClientException -from app.dao import notifications_dao, provider_details_dao -from app.dao import provider_statistics_dao -from app.dao.provider_statistics_dao import get_provider_statistics -from app.models import ( - Notification, - NotificationStatistics, - Job, - Organisation, - KEY_TYPE_NORMAL, - KEY_TYPE_TEST, - BRANDING_ORG, - BRANDING_BOTH -) +from app.models import Notification from tests.app.conftest import sample_notification diff --git a/tests/app/delivery/test_send_to_providers.py b/tests/app/delivery/test_send_to_providers.py index 755f33835..af49d64f5 100644 --- a/tests/app/delivery/test_send_to_providers.py +++ b/tests/app/delivery/test_send_to_providers.py @@ -5,6 +5,7 @@ import pytest from mock import ANY import app +from sqlalchemy.orm.exc import NoResultFound from app import mmg_client from app.dao import (provider_details_dao, notifications_dao, provider_statistics_dao) from app.dao.provider_statistics_dao import get_provider_statistics @@ -46,6 +47,18 @@ def test_should_return_highest_priority_active_provider(notify_db, notify_db_ses assert send_to_providers.provider_to_use('sms', '1234').name == first.identifier +def test_raises_not_found_exception_if_no_notification_for_id(notify_db, notify_db_session, mocker): + mocker.patch('app.mmg_client.send_sms') + mocker.patch('app.mmg_client.get_name', return_value="mmg") + notification_id = uuid.uuid4() + + with pytest.raises(NoResultFound) as exc: + send_to_providers.send_sms_to_provider(notification_id) + + assert str(exc.value) == "No notification for {}".format(str(notification_id)) + app.mmg_client.send_sms.assert_not_called() + + def test_should_send_personalised_template_to_correct_sms_provider_and_persist( notify_db, notify_db_session,