2016-11-11 14:56:33 +00:00
|
|
|
import datetime
|
2016-11-25 17:32:01 +00:00
|
|
|
import uuid
|
2016-11-11 14:56:33 +00:00
|
|
|
|
2016-10-27 11:46:37 +01:00
|
|
|
import pytest
|
2016-10-28 17:10:00 +01:00
|
|
|
from boto3.exceptions import Boto3Error
|
2016-10-27 17:34:54 +01:00
|
|
|
from sqlalchemy.exc import SQLAlchemyError
|
2016-11-22 12:53:20 +00:00
|
|
|
from freezegun import freeze_time
|
2016-12-09 17:37:18 +00:00
|
|
|
from collections import namedtuple
|
2016-10-27 11:46:37 +01:00
|
|
|
|
2016-10-27 17:34:54 +01:00
|
|
|
from app.models import Template, Notification, NotificationHistory
|
2016-11-21 15:11:19 +00:00
|
|
|
from app.notifications import SendNotificationToQueueError
|
2017-04-26 17:26:06 +01:00
|
|
|
from app.notifications.process_notifications import (
|
|
|
|
|
create_content_for_notification,
|
|
|
|
|
persist_notification,
|
|
|
|
|
send_notification_to_queue,
|
|
|
|
|
simulated_recipient
|
|
|
|
|
)
|
|
|
|
|
from notifications_utils.recipients import validate_and_format_phone_number, validate_and_format_email_address
|
2017-02-14 14:22:52 +00:00
|
|
|
from app.utils import cache_key_for_service_template_counter
|
2016-10-27 11:46:37 +01:00
|
|
|
from app.v2.errors import BadRequestError
|
2017-03-30 13:43:44 +01:00
|
|
|
from tests.app.conftest import sample_api_key as create_api_key
|
2016-10-27 11:46:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_create_content_for_notification_passes(sample_email_template):
|
|
|
|
|
template = Template.query.get(sample_email_template.id)
|
|
|
|
|
content = create_content_for_notification(template, None)
|
2016-12-09 15:56:25 +00:00
|
|
|
assert str(content) == template.content
|
2016-10-28 17:10:00 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_create_content_for_notification_with_placeholders_passes(sample_template_with_placeholders):
|
|
|
|
|
template = Template.query.get(sample_template_with_placeholders.id)
|
|
|
|
|
content = create_content_for_notification(template, {'name': 'Bobby'})
|
2016-10-27 11:46:37 +01:00
|
|
|
assert content.content == template.content
|
2016-12-09 15:56:25 +00:00
|
|
|
assert 'Bobby' in str(content)
|
2016-10-27 11:46:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_create_content_for_notification_fails_with_missing_personalisation(sample_template_with_placeholders):
|
|
|
|
|
template = Template.query.get(sample_template_with_placeholders.id)
|
|
|
|
|
with pytest.raises(BadRequestError):
|
|
|
|
|
create_content_for_notification(template, None)
|
2016-10-27 17:34:54 +01:00
|
|
|
|
|
|
|
|
|
2017-03-07 16:03:10 +00:00
|
|
|
def test_create_content_for_notification_allows_additional_personalisation(sample_template_with_placeholders):
|
2016-10-28 17:10:00 +01:00
|
|
|
template = Template.query.get(sample_template_with_placeholders.id)
|
2017-03-07 16:03:10 +00:00
|
|
|
create_content_for_notification(template, {'name': 'Bobby', 'Additional placeholder': 'Data'})
|
2016-10-28 17:10:00 +01:00
|
|
|
|
|
|
|
|
|
2016-11-22 12:53:20 +00:00
|
|
|
@freeze_time("2016-01-01 11:09:00.061258")
|
2016-12-15 16:59:03 +00:00
|
|
|
def test_persist_notification_creates_and_save_to_db(sample_template, sample_api_key, sample_job, mocker):
|
2017-02-15 11:49:19 +00:00
|
|
|
mocked_redis = mocker.patch('app.notifications.process_notifications.redis_store.get')
|
2016-11-22 12:53:20 +00:00
|
|
|
|
2016-10-27 17:34:54 +01:00
|
|
|
assert Notification.query.count() == 0
|
|
|
|
|
assert NotificationHistory.query.count() == 0
|
|
|
|
|
notification = persist_notification(sample_template.id, sample_template.version, '+447111111111',
|
2016-12-19 16:45:18 +00:00
|
|
|
sample_template.service, {}, 'sms', sample_api_key.id,
|
2016-12-15 16:59:03 +00:00
|
|
|
sample_api_key.key_type, job_id=sample_job.id,
|
|
|
|
|
job_row_number=100, reference="ref")
|
|
|
|
|
|
2016-11-01 10:33:34 +00:00
|
|
|
assert Notification.query.get(notification.id) is not None
|
2016-12-15 16:59:03 +00:00
|
|
|
assert NotificationHistory.query.get(notification.id) is not None
|
|
|
|
|
|
|
|
|
|
notification_from_db = Notification.query.one()
|
|
|
|
|
notification_history_from_db = NotificationHistory.query.one()
|
|
|
|
|
|
|
|
|
|
assert notification_from_db.id == notification_history_from_db.id
|
|
|
|
|
assert notification_from_db.template_id == notification_history_from_db.template_id
|
|
|
|
|
assert notification_from_db.template_version == notification_history_from_db.template_version
|
|
|
|
|
assert notification_from_db.api_key_id == notification_history_from_db.api_key_id
|
|
|
|
|
assert notification_from_db.key_type == notification_history_from_db.key_type
|
|
|
|
|
assert notification_from_db.key_type == notification_history_from_db.key_type
|
|
|
|
|
assert notification_from_db.billable_units == notification_history_from_db.billable_units
|
|
|
|
|
assert notification_from_db.notification_type == notification_history_from_db.notification_type
|
|
|
|
|
assert notification_from_db.created_at == notification_history_from_db.created_at
|
|
|
|
|
assert not notification_from_db.sent_at
|
|
|
|
|
assert not notification_history_from_db.sent_at
|
|
|
|
|
assert notification_from_db.updated_at == notification_history_from_db.updated_at
|
|
|
|
|
assert notification_from_db.status == notification_history_from_db.status
|
|
|
|
|
assert notification_from_db.reference == notification_history_from_db.reference
|
|
|
|
|
assert notification_from_db.client_reference == notification_history_from_db.client_reference
|
|
|
|
|
|
2016-11-22 12:53:20 +00:00
|
|
|
mocked_redis.assert_called_once_with(str(sample_template.service_id) + "-2016-01-01-count")
|
2016-10-27 17:34:54 +01:00
|
|
|
|
|
|
|
|
|
2016-11-11 14:56:33 +00:00
|
|
|
def test_persist_notification_throws_exception_when_missing_template(sample_api_key):
|
2016-10-27 17:34:54 +01:00
|
|
|
assert Notification.query.count() == 0
|
2016-10-28 17:10:00 +01:00
|
|
|
assert NotificationHistory.query.count() == 0
|
2016-10-27 17:34:54 +01:00
|
|
|
with pytest.raises(SQLAlchemyError):
|
|
|
|
|
persist_notification(template_id=None,
|
|
|
|
|
template_version=None,
|
|
|
|
|
recipient='+447111111111',
|
2016-12-19 16:45:18 +00:00
|
|
|
service=sample_api_key.service,
|
2016-11-11 14:56:33 +00:00
|
|
|
personalisation=None,
|
|
|
|
|
notification_type='sms',
|
2016-10-27 17:34:54 +01:00
|
|
|
api_key_id=sample_api_key.id,
|
|
|
|
|
key_type=sample_api_key.key_type)
|
2016-10-28 17:10:00 +01:00
|
|
|
assert Notification.query.count() == 0
|
|
|
|
|
assert NotificationHistory.query.count() == 0
|
2016-10-27 17:34:54 +01:00
|
|
|
|
|
|
|
|
|
2016-11-22 12:53:20 +00:00
|
|
|
def test_exception_thown_by_redis_store_get_should_not_be_fatal(sample_template, sample_api_key, mocker):
|
|
|
|
|
mocker.patch(
|
|
|
|
|
'app.notifications.process_notifications.redis_store.redis_store.incr',
|
|
|
|
|
side_effect=Exception("broken redis"))
|
|
|
|
|
|
|
|
|
|
notification = persist_notification(
|
|
|
|
|
sample_template.id,
|
|
|
|
|
sample_template.version,
|
|
|
|
|
'+447111111111',
|
2016-12-19 16:45:18 +00:00
|
|
|
sample_template.service,
|
2016-11-22 12:53:20 +00:00
|
|
|
{},
|
|
|
|
|
'sms',
|
|
|
|
|
sample_api_key.id,
|
|
|
|
|
sample_api_key.key_type)
|
|
|
|
|
assert Notification.query.count() == 1
|
|
|
|
|
assert Notification.query.get(notification.id) is not None
|
|
|
|
|
assert NotificationHistory.query.count() == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_cache_is_not_incremented_on_failure_to_persist_notification(sample_api_key, mocker):
|
2017-02-15 11:49:19 +00:00
|
|
|
mocked_redis = mocker.patch('app.redis_store.get')
|
|
|
|
|
mock_service_template_cache = mocker.patch('app.redis_store.get_all_from_hash')
|
2016-11-22 12:53:20 +00:00
|
|
|
with pytest.raises(SQLAlchemyError):
|
|
|
|
|
persist_notification(template_id=None,
|
|
|
|
|
template_version=None,
|
|
|
|
|
recipient='+447111111111',
|
2016-12-19 16:45:18 +00:00
|
|
|
service=sample_api_key.service,
|
2016-11-22 12:53:20 +00:00
|
|
|
personalisation=None,
|
|
|
|
|
notification_type='sms',
|
|
|
|
|
api_key_id=sample_api_key.id,
|
|
|
|
|
key_type=sample_api_key.key_type)
|
|
|
|
|
mocked_redis.assert_not_called()
|
2017-02-14 14:22:52 +00:00
|
|
|
mock_service_template_cache.assert_not_called()
|
2016-11-22 12:53:20 +00:00
|
|
|
|
|
|
|
|
|
2017-03-30 13:43:44 +01:00
|
|
|
def test_persist_notification_does_not_increment_cache_if_test_key(
|
|
|
|
|
notify_db, notify_db_session, sample_template, sample_job, mocker
|
|
|
|
|
):
|
|
|
|
|
api_key = create_api_key(notify_db=notify_db, notify_db_session=notify_db_session, service=sample_template.service,
|
|
|
|
|
key_type='test')
|
|
|
|
|
mocker.patch('app.notifications.process_notifications.redis_store.get', return_value="cache")
|
|
|
|
|
mocker.patch('app.notifications.process_notifications.redis_store.get_all_from_hash', return_value="cache")
|
|
|
|
|
daily_limit_cache = mocker.patch('app.notifications.process_notifications.redis_store.incr')
|
|
|
|
|
template_usage_cache = mocker.patch('app.notifications.process_notifications.redis_store.increment_hash_value')
|
|
|
|
|
|
|
|
|
|
assert Notification.query.count() == 0
|
|
|
|
|
assert NotificationHistory.query.count() == 0
|
|
|
|
|
persist_notification(
|
|
|
|
|
sample_template.id,
|
|
|
|
|
sample_template.version,
|
|
|
|
|
'+447111111111',
|
|
|
|
|
sample_template.service,
|
|
|
|
|
{},
|
|
|
|
|
'sms',
|
|
|
|
|
api_key.id,
|
|
|
|
|
api_key.key_type,
|
|
|
|
|
job_id=sample_job.id,
|
|
|
|
|
job_row_number=100,
|
|
|
|
|
reference="ref")
|
|
|
|
|
|
|
|
|
|
assert Notification.query.count() == 1
|
|
|
|
|
|
|
|
|
|
assert not daily_limit_cache.called
|
|
|
|
|
assert not template_usage_cache.called
|
|
|
|
|
|
|
|
|
|
|
2016-11-22 12:53:20 +00:00
|
|
|
@freeze_time("2016-01-01 11:09:00.061258")
|
2016-11-22 13:00:37 +00:00
|
|
|
def test_persist_notification_with_optionals(sample_job, sample_api_key, mocker):
|
2016-11-11 14:56:33 +00:00
|
|
|
assert Notification.query.count() == 0
|
|
|
|
|
assert NotificationHistory.query.count() == 0
|
2017-02-15 11:49:19 +00:00
|
|
|
mocked_redis = mocker.patch('app.notifications.process_notifications.redis_store.get')
|
2017-02-14 14:22:52 +00:00
|
|
|
mock_service_template_cache = mocker.patch(
|
2017-02-15 11:49:19 +00:00
|
|
|
'app.notifications.process_notifications.redis_store.get_all_from_hash')
|
2016-11-25 17:32:01 +00:00
|
|
|
n_id = uuid.uuid4()
|
2016-11-11 14:56:33 +00:00
|
|
|
created_at = datetime.datetime(2016, 11, 11, 16, 8, 18)
|
2017-04-26 10:22:20 +01:00
|
|
|
persist_notification(
|
2017-04-26 11:52:03 +01:00
|
|
|
template_id=sample_job.template.id,
|
|
|
|
|
template_version=sample_job.template.version,
|
|
|
|
|
recipient='+447111111111',
|
|
|
|
|
service=sample_job.service,
|
|
|
|
|
personalisation=None,
|
|
|
|
|
notification_type='sms',
|
|
|
|
|
api_key_id=sample_api_key.id,
|
|
|
|
|
key_type=sample_api_key.key_type,
|
|
|
|
|
created_at=created_at,
|
|
|
|
|
job_id=sample_job.id,
|
|
|
|
|
job_row_number=10,
|
|
|
|
|
client_reference="ref from client",
|
|
|
|
|
notification_id=n_id
|
|
|
|
|
)
|
2016-11-11 14:56:33 +00:00
|
|
|
assert Notification.query.count() == 1
|
|
|
|
|
assert NotificationHistory.query.count() == 1
|
|
|
|
|
persisted_notification = Notification.query.all()[0]
|
2016-11-25 17:32:01 +00:00
|
|
|
assert persisted_notification.id == n_id
|
|
|
|
|
persisted_notification.job_id == sample_job.id
|
2016-11-11 14:56:33 +00:00
|
|
|
assert persisted_notification.job_row_number == 10
|
|
|
|
|
assert persisted_notification.created_at == created_at
|
2016-11-22 12:53:20 +00:00
|
|
|
mocked_redis.assert_called_once_with(str(sample_job.service_id) + "-2016-01-01-count")
|
2017-02-15 11:49:19 +00:00
|
|
|
mock_service_template_cache.assert_called_once_with(cache_key_for_service_template_counter(sample_job.service_id))
|
2016-11-17 13:42:34 +00:00
|
|
|
assert persisted_notification.client_reference == "ref from client"
|
|
|
|
|
assert persisted_notification.reference is None
|
2017-04-26 10:22:20 +01:00
|
|
|
assert persisted_notification.international is False
|
|
|
|
|
assert persisted_notification.phone_prefix is None
|
|
|
|
|
assert persisted_notification.rate_multiplier is None
|
|
|
|
|
|
2016-11-11 14:56:33 +00:00
|
|
|
|
2017-02-15 11:49:19 +00:00
|
|
|
@freeze_time("2016-01-01 11:09:00.061258")
|
|
|
|
|
def test_persist_notification_increments_cache_if_key_exists(sample_template, sample_api_key, mocker):
|
|
|
|
|
mock_incr = mocker.patch('app.notifications.process_notifications.redis_store.incr')
|
|
|
|
|
mock_incr_hash_value = mocker.patch('app.notifications.process_notifications.redis_store.increment_hash_value')
|
|
|
|
|
|
|
|
|
|
persist_notification(sample_template.id, sample_template.version, '+447111111111',
|
2017-02-15 11:59:31 +00:00
|
|
|
sample_template.service, {}, 'sms', sample_api_key.id,
|
|
|
|
|
sample_api_key.key_type, reference="ref")
|
2017-02-15 11:49:19 +00:00
|
|
|
mock_incr.assert_not_called()
|
|
|
|
|
mock_incr_hash_value.assert_not_called()
|
|
|
|
|
|
|
|
|
|
mocker.patch('app.notifications.process_notifications.redis_store.get', return_value=1)
|
|
|
|
|
mocker.patch('app.notifications.process_notifications.redis_store.get_all_from_hash',
|
|
|
|
|
return_value={sample_template.id, 1})
|
|
|
|
|
persist_notification(sample_template.id, sample_template.version, '+447111111122',
|
2017-02-15 11:59:31 +00:00
|
|
|
sample_template.service, {}, 'sms', sample_api_key.id,
|
|
|
|
|
sample_api_key.key_type, reference="ref2")
|
2017-02-15 11:49:19 +00:00
|
|
|
mock_incr.assert_called_once_with(str(sample_template.service_id) + "-2016-01-01-count", )
|
|
|
|
|
mock_incr_hash_value.assert_called_once_with(cache_key_for_service_template_counter(sample_template.service_id),
|
|
|
|
|
sample_template.id)
|
|
|
|
|
|
|
|
|
|
|
2016-12-09 12:10:42 +00:00
|
|
|
@pytest.mark.parametrize('research_mode, requested_queue, expected_queue, notification_type, key_type',
|
|
|
|
|
[(True, None, 'research-mode', 'sms', 'normal'),
|
|
|
|
|
(True, None, 'research-mode', 'email', 'normal'),
|
|
|
|
|
(True, None, 'research-mode', 'email', 'team'),
|
|
|
|
|
(False, None, 'send-sms', 'sms', 'normal'),
|
|
|
|
|
(False, None, 'send-email', 'email', 'normal'),
|
|
|
|
|
(False, None, 'send-sms', 'sms', 'team'),
|
|
|
|
|
(False, None, 'research-mode', 'sms', 'test'),
|
|
|
|
|
(True, 'notify', 'research-mode', 'email', 'normal'),
|
|
|
|
|
(False, 'notify', 'notify', 'sms', 'normal'),
|
|
|
|
|
(False, 'notify', 'notify', 'email', 'normal'),
|
|
|
|
|
(False, 'notify', 'research-mode', 'sms', 'test')])
|
2016-10-27 17:34:54 +01:00
|
|
|
def test_send_notification_to_queue(notify_db, notify_db_session,
|
2016-12-09 12:10:42 +00:00
|
|
|
research_mode, requested_queue, expected_queue,
|
|
|
|
|
notification_type, key_type, mocker):
|
2016-10-27 17:34:54 +01:00
|
|
|
mocked = mocker.patch('app.celery.provider_tasks.deliver_{}.apply_async'.format(notification_type))
|
2016-12-09 17:37:18 +00:00
|
|
|
Notification = namedtuple('Notification', ['id', 'key_type', 'notification_type', 'created_at'])
|
|
|
|
|
notification = Notification(
|
|
|
|
|
id=uuid.uuid4(),
|
|
|
|
|
key_type=key_type,
|
|
|
|
|
notification_type=notification_type,
|
|
|
|
|
created_at=datetime.datetime(2016, 11, 11, 16, 8, 18),
|
|
|
|
|
)
|
|
|
|
|
|
2016-12-09 12:10:42 +00:00
|
|
|
send_notification_to_queue(notification=notification, research_mode=research_mode, queue=requested_queue)
|
2016-10-27 17:34:54 +01:00
|
|
|
|
2016-12-09 12:10:42 +00:00
|
|
|
mocked.assert_called_once_with([str(notification.id)], queue=expected_queue)
|
2016-10-27 17:34:54 +01:00
|
|
|
|
|
|
|
|
|
2016-10-28 17:10:00 +01:00
|
|
|
def test_send_notification_to_queue_throws_exception_deletes_notification(sample_notification, mocker):
|
|
|
|
|
mocked = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async', side_effect=Boto3Error("EXPECTED"))
|
2016-11-22 13:10:12 +00:00
|
|
|
with pytest.raises(SendNotificationToQueueError):
|
2016-10-27 17:34:54 +01:00
|
|
|
send_notification_to_queue(sample_notification, False)
|
|
|
|
|
mocked.assert_called_once_with([(str(sample_notification.id))], queue='send-sms')
|
2016-10-28 17:10:00 +01:00
|
|
|
|
|
|
|
|
assert Notification.query.count() == 0
|
|
|
|
|
assert NotificationHistory.query.count() == 0
|
2017-01-17 12:08:24 +00:00
|
|
|
|
|
|
|
|
|
2017-04-26 17:26:06 +01:00
|
|
|
@pytest.mark.parametrize("to_address, notification_type, expected", [
|
|
|
|
|
("+447700900000", "sms", True),
|
|
|
|
|
("+447700900111", "sms", True),
|
|
|
|
|
("+447700900222", "sms", True),
|
|
|
|
|
("07700900000", "sms", True),
|
|
|
|
|
("7700900111", "sms", True),
|
|
|
|
|
("simulate-delivered@notifications.service.gov.uk", "email", True),
|
|
|
|
|
("simulate-delivered-2@notifications.service.gov.uk", "email", True),
|
|
|
|
|
("simulate-delivered-3@notifications.service.gov.uk", "email", True),
|
|
|
|
|
("07515896969", "sms", False),
|
|
|
|
|
("valid_email@test.com", "email", False)
|
|
|
|
|
])
|
2017-01-17 12:08:24 +00:00
|
|
|
def test_simulated_recipient(notify_api, to_address, notification_type, expected):
|
2017-04-26 17:26:06 +01:00
|
|
|
"""
|
|
|
|
|
The values where the expected = 'research-mode' are listed in the config['SIMULATED_EMAIL_ADDRESSES']
|
|
|
|
|
and config['SIMULATED_SMS_NUMBERS']. These values should result in using the research mode queue.
|
|
|
|
|
SIMULATED_EMAIL_ADDRESSES = (
|
|
|
|
|
'simulate-delivered@notifications.service.gov.uk',
|
|
|
|
|
'simulate-delivered-2@notifications.service.gov.uk',
|
|
|
|
|
'simulate-delivered-2@notifications.service.gov.uk'
|
|
|
|
|
)
|
|
|
|
|
SIMULATED_SMS_NUMBERS = ('+447700900000', '+447700900111', '+447700900222')
|
|
|
|
|
"""
|
|
|
|
|
formatted_address = None
|
|
|
|
|
|
|
|
|
|
if notification_type == 'email':
|
|
|
|
|
formatted_address = validate_and_format_email_address(to_address)
|
|
|
|
|
else:
|
|
|
|
|
formatted_address = validate_and_format_phone_number(to_address)
|
|
|
|
|
|
|
|
|
|
is_simulated_address = simulated_recipient(formatted_address, notification_type)
|
|
|
|
|
|
|
|
|
|
assert is_simulated_address == expected
|