Added an optional parameter in the form for POST /v2/notifications/sms and /service/<service_id>/send-notification to pass in the SMS sender id.

The send_sms_to_provider still needs to use the SMS sender being passed in to the POST.

As part of https://www.pivotaltracker.com/story/show/152106587
This commit is contained in:
Rebecca Law
2017-10-30 13:36:49 +00:00
parent f66b7f0a5d
commit 4eec11b633
12 changed files with 251 additions and 62 deletions

View File

@@ -43,8 +43,8 @@ from app.models import (
NOTIFICATION_TECHNICAL_FAILURE,
NOTIFICATION_TEMPORARY_FAILURE,
NOTIFICATION_PERMANENT_FAILURE,
NOTIFICATION_SENT
)
NOTIFICATION_SENT,
NotificationSmsSender)
from app.dao.dao_utils import transactional
from app.statsd_decorators import statsd
@@ -650,3 +650,12 @@ def dao_get_last_notification_added_for_job_id(job_id):
).first()
return last_notification_added
@transactional
def dao_create_notification_sms_sender_mapping(notification_id, sms_sender_id):
notification_to_sms_sender = NotificationSmsSender(
notification_id=notification_id,
service_sms_sender_id=sms_sender_id
)
db.session.add(notification_to_sms_sender)

View File

@@ -17,7 +17,8 @@ from app.models import SMS_TYPE, Notification, KEY_TYPE_TEST, EMAIL_TYPE, NOTIFI
from app.dao.notifications_dao import (dao_create_notification,
dao_delete_notifications_and_history_by_id,
dao_created_scheduled_notification,
dao_create_notification_email_reply_to_mapping)
dao_create_notification_email_reply_to_mapping,
dao_create_notification_sms_sender_mapping)
from app.v2.errors import BadRequestError
from app.utils import get_template_instance, cache_key_for_service_template_counter, convert_bst_to_utc
@@ -146,3 +147,7 @@ def persist_scheduled_notification(notification_id, scheduled_for):
def persist_email_reply_to_id_for_notification(notification_id, email_reply_to_id):
dao_create_notification_email_reply_to_mapping(notification_id, email_reply_to_id)
def persist_sms_sender_id_for_notification(notification_id, sms_sender_id):
dao_create_notification_sms_sender_mapping(notification_id, sms_sender_id)

View File

@@ -8,6 +8,7 @@ from notifications_utils.recipients import (
from notifications_utils.clients.redis import rate_limit_cache_key, daily_limit_cache_key
from app.dao import services_dao, templates_dao
from app.dao.service_sms_sender_dao import dao_get_service_sms_senders_by_id
from app.models import (
INTERNATIONAL_SMS_TYPE, SMS_TYPE, EMAIL_TYPE,
KEY_TYPE_TEST, KEY_TYPE_TEAM, SCHEDULE_NOTIFICATIONS
@@ -135,11 +136,27 @@ def validate_template(template_id, personalisation, service, notification_type):
return template, template_with_content
def check_service_email_reply_to_id(service_id, reply_to_id):
def check_service_email_reply_to_id(service_id, reply_to_id, notification_type):
if not (reply_to_id is None):
if notification_type != EMAIL_TYPE:
message = 'You sent a email_reply_to_id for a {} notification type'.format(notification_type)
raise BadRequestError(message=message)
try:
dao_get_reply_to_by_id(service_id, reply_to_id)
except NoResultFound:
message = 'email_reply_to_id {} does not exist in database for service id {}'\
.format(reply_to_id, service_id)
raise BadRequestError(message=message)
def check_service_sms_sender_id(service_id, sms_sender_id, notification_type):
if not (sms_sender_id is None):
if notification_type != SMS_TYPE:
message = 'You sent a sms_sender_id for a {} notification type'.format(notification_type)
raise BadRequestError(message=message)
try:
dao_get_service_sms_senders_by_id(service_id, sms_sender_id)
except NoResultFound:
message = 'sms_sender_id {} does not exist in database for service id {}'\
.format(sms_sender_id, service_id)
raise BadRequestError(message=message)

View File

@@ -2,13 +2,13 @@ from app.config import QueueNames
from app.notifications.validators import (
check_service_over_daily_message_limit,
validate_and_format_recipient,
validate_template,
check_service_email_reply_to_id)
validate_template)
from app.notifications.process_notifications import (
create_content_for_notification,
persist_notification,
send_notification_to_queue,
persist_email_reply_to_id_for_notification)
persist_email_reply_to_id_for_notification,
persist_sms_sender_id_for_notification
)
from app.models import (
KEY_TYPE_NORMAL,
PRIORITY,
@@ -64,9 +64,11 @@ def send_one_off_notification(service_id, post_data):
created_by_id=post_data['created_by']
)
sender_id = post_data.get('sender_id', None)
if sender_id and template.template_type == EMAIL_TYPE:
check_service_email_reply_to_id(service_id, sender_id)
persist_email_reply_to_id_for_notification(notification.id, sender_id)
if sender_id:
if template.template_type == EMAIL_TYPE:
persist_email_reply_to_id_for_notification(notification.id, sender_id)
if template.template_type == SMS_TYPE:
persist_sms_sender_id_for_notification(notification.id, sender_id)
queue_name = QueueNames.PRIORITY if template.process_type == PRIORITY else None
send_notification_to_queue(

View File

@@ -119,7 +119,8 @@ post_sms_request = {
"phone_number": {"type": "string", "format": "phone_number"},
"template_id": uuid,
"personalisation": personalisation,
"scheduled_for": {"type": ["string", "null"], "format": "datetime"}
"scheduled_for": {"type": ["string", "null"], "format": "datetime"},
"sms_sender_id": uuid
},
"required": ["phone_number", "template_id"]
}

