diff --git a/app/service/rest.py b/app/service/rest.py index eb6ac62e3..39e6800d9 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -595,6 +595,6 @@ def handle_sql_errror(e): @service_blueprint.route('//send-notification', methods=['POST']) -def post_notification(service_id): +def create_one_off_notification(service_id): resp = send_one_off_notification(service_id, request.get_json()) return jsonify(resp), 201 diff --git a/app/service/send_notification.py b/app/service/send_notification.py index 9d93e9656..0e5f46659 100644 --- a/app/service/send_notification.py +++ b/app/service/send_notification.py @@ -1,8 +1,8 @@ from app.config import QueueNames from app.notifications.validators import ( check_service_over_daily_message_limit, - check_sms_content_char_count, validate_and_format_recipient, + validate_template, ) from app.notifications.process_notifications import ( create_content_for_notification, @@ -27,9 +27,7 @@ def send_one_off_notification(service_id, post_data): personalisation = post_data.get('personalisation', None) - if template.template_type == SMS_TYPE: - template_with_content = create_content_for_notification(template, personalisation) - check_sms_content_char_count(template_with_content.content_count) + validate_template(template.id, personalisation, service, template.template_type) check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service) diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index c9bba479d..d932efb4b 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -12,7 +12,7 @@ from app import encryption from app.dao.users_dao import save_model_user from app.dao.services_dao import dao_remove_user_from_service from app.models import ( - User, Organisation, Rate, Service, ServicePermission, + User, Organisation, Rate, Service, ServicePermission, Notification, DVLA_ORG_LAND_REGISTRY, KEY_TYPE_NORMAL, KEY_TYPE_TEAM, KEY_TYPE_TEST, EMAIL_TYPE, SMS_TYPE, LETTER_TYPE, INTERNATIONAL_SMS_TYPE, INBOUND_SMS_TYPE, @@ -27,6 +27,8 @@ from tests.app.conftest import ( sample_notification_history as create_notification_history, sample_notification_with_job ) +from tests.app.db import create_user +from tests.conftest import set_config_values def test_get_service_list(client, service_factory): @@ -2245,3 +2247,20 @@ def test_fetch_service_inbound_api(client, sample_service): assert response.status_code == 200 assert json.loads(response.get_data(as_text=True))["data"] == service_inbound_api.serialize() + + +def test_send_one_off_notification(admin_request, sample_template): + response = admin_request.post( + 'service.create_one_off_notification', + endpoint_kwargs={ + 'service_id': sample_template.service_id + }, + data={ + 'template_id': str(sample_template.id), + 'to': '07700900001' + }, + expected_status=201 + ) + + noti = Notification.query.one() + assert response['id'] == str(noti.id) diff --git a/tests/app/service/test_send_one_off_notification.py b/tests/app/service/test_send_one_off_notification.py new file mode 100644 index 000000000..89ddcd91a --- /dev/null +++ b/tests/app/service/test_send_one_off_notification.py @@ -0,0 +1,180 @@ +import uuid +from unittest.mock import Mock + +import pytest +from notifications_utils.recipients import InvalidPhoneError + +from app.v2.errors import BadRequestError, TooManyRequestsError +from app.config import QueueNames +from app.service.send_notification import send_one_off_notification +from app.models import KEY_TYPE_NORMAL, PRIORITY, SMS_TYPE + +from tests.app.db import create_user + + +@pytest.fixture +def persist_mock(mocker): + noti = Mock(id=uuid.uuid4()) + return mocker.patch('app.service.send_notification.persist_notification', return_value=noti) + + +@pytest.fixture +def celery_mock(mocker): + return mocker.patch('app.service.send_notification.send_notification_to_queue') + + +def test_send_one_off_notification_calls_celery_correctly(persist_mock, celery_mock, sample_template): + service = sample_template.service + + post_data = { + 'template_id': str(sample_template.id), + 'to': '07700 900 001', + 'created_by': str(service.created_by_id) + } + + resp = send_one_off_notification(service.id, post_data) + + assert resp == { + 'id': str(persist_mock.return_value.id) + } + + celery_mock.assert_called_once_with( + notification=persist_mock.return_value, + research_mode=False, + queue=None + ) + + +def test_send_one_off_notification_calls_persist_correctly( + persist_mock, + celery_mock, + sample_template_with_placeholders +): + template = sample_template_with_placeholders + service = template.service + + post_data = { + 'template_id': str(template.id), + 'to': '07700 900 001', + 'personalisation': {'name': 'foo'}, + 'created_by': str(service.created_by_id) + } + + send_one_off_notification(service.id, post_data) + + persist_mock.assert_called_once_with( + template_id=template.id, + template_version=template.version, + recipient=post_data['to'], + service=template.service, + personalisation={'name': 'foo'}, + notification_type=SMS_TYPE, + api_key_id=None, + key_type=KEY_TYPE_NORMAL, + created_by_id=str(service.created_by_id) + ) + + +def test_send_one_off_notification_honors_research_mode(persist_mock, celery_mock, sample_template): + service = sample_template.service + service.research_mode = True + + post_data = { + 'template_id': str(sample_template.id), + 'to': '07700 900 001', + 'created_by': str(service.created_by_id) + } + + send_one_off_notification(service.id, post_data) + + assert celery_mock.call_args[1]['research_mode'] is True + + +def test_send_one_off_notification_honors_priority(persist_mock, celery_mock, sample_template): + service = sample_template.service + sample_template.process_type = PRIORITY + + post_data = { + 'template_id': str(sample_template.id), + 'to': '07700 900 001', + 'created_by': str(service.created_by_id) + } + + send_one_off_notification(service.id, post_data) + + assert celery_mock.call_args[1]['queue'] == QueueNames.PRIORITY + + +def test_send_one_off_notification_raises_if_invalid_recipient(sample_template): + service = sample_template.service + + post_data = { + 'template_id': str(sample_template.id), + 'to': 'not a phone number', + 'created_by': str(service.created_by_id) + } + + with pytest.raises(InvalidPhoneError): + send_one_off_notification(service.id, post_data) + + +def test_send_one_off_notification_raises_if_cant_send_to_recipient(sample_template): + service = sample_template.service + service.restricted = True + + post_data = { + 'template_id': str(sample_template.id), + 'to': '07700 900 001', + 'created_by': str(service.created_by_id) + } + + with pytest.raises(BadRequestError) as e: + send_one_off_notification(service.id, post_data) + + assert 'service is in trial mode' in e.value.message + + +def test_send_one_off_notification_raises_if_over_limit(sample_template): + service = sample_template.service + service.message_limit = 0 + + post_data = { + 'template_id': str(sample_template.id), + 'to': '07700 900 001', + 'created_by': str(service.created_by_id) + } + + with pytest.raises(TooManyRequestsError): + send_one_off_notification(service.id, post_data) + + +def test_send_one_off_notification_raises_if_message_too_long(persist_mock, sample_template_with_placeholders): + template = sample_template_with_placeholders + service = template.service + + post_data = { + 'template_id': str(template.id), + 'to': '07700 900 001', + 'personalisation': {'name': '🚫' * 500}, + 'created_by': str(service.created_by_id) + } + + with pytest.raises(BadRequestError) as e: + send_one_off_notification(service.id, post_data) + + assert e.value.message == 'Content for template has a character count greater than the limit of 495' + + +def test_send_one_off_notification_fails_if_created_by_other_service(sample_template): + user_not_in_service = create_user(email='some-other-user@gov.uk') + + post_data = { + 'template_id': str(sample_template.id), + 'to': '07700 900 001', + 'created_by': str(user_not_in_service.id) + } + + with pytest.raises(BadRequestError) as e: + send_one_off_notification(sample_template.service_id, post_data) + + assert e.value.message == 'Can’t create notification - Test User is not part of the "Sample service" service'