diff --git a/app/models.py b/app/models.py index 445977251..a9a4b8748 100644 --- a/app/models.py +++ b/app/models.py @@ -22,7 +22,7 @@ from app.authentication.utils import get_secret from app import ( db, encryption, - DATETIME_FORMAT) + DATETIME_FORMAT, create_uuid) from app.history_meta import Versioned @@ -562,6 +562,30 @@ class Notification(db.Model): key_type=key_type ) + @classmethod + def from_v2_api_request(cls, + template_id, + template_version, + recipient, + service_id, + personalisation, + notification_type, + api_key_id, + key_type): + return cls( + id=create_uuid(), + template_id=template_id, + template_version=template_version, + to=recipient, + service_id=service_id, + status='created', + created_at=datetime.datetime.strftime(datetime.datetime.utcnow(), DATETIME_FORMAT), + personalisation=personalisation, + notification_type=notification_type, + api_key_id=api_key_id, + key_type=key_type + ) + class NotificationHistory(db.Model): __tablename__ = 'notification_history' diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index 830c51c47..2b0482e97 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -1,7 +1,11 @@ +from flask import current_app from notifications_utils.renderers import PassThrough from notifications_utils.template import Template -from app.models import SMS_TYPE +from app.celery import provider_tasks +from app.dao.notifications_dao import dao_create_notification, dao_delete_notifications_and_history_by_id +from app.errors import InvalidRequest +from app.models import SMS_TYPE, Notification, KEY_TYPE_TEST, EMAIL_TYPE from app.notifications.validators import check_sms_content_char_count from app.v2.errors import BadRequestError @@ -27,17 +31,44 @@ def create_content_for_notification(template, personalisation): return template_object -def persist_notification(): - ''' - persist the notification - :return: - ''' - pass +def persist_notification(template_id, + template_version, + recipient, + service_id, + personalisation, + notification_type, + api_key_id, + key_type): + notification = Notification.from_v2_api_request(template_id, + template_version, + recipient, + service_id, + personalisation, + notification_type, + api_key_id, + key_type) + dao_create_notification(notification) + return notification -def send_notificaiton_to_queue(): - ''' - send the notification to the queue - :return: - ''' - pass +def send_notification_to_queue(notification, research_mode): + try: + research_mode = research_mode or notification.key_type == KEY_TYPE_TEST + if notification.notification_type == SMS_TYPE: + provider_tasks.deliver_sms.apply_async( + [str(notification.id)], + queue='send-sms' if not research_mode else 'research-mode' + ) + if notification.notification_type == EMAIL_TYPE: + provider_tasks.deliver_email.apply_async( + [str(notification.id)], + queue='send-email' if not research_mode else 'research-mode' + ) + except Exception as e: + current_app.logger.exception("Failed to send to SQS exception") + dao_delete_notifications_and_history_by_id(notification.id) + raise InvalidRequest(message="Internal server error", status_code=500) + + current_app.logger.info( + "{} {} created at {}".format(notification.notification_type, notification.id, notification.created_at) + ) diff --git a/app/v2/notifications/notification_schemas.py b/app/v2/notifications/notification_schemas.py index 24bb7c1de..1fac4c7c7 100644 --- a/app/v2/notifications/notification_schemas.py +++ b/app/v2/notifications/notification_schemas.py @@ -54,3 +54,14 @@ post_sms_response = { }, "required": ["id", "content", "uri", "template"] } + + +def create_post_sms_response_from_notification(notification, content): + return {"id": notification.id, + "reference": None, # not yet implemented + "content": content, + "uri": "v2/notifications/{}".format(notification.id), + "template": {"id": notification.template_id, + "version": notification.template_version, + "uri": "v2/templates/{}".format(notification.template_id)} + } diff --git a/app/v2/notifications/post_notifications.py b/app/v2/notifications/post_notifications.py index 2b36fbeca..4b4a3162a 100644 --- a/app/v2/notifications/post_notifications.py +++ b/app/v2/notifications/post_notifications.py @@ -1,9 +1,9 @@ -from flask import request - +from flask import request, jsonify from app import api_user from app.dao import services_dao, templates_dao from app.models import SMS_TYPE -from app.notifications.process_notifications import create_content_for_notification +from app.notifications.process_notifications import create_content_for_notification, persist_notification, \ + send_notification_to_queue from app.notifications.validators import (check_service_message_limit, check_template_is_for_notification_type, check_template_is_active, @@ -11,7 +11,8 @@ from app.notifications.validators import (check_service_message_limit, check_sms_content_char_count) from app.schema_validation import validate from app.v2.notifications import notification_blueprint -from app.v2.notifications.notification_schemas import post_sms_request +from app.v2.notifications.notification_schemas import (post_sms_request, + create_post_sms_response_from_notification) @notification_blueprint.route('/sms', methods=['POST']) @@ -34,9 +35,17 @@ def post_sms_notification(): check_sms_content_char_count(template_with_content.replaced_content_count) # persist notification - # send sms to provider queue for research mode queue - # return post_sms_response schema - return "post_sms_response schema", 201 + notification = persist_notification(template_id=template.id, + template_version=template.version, + recipient=form['phone_number'], + service_id=service.id, + personalisation=form.get('personalisation', None), + notification_type=SMS_TYPE, + api_key_id=api_user.id, + key_type=api_user.key_type) + send_notification_to_queue(notification, service.research_mode) + resp = create_post_sms_response_from_notification(notification, template_with_content.content) + return jsonify(resp), 201 @notification_blueprint.route('/email', methods=['POST']) diff --git a/tests/app/notifications/test_process_notification.py b/tests/app/notifications/test_process_notification.py index a7edc571e..7407ffe56 100644 --- a/tests/app/notifications/test_process_notification.py +++ b/tests/app/notifications/test_process_notification.py @@ -1,8 +1,12 @@ import pytest +from sqlalchemy.exc import SQLAlchemyError -from app.models import Template -from app.notifications.process_notifications import create_content_for_notification +from app.errors import InvalidRequest +from app.models import Template, Notification, NotificationHistory +from app.notifications.process_notifications import (create_content_for_notification, + persist_notification, send_notification_to_queue) from app.v2.errors import BadRequestError +from tests.app.conftest import sample_notification, sample_template, sample_email_template def test_create_content_for_notification_passes(sample_email_template): @@ -15,3 +19,51 @@ def test_create_content_for_notification_fails_with_missing_personalisation(samp template = Template.query.get(sample_template_with_placeholders.id) with pytest.raises(BadRequestError): create_content_for_notification(template, None) + + +def test_persist_notification_creates_and_save_to_db(sample_template, sample_api_key): + assert Notification.query.count() == 0 + assert NotificationHistory.query.count() == 0 + notification = persist_notification(sample_template.id, sample_template.version, '+447111111111', + sample_template.service.id, {}, 'sms', sample_api_key.id, + sample_api_key.key_type) + assert Notification.query.count() == 1 + assert Notification.query.get(notification.id).__eq__(notification) + assert NotificationHistory.query.count() == 1 + + +def test_persist_notification_throws_exception_when_missing_template(sample_template, sample_api_key): + assert Notification.query.count() == 0 + with pytest.raises(SQLAlchemyError): + persist_notification(template_id=None, + template_version=None, + recipient='+447111111111', + service_id=sample_template.service.id, + personalisation=None, notification_type='sms', + api_key_id=sample_api_key.id, + key_type=sample_api_key.key_type) + + +@pytest.mark.parametrize('research_mode, queue, notification_type, key_type', + [(True, 'research-mode', 'sms', 'normal'), + (False, 'send-sms', 'sms', 'normal'), + (True, 'research-mode', 'email', 'normal'), + (False, 'send-email', 'email', 'normal'), + (False, 'research-mode', 'sms', 'test')]) +def test_send_notification_to_queue(notify_db, notify_db_session, + research_mode, notification_type, + queue, key_type, mocker): + mocked = mocker.patch('app.celery.provider_tasks.deliver_{}.apply_async'.format(notification_type)) + template = sample_template(notify_db, notify_db_session) if notification_type == 'sms' \ + else sample_email_template(notify_db, notify_db_session) + notification = sample_notification(notify_db, notify_db_session, template=template, key_type=key_type) + send_notification_to_queue(notification=notification, research_mode=research_mode) + + mocked.assert_called_once_with([str(notification.id)], queue=queue) + + +def test_send_notification_to_queue(sample_notification, mocker): + mocked = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async', side_effect=Exception("EXPECTED")) + with pytest.raises(InvalidRequest): + send_notification_to_queue(sample_notification, False) + mocked.assert_called_once_with([(str(sample_notification.id))], queue='send-sms') diff --git a/tests/app/v2/notifications/test_post_notifications.py b/tests/app/v2/notifications/test_post_notifications.py index e76ec5ef7..44e5e78ac 100644 --- a/tests/app/v2/notifications/test_post_notifications.py +++ b/tests/app/v2/notifications/test_post_notifications.py @@ -3,9 +3,10 @@ from flask import json from tests import create_authorization_header -def test_post_sms_notification_returns_201(notify_api, sample_template): +def test_post_sms_notification_returns_201(notify_api, sample_template, mocker): with notify_api.test_request_context(): with notify_api.test_client() as client: + mocked = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async') data = { 'phone_number': '+447700900855', 'template_id': str(sample_template.id) @@ -18,6 +19,12 @@ def test_post_sms_notification_returns_201(notify_api, sample_template): headers=[('Content-Type', 'application/json'), auth_header]) assert response.status_code == 201 + resp_json = json.loads(response.get_data(as_text=True)) + assert resp_json['id'] is not None + assert resp_json['reference'] is None + assert resp_json['template']['id'] == str(sample_template.id) + assert resp_json['template']['version'] == sample_template.version + assert mocked.called def test_post_sms_notification_returns_404_when_template_is_wrong_type(notify_api, sample_email_template):