View File

@@ -20,8 +20,8 @@ from app.notifications.process_notifications import (
persist_notification,
persist_scheduled_notification,
send_notification_to_queue,
simulated_recipient
)
simulated_recipient,
persist_sms_sender_id_for_notification)
from app.notifications.process_letter_notifications import (
create_letter_notification
)
@@ -31,7 +31,8 @@ from app.notifications.validators import (
check_service_can_schedule_notification,
check_service_has_permission,
validate_template,
check_service_email_reply_to_id
check_service_email_reply_to_id,
check_service_sms_sender_id
)
from app.schema_validation import validate
from app.v2.errors import BadRequestError
@@ -63,12 +64,14 @@ def post_notification(notification_type):
scheduled_for = form.get("scheduled_for", None)
service_email_reply_to_id = form.get("email_reply_to_id", None)
service_sms_sender_id = form.get("sms_sender_id", None)
check_service_can_schedule_notification(authenticated_service.permissions, scheduled_for)
check_rate_limiting(authenticated_service, api_user)
check_service_email_reply_to_id(str(authenticated_service.id), service_email_reply_to_id)
check_service_email_reply_to_id(str(authenticated_service.id), service_email_reply_to_id, notification_type)
check_service_sms_sender_id(str(authenticated_service.id), service_sms_sender_id, notification_type)
template, template_with_content = validate_template(
form['template_id'],
@@ -142,9 +145,7 @@ def process_sms_or_email_notification(*, form, notification_type, api_key, templ
simulated=simulated
)
email_reply_to_id = form.get("email_reply_to_id", None)
if email_reply_to_id:
persist_email_reply_to_id_for_notification(notification.id, email_reply_to_id)
persist_sender_to_notification_mapping(form, notification)
scheduled_for = form.get("scheduled_for", None)
if scheduled_for:
@@ -163,6 +164,15 @@ def process_sms_or_email_notification(*, form, notification_type, api_key, templ
return notification
def persist_sender_to_notification_mapping(form, notification):
email_reply_to_id = form.get("email_reply_to_id", None)
if email_reply_to_id:
persist_email_reply_to_id_for_notification(notification.id, email_reply_to_id)
sms_sender_id = form.get("sms_sender_id", None)
if sms_sender_id:
persist_sms_sender_id_for_notification(notification.id, sms_sender_id)
def process_letter_notification(*, letter_data, api_key, template):
if api_key.key_type == KEY_TYPE_TEAM:
raise BadRequestError(message='Cannot send letters with a team api key', status_code=403)

View File

@@ -14,7 +14,6 @@ from app.models import (
NotificationHistory,
NotificationStatistics,
ScheduledNotification,
ServiceEmailReplyTo,
EMAIL_TYPE,
NOTIFICATION_STATUS_TYPES,
NOTIFICATION_STATUS_TYPES_FAILED,
@@ -23,7 +22,7 @@ from app.models import (
KEY_TYPE_NORMAL,
KEY_TYPE_TEAM,
KEY_TYPE_TEST,
JOB_STATUS_IN_PROGRESS)
JOB_STATUS_IN_PROGRESS, NotificationSmsSender)
from app.dao.notifications_dao import (
dao_create_notification,
@@ -51,15 +50,18 @@ from app.dao.notifications_dao import (
set_scheduled_notification_to_processed,
update_notification_status_by_id,
update_notification_status_by_reference,
dao_get_last_notification_added_for_job_id, dao_update_notifications_by_reference)
dao_get_last_notification_added_for_job_id,
dao_update_notifications_by_reference,
dao_create_notification_sms_sender_mapping
)
from app.dao.services_dao import dao_update_service
from tests.app.db import (
create_api_key,
create_job,
create_notification,
create_reply_to_email
)
create_reply_to_email,
create_service_sms_sender)
from tests.app.conftest import (
sample_notification,
sample_template,
@@ -2100,3 +2102,13 @@ def test_dao_update_notifications_by_reference_returns_zero_when_no_notification
"billable_units": 2}
)
assert updated_count == 0
def test_dao_create_notification_sms_sender_mapping(sample_notification):
sms_sender = create_service_sms_sender(service=sample_notification.service, sms_sender='123456')
dao_create_notification_sms_sender_mapping(notification_id=sample_notification.id,
sms_sender_id=sms_sender.id)
notification_to_senders = NotificationSmsSender.query.all()
assert len(notification_to_senders) == 1
assert notification_to_senders[0].notification_id == sample_notification.id
assert notification_to_senders[0].service_sms_sender_id == sms_sender.id

View File

@@ -8,19 +8,20 @@ from freezegun import freeze_time
from collections import namedtuple
from app.dao.service_email_reply_to_dao import dao_get_reply_to_by_service_id
from app.models import Template, Notification, NotificationHistory, ScheduledNotification, NotificationEmailReplyTo
from app.models import Template, Notification, NotificationHistory, ScheduledNotification, NotificationEmailReplyTo, \
NotificationSmsSender
from app.notifications.process_notifications import (
create_content_for_notification,
persist_notification,
send_notification_to_queue,
simulated_recipient,
persist_scheduled_notification,
persist_email_reply_to_id_for_notification)
persist_email_reply_to_id_for_notification, persist_sms_sender_id_for_notification)
from notifications_utils.recipients import validate_and_format_phone_number, validate_and_format_email_address
from app.utils import cache_key_for_service_template_counter
from app.v2.errors import BadRequestError
from tests.app.conftest import sample_api_key as create_api_key
from tests.app.db import create_reply_to_email
from tests.app.db import create_reply_to_email, create_service_sms_sender
def test_create_content_for_notification_passes(sample_email_template):
@@ -452,3 +453,15 @@ def test_persist_email_reply_to_id_for_notification(sample_service, sample_notif
assert len(email_reply_to) == 1
assert email_reply_to[0].notification_id == sample_notification.id
assert email_reply_to[0].service_email_reply_to_id == reply_to_address[0].id
def test_persist_sms_sender_id_for_notification(sample_service, sample_notification):
sms_sender = create_service_sms_sender(service=sample_service, sms_sender="123456")
persist_sms_sender_id_for_notification(notification_id=sample_notification.id,
sms_sender_id=sms_sender.id)
notification_to_sms_sender = NotificationSmsSender.query.all()
assert len(notification_to_sms_sender) == 1
assert notification_to_sms_sender[0].notification_id == sample_notification.id
assert notification_to_sms_sender[0].service_sms_sender_id == sms_sender.id

View File

@@ -11,8 +11,8 @@ from app.notifications.validators import (
check_sms_content_char_count,
check_service_over_api_rate_limit,
validate_and_format_recipient,
check_service_email_reply_to_id
)
check_service_email_reply_to_id,
check_service_sms_sender_id)
from app.v2.errors import (
BadRequestError,
@@ -23,7 +23,7 @@ from tests.app.conftest import (
sample_service as create_service,
sample_service_whitelist,
sample_api_key)
from tests.app.db import create_reply_to_email
from tests.app.db import create_reply_to_email, create_service_sms_sender
@pytest.mark.parametrize('key_type', ['test', 'team', 'normal'])
@@ -331,14 +331,15 @@ def test_allows_api_calls_with_international_numbers_if_service_does_allow_int_s
assert result == '201212341234'
def test_check_service_email_reply_to_id_where_reply_to_id_is_none():
assert check_service_email_reply_to_id(None, None) is None
@pytest.mark.parametrize('notification_type', ['sms', 'email', 'letter'])
def test_check_service_email_reply_to_id_where_reply_to_id_is_none(notification_type):
assert check_service_email_reply_to_id(None, None, notification_type) is None
def test_check_service_email_reply_to_id_where_service_id_is_not_found(sample_service, fake_uuid):
reply_to_address = create_reply_to_email(sample_service, "test@test.com")
with pytest.raises(BadRequestError) as e:
check_service_email_reply_to_id(fake_uuid, reply_to_address.id)
check_service_email_reply_to_id(fake_uuid, reply_to_address.id, EMAIL_TYPE)
assert e.value.status_code == 400
assert e.value.message == 'email_reply_to_id {} does not exist in database for service id {}'\
.format(reply_to_address.id, fake_uuid)
@@ -346,12 +347,57 @@ def test_check_service_email_reply_to_id_where_service_id_is_not_found(sample_se
def test_check_service_email_reply_to_id_where_reply_to_id_is_not_found(sample_service, fake_uuid):
with pytest.raises(BadRequestError) as e:
check_service_email_reply_to_id(sample_service.id, fake_uuid)
check_service_email_reply_to_id(sample_service.id, fake_uuid, EMAIL_TYPE)
assert e.value.status_code == 400
assert e.value.message == 'email_reply_to_id {} does not exist in database for service id {}'\
.format(fake_uuid, sample_service.id)
def test_check_service_email_reply_to_id_where_reply_to_id_is_found(sample_service):
reply_to_email = create_reply_to_email(sample_service, 'test@test.com')
assert check_service_email_reply_to_id(sample_service.id, reply_to_email.id) is None
def test_check_service_sms_sender_id_where_sms_sender_id_is_found(sample_service):
sms_sender = create_service_sms_sender(service=sample_service, sms_sender='123456')
assert check_service_sms_sender_id(sample_service.id, sms_sender.id, SMS_TYPE) is None
@pytest.mark.parametrize('notification_type', ['email', 'letter'])
def test_check_service_sms_sender_id_when_channel_type_is_wrong(sample_service, notification_type):
sms_sender = create_service_sms_sender(service=sample_service, sms_sender='123456')
with pytest.raises(BadRequestError) as e:
check_service_sms_sender_id(sample_service.id, sms_sender.id, notification_type)
assert e.value.status_code == 400
assert e.value.message == 'You sent a sms_sender_id for a {} notification type'.format(notification_type)
@pytest.mark.parametrize('notification_type', ['sms', 'email', 'letter'])
def test_check_service_sms_sender_id_where_reply_to_id_is_valid(notification_type):
assert check_service_sms_sender_id(None, None, notification_type) is None
def test_check_service_sms_sender_id_where_reply_to_id_is_found(sample_service):
sms_sender = create_service_sms_sender(service=sample_service, sms_sender='123456')
assert check_service_sms_sender_id(sample_service.id, sms_sender.id, SMS_TYPE) is None
def test_check_service_sms_sender_id_where_service_id_is_not_found(sample_service, fake_uuid):
sms_sender = create_service_sms_sender(service=sample_service, sms_sender='123456')
with pytest.raises(BadRequestError) as e:
check_service_sms_sender_id(fake_uuid, sms_sender.id, SMS_TYPE)
assert e.value.status_code == 400
assert e.value.message == 'sms_sender_id {} does not exist in database for service id {}'\
.format(sms_sender.id, fake_uuid)
def test_check_service_sms_sender_id_where_reply_to_id_is_not_found(sample_service, fake_uuid):
with pytest.raises(BadRequestError) as e:
check_service_sms_sender_id(sample_service.id, fake_uuid, SMS_TYPE)
assert e.value.status_code == 400
assert e.value.message == 'sms_sender_id {} does not exist in database for service id {}'\
.format(fake_uuid, sample_service.id)
@pytest.mark.parametrize('notification_type', ['email', 'letter'])
def test_check_service_email_reply_to_id_when_channel_type_is_wrong(sample_service, notification_type):
sms_sender = create_service_sms_sender(service=sample_service, sms_sender='123456')
with pytest.raises(BadRequestError) as e:
check_service_sms_sender_id(sample_service.id, sms_sender.id, notification_type)
assert e.value.status_code == 400
assert e.value.message == 'You sent a sms_sender_id for a {} notification type'.format(notification_type)

View File

@@ -3,6 +3,7 @@ from unittest.mock import Mock
import pytest
from notifications_utils.recipients import InvalidPhoneError
from sqlalchemy.exc import SQLAlchemyError
from app.v2.errors import BadRequestError, TooManyRequestsError
from app.config import QueueNames
@@ -12,9 +13,11 @@ from app.models import (
PRIORITY,
SMS_TYPE,
NotificationEmailReplyTo,
Notification)
Notification,
NotificationSmsSender
)
from tests.app.db import create_user, create_reply_to_email
from tests.app.db import create_user, create_reply_to_email, create_service_sms_sender
@pytest.fixture
@@ -206,7 +209,7 @@ def test_send_one_off_notification_should_add_email_reply_to_id_for_email(sample
def test_send_one_off_notification_should_throw_exception_if_reply_to_id_doesnot_exist(
sample_email_template, celery_mock
sample_email_template
):
data = {
'to': 'ok@ok.com',
@@ -215,5 +218,39 @@ def test_send_one_off_notification_should_throw_exception_if_reply_to_id_doesnot
'created_by': str(sample_email_template.service.created_by_id)
}
with pytest.raises(expected_exception=BadRequestError):
with pytest.raises(expected_exception=SQLAlchemyError):
send_one_off_notification(service_id=sample_email_template.service.id, post_data=data)
def test_send_one_off_notification_should_add_sms_sender_mapping_for_sms(sample_template, celery_mock):
sms_sender = create_service_sms_sender(service=sample_template.service, sms_sender='123456')
data = {
'to': '07700 900 001',
'template_id': str(sample_template.id),
'sender_id': sms_sender.id,
'created_by': str(sample_template.service.created_by_id)
}
notification_id = send_one_off_notification(service_id=sample_template.service.id, post_data=data)
notification = Notification.query.get(notification_id['id'])
celery_mock.assert_called_once_with(
notification=notification,
research_mode=False,
queue=None
)
mapping_row = NotificationSmsSender.query.filter_by(notification_id=notification_id['id']).first()
assert mapping_row.service_sms_sender_id == sms_sender.id
def test_send_one_off_notification_should_throw_exception_if_sms_sender_id_doesnot_exist(
sample_template
):
data = {
'to': '07700 900 001',
'template_id': str(sample_template.id),
'sender_id': str(uuid.uuid4()),
'created_by': str(sample_template.service.created_by_id)
}
with pytest.raises(expected_exception=SQLAlchemyError):
send_one_off_notification(service_id=sample_template.service.id, post_data=data)

View File

@@ -28,7 +28,6 @@ from tests.app.db import (
create_service,
create_inbound_number,
create_reply_to_email,
create_service_sms_sender,
create_letter_contact
)
from tests.conftest import set_config

View File

@@ -8,8 +8,8 @@ from app.models import (
ScheduledNotification,
SCHEDULE_NOTIFICATIONS,
EMAIL_TYPE,
SMS_TYPE
)
SMS_TYPE,
NotificationSmsSender)
from flask import json, current_app
from app.models import Notification
@@ -27,8 +27,8 @@ from tests.app.conftest import (
from tests.app.db import (
create_service,
create_template,
create_reply_to_email
)
create_reply_to_email,
create_service_sms_sender)
@pytest.mark.parametrize("reference", [None, "reference_from_client"])
@@ -93,6 +93,34 @@ def test_post_sms_notification_uses_inbound_number_as_sender(client, notify_db_s
mocked.assert_called_once_with([str(notification_id)], queue='send-sms-tasks')
def test_post_sms_notification_returns_201_with_sms_sender_id(
client, sample_template_with_placeholders, mocker
):
sms_sender = create_service_sms_sender(service=sample_template_with_placeholders.service, sms_sender='123456')
mocked = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
data = {
'phone_number': '+447700900855',
'template_id': str(sample_template_with_placeholders.id),
'personalisation': {' Name': 'Jo'},
'sms_sender_id': str(sms_sender.id)
}
auth_header = create_authorization_header(service_id=sample_template_with_placeholders.service_id)
response = client.post(
path='/v2/notifications/sms',
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
assert response.status_code == 201
resp_json = json.loads(response.get_data(as_text=True))
assert validate(resp_json, post_sms_response) == resp_json
notification_to_sms_sender = NotificationSmsSender.query.all()
assert len(notification_to_sms_sender) == 1
assert str(notification_to_sms_sender[0].notification_id) == resp_json['id']
assert notification_to_sms_sender[0].service_sms_sender_id == sms_sender.id
mocked.assert_called_once_with([resp_json['id']], queue='send-sms-tasks')
@pytest.mark.parametrize("notification_type, key_send_to, send_to",
[("sms", "phone_number", "+447700900855"),
("email", "email_address", "sample@email.com")])
@@ -501,30 +529,40 @@ def test_post_notification_raises_bad_request_if_not_valid_notification_type(cli
assert 'The requested URL was not found on the server.' in error_json['message']
@pytest.mark.parametrize("reference", [None, "reference_from_client"])
def test_post_sms_notification_with_invalid_reply_to_email_id(
@pytest.mark.parametrize("notification_type",
['sms', 'email'])
def test_post_notification_with_wrong_type_of_sender(
client,
sample_template_with_placeholders,
reference,
sample_template,
sample_email_template,
notification_type,
fake_uuid):
data = {
'phone_number': '+447700900855',
'template_id': str(sample_template_with_placeholders.id),
'personalisation': {' Name': 'Jo'},
'email_reply_to_id': fake_uuid
}
if reference:
data.update({"reference": reference})
auth_header = create_authorization_header(service_id=sample_template_with_placeholders.service_id)
if notification_type == EMAIL_TYPE:
template = sample_email_template
form_label = 'sms_sender_id'
data = {
'email_address': 'test@test.com',
'template_id': str(sample_email_template.id),
form_label: fake_uuid
}
elif notification_type == SMS_TYPE:
template = sample_template
form_label = 'email_reply_to_id'
data = {
'phone_number': '+447700900855',
'template_id': str(template.id),
form_label: fake_uuid
}
auth_header = create_authorization_header(service_id=template.service_id)
response = client.post(
path='/v2/notifications/sms',
path='/v2/notifications/{}'.format(notification_type),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
assert response.status_code == 400
resp_json = json.loads(response.get_data(as_text=True))
assert 'email_reply_to_id {} does not exist in database for service id {}'.\
format(fake_uuid, sample_template_with_placeholders.service_id) in resp_json['errors'][0]['message']
assert 'You sent a {} for a {} notification type'.\
format(form_label, notification_type) in resp_json['errors'][0]['message']
assert 'BadRequestError' in resp_json['errors'][0]['error']