Adds notification stats update into the callback process

- when a provider callback occurs and we update the status of the notification, also update the statistics table

Adds:
- Mapping object to the clients to handle mapping to various states from the response codes, this replaces the map.
- query lookup in the DAO to get the query based on response type / template type

Tests around rest class and dao to check correct updating of stats

Missing:
- multiple client callbacks will keep incrementing the counts of success/failure. This edge case needs to be handle in a future story.
This commit is contained in:
Martyn Inglis
2016-03-21 13:24:37 +00:00
parent e2cfbce8c4
commit e0316d1881
10 changed files with 289 additions and 54 deletions

View File

@@ -13,6 +13,11 @@ class Client(object):
pass pass
STATISTICS_REQUESTED = 'requested'
STATISTICS_DELIVERED = 'delivered'
STATISTICS_FAILURE = 'failure'
class ClientResponse: class ClientResponse:
def __init__(self): def __init__(self):
self.__response_model__ = None self.__response_model__ = None
@@ -24,7 +29,6 @@ class ClientResponse:
return self.response_code_to_object(response_code)['message'] return self.response_code_to_object(response_code)['message']
def response_code_to_notification_status(self, response_code): def response_code_to_notification_status(self, response_code):
print(response_code)
return self.response_code_to_object(response_code)['notification_status'] return self.response_code_to_object(response_code)['notification_status']
def response_code_to_notification_statistics_status(self, response_code): def response_code_to_notification_statistics_status(self, response_code):

View File

@@ -1,7 +1,7 @@
import boto3 import boto3
from flask import current_app from flask import current_app
from monotonic import monotonic from monotonic import monotonic
from app.clients import ClientResponse from app.clients import ClientResponse, STATISTICS_DELIVERED, STATISTICS_FAILURE
from app.clients.email import (EmailClientException, EmailClient) from app.clients.email import (EmailClientException, EmailClient)
@@ -13,19 +13,19 @@ class AwsSesResponses(ClientResponse):
"message": 'Bounced', "message": 'Bounced',
"success": False, "success": False,
"notification_status": 'bounce', "notification_status": 'bounce',
"notification_statistics_status": 'failed' "notification_statistics_status": STATISTICS_FAILURE
}, },
'Delivery': { 'Delivery': {
"message": 'Delivered', "message": 'Delivered',
"success": True, "success": True,
"notification_status": 'delivered', "notification_status": 'delivered',
"notification_statistics_status": 'delivered' "notification_statistics_status": STATISTICS_DELIVERED
}, },
'Complaint': { 'Complaint': {
"message": 'Complaint', "message": 'Complaint',
"success": False, "success": False,
"notification_status": 'complaint', "notification_status": 'complaint',
"notification_statistics_status": 'failed' "notification_statistics_status": STATISTICS_FAILURE
} }
} }

View File

@@ -6,7 +6,7 @@ from app.clients.sms import (
) )
from flask import current_app from flask import current_app
from requests import request, RequestException, HTTPError from requests import request, RequestException, HTTPError
from app.clients import ClientResponse from app.clients import ClientResponse, STATISTICS_DELIVERED, STATISTICS_FAILURE
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -17,14 +17,14 @@ class FiretextResponses(ClientResponse):
self.__response_model__ = { self.__response_model__ = {
'0': { '0': {
"message": 'Delivered', "message": 'Delivered',
"notification_statistics_status": 'delivered', "notification_statistics_status": STATISTICS_DELIVERED,
"success": True, "success": True,
"notification_status": 'delivered' "notification_status": 'delivered'
}, },
'1': { '1': {
"message": 'Declined', "message": 'Declined',
"success": False, "success": False,
"notification_statistics_status": 'failed', "notification_statistics_status": STATISTICS_FAILURE,
"notification_status": 'failed' "notification_status": 'failed'
}, },
'2': { '2': {

View File

@@ -3,7 +3,8 @@ from app import db
from app.models import Notification, Job, NotificationStatistics, TEMPLATE_TYPE_SMS, TEMPLATE_TYPE_EMAIL from app.models import Notification, Job, NotificationStatistics, TEMPLATE_TYPE_SMS, TEMPLATE_TYPE_EMAIL
from sqlalchemy import desc from sqlalchemy import desc
from datetime import datetime, timedelta from datetime import datetime, timedelta
from app.clients.sms.firetext import FiretextResponses from app.clients import (STATISTICS_FAILURE, STATISTICS_DELIVERED, STATISTICS_REQUESTED)
def dao_get_notification_statistics_for_service(service_id): def dao_get_notification_statistics_for_service(service_id):
return NotificationStatistics.query.filter_by( return NotificationStatistics.query.filter_by(
@@ -49,18 +50,16 @@ def dao_create_notification(notification, notification_type):
def update_query(notification_type, status): def update_query(notification_type, status):
print(notification_type)
print(status)
mapping = { mapping = {
'sms': { TEMPLATE_TYPE_SMS: {
'requested': NotificationStatistics.sms_requested, STATISTICS_REQUESTED: NotificationStatistics.sms_requested,
'success': NotificationStatistics.sms_delivered, STATISTICS_DELIVERED: NotificationStatistics.sms_delivered,
'failure': NotificationStatistics.sms_error STATISTICS_FAILURE: NotificationStatistics.sms_error
}, },
'email': { TEMPLATE_TYPE_EMAIL: {
'requested': NotificationStatistics.emails_requested, STATISTICS_REQUESTED: NotificationStatistics.emails_requested,
'success': NotificationStatistics.emails_delivered, STATISTICS_DELIVERED: NotificationStatistics.emails_delivered,
'failure': NotificationStatistics.emails_error STATISTICS_FAILURE: NotificationStatistics.emails_error
} }
} }
return { return {
@@ -74,29 +73,46 @@ def dao_update_notification(notification):
db.session.commit() db.session.commit()
def update_notification_status_by_id(notification_id, status): def update_notification_status_by_id(notification_id, status, notification_statistics_status):
count = db.session.query(Notification).filter_by( count = db.session.query(Notification).filter_by(
id=notification_id id=notification_id
).update({ ).update({
Notification.status: status Notification.status: status
}) })
if count == 1:
if count == 1 and notification_statistics_status:
notification = Notification.query.get(notification_id) notification = Notification.query.get(notification_id)
db.session.query(NotificationStatistics).filter_by( db.session.query(NotificationStatistics).filter_by(
day=notification.created_at.strftime('%Y-%m-%d'), day=notification.created_at.strftime('%Y-%m-%d'),
service_id=notification.service_id service_id=notification.service_id
).update(update_query(notification.template.template_type, FiretextResponses.response_code_to_notify_stats(status))) ).update(
update_query(notification.template.template_type, notification_statistics_status)
)
db.session.commit() db.session.commit()
return count return count
def update_notification_status_by_reference(reference, status): def update_notification_status_by_reference(reference, status, notification_statistics_status):
count = db.session.query(Notification).filter_by( count = db.session.query(Notification).filter_by(
reference=reference reference=reference
).update({ ).update({
Notification.status: status Notification.status: status
}) })
if count == 1:
notification = Notification.query.filter_by(
reference=reference
).first()
db.session.query(NotificationStatistics).filter_by(
day=notification.created_at.strftime('%Y-%m-%d'),
service_id=notification.service_id
).update(
update_query(notification.template.template_type, notification_statistics_status)
)
db.session.commit() db.session.commit()
return count return count

View File

@@ -11,8 +11,8 @@ from flask import (
) )
from utils.template import Template from utils.template import Template
from app.clients.sms.firetext import firetext_response_status from app.clients.sms.firetext import FiretextResponses
from app.clients.email.aws_ses import ses_response_status from app.clients.email.aws_ses import AwsSesResponses
from app import api_user, encryption, create_uuid, DATETIME_FORMAT, DATE_FORMAT from app import api_user, encryption, create_uuid, DATETIME_FORMAT, DATE_FORMAT
from app.authentication.auth import require_admin from app.authentication.auth import require_admin
from app.dao import ( from app.dao import (
@@ -34,6 +34,9 @@ from app.errors import register_errors
register_errors(notifications) register_errors(notifications)
aws_response = AwsSesResponses()
firetext_response = FiretextResponses()
@notifications.route('/notifications/email/ses', methods=['POST']) @notifications.route('/notifications/email/ses', methods=['POST'])
def process_ses_response(): def process_ses_response():
@@ -57,35 +60,54 @@ def process_ses_response():
result="error", message="SES callback failed: notificationType missing" result="error", message="SES callback failed: notificationType missing"
), 400 ), 400
status = ses_response_status.get(ses_message['notificationType'], None) try:
if not status: aws_response.response_code_to_object(ses_message['notificationType'])
except KeyError:
current_app.logger.info( current_app.logger.info(
"SES callback failed: status {} not found.".format(status) "SES callback failed: status {} not found.".format(ses_message['notificationType'])
) )
return jsonify( return jsonify(
result="error", result="error",
message="SES callback failed: status {} not found".format(ses_message['notificationType']) message="SES callback failed: status {} not found".format(ses_message['notificationType'])
), 400 ), 400
notification_status = aws_response.response_code_to_notification_status(ses_message['notificationType'])
notification_statistics_status = aws_response.response_code_to_notification_statistics_status(
ses_message['notificationType']
)
try: try:
source = ses_message['mail']['source'] source = ses_message['mail']['source']
if is_not_a_notification(source): if is_not_a_notification(source):
current_app.logger.info( current_app.logger.info(
"SES callback for notify success:. source {} status {}".format(source, status['notify_status']) "SES callback for notify success:. source {} status {}".format(source, notification_status)
) )
return jsonify( return jsonify(
result="success", message="SES callback succeeded" result="success", message="SES callback succeeded"
), 200 ), 200
reference = ses_message['mail']['messageId'] reference = ses_message['mail']['messageId']
if notifications_dao.update_notification_status_by_reference(reference, status['notify_status']) == 0: if notifications_dao.update_notification_status_by_reference(
reference,
notification_status,
notification_statistics_status
) == 0:
current_app.logger.info( current_app.logger.info(
"SES callback failed: notification not found. Status {}".format(status['notify_status']) "SES callback failed: notification not found. Status {}".format(notification_status)
) )
return jsonify( return jsonify(
result="error", result="error",
message="SES callback failed: notification not found. Status {}".format(status['notify_status']) message="SES callback failed: notification not found. Status {}".format(notification_status)
), 404 ), 404
if not aws_response.response_code_to_notification_success(ses_message['notificationType']):
current_app.logger.info(
"SES delivery failed: notification {} has error found. Status {}".format(
reference,
aws_response.response_code_to_message(ses_message['notificationType'])
)
)
return jsonify( return jsonify(
result="success", message="SES callback succeeded" result="success", message="SES callback succeeded"
), 200 ), 200
@@ -149,14 +171,22 @@ def process_firetext_response():
result="error", message="Firetext callback with invalid reference {}".format(reference) result="error", message="Firetext callback with invalid reference {}".format(reference)
), 400 ), 400
notification_status = firetext_response_status.get(status, None) try:
if not notification_status: firetext_response.response_code_to_object(status)
except KeyError:
current_app.logger.info( current_app.logger.info(
"Firetext callback failed: status {} not found.".format(status) "Firetext callback failed: status {} not found.".format(status)
) )
return jsonify(result="error", message="Firetext callback failed: status {} not found.".format(status)), 400 return jsonify(result="error", message="Firetext callback failed: status {} not found.".format(status)), 400
if notifications_dao.update_notification_status_by_id(reference, notification_status['notify_status']) == 0: notification_status = firetext_response.response_code_to_notification_status(status)
notification_statistics_status = firetext_response.response_code_to_notification_statistics_status(status)
if notifications_dao.update_notification_status_by_id(
reference,
notification_status,
notification_statistics_status
) == 0:
current_app.logger.info( current_app.logger.info(
"Firetext callback failed: notification {} not found. Status {}".format(reference, status) "Firetext callback failed: notification {} not found. Status {}".format(reference, status)
) )
@@ -164,15 +194,15 @@ def process_firetext_response():
result="error", result="error",
message="Firetext callback failed: notification {} not found. Status {}".format( message="Firetext callback failed: notification {} not found. Status {}".format(
reference, reference,
notification_status['firetext_message'] firetext_response.response_code_to_message(status)
) )
), 404 ), 404
if not notification_status['success']: if not firetext_response.response_code_to_notification_success(status):
current_app.logger.info( current_app.logger.info(
"Firetext delivery failed: notification {} has error found. Status {}".format( "Firetext delivery failed: notification {} has error found. Status {}".format(
reference, reference,
firetext_response_status[status]['firetext_message'] FiretextResponses().response_code_to_message(status)
) )
) )
return jsonify( return jsonify(

View File

@@ -15,14 +15,14 @@ def test_should_return_correct_details_for_delivery():
def test_should_return_correct_details_for_bounced(): def test_should_return_correct_details_for_bounced():
assert aws_responses.response_code_to_message('Bounce') == 'Bounced' assert aws_responses.response_code_to_message('Bounce') == 'Bounced'
assert aws_responses.response_code_to_notification_status('Bounce') == 'bounce' assert aws_responses.response_code_to_notification_status('Bounce') == 'bounce'
assert aws_responses.response_code_to_notification_statistics_status('Bounce') == 'failed' assert aws_responses.response_code_to_notification_statistics_status('Bounce') == 'failure'
assert not aws_responses.response_code_to_notification_success('Bounce') assert not aws_responses.response_code_to_notification_success('Bounce')
def test_should_return_correct_details_for_complaint(): def test_should_return_correct_details_for_complaint():
assert aws_responses.response_code_to_message('Complaint') == 'Complaint' assert aws_responses.response_code_to_message('Complaint') == 'Complaint'
assert aws_responses.response_code_to_notification_status('Complaint') == 'complaint' assert aws_responses.response_code_to_notification_status('Complaint') == 'complaint'
assert aws_responses.response_code_to_notification_statistics_status('Complaint') == 'failed' assert aws_responses.response_code_to_notification_statistics_status('Complaint') == 'failure'
assert not aws_responses.response_code_to_notification_success('Complaint') assert not aws_responses.response_code_to_notification_success('Complaint')

View File

@@ -15,7 +15,7 @@ def test_should_return_correct_details_for_delivery():
def test_should_return_correct_details_for_bounced(): def test_should_return_correct_details_for_bounced():
assert responses.response_code_to_message('1') == 'Declined' assert responses.response_code_to_message('1') == 'Declined'
assert responses.response_code_to_notification_status('1') == 'failed' assert responses.response_code_to_notification_status('1') == 'failed'
assert responses.response_code_to_notification_statistics_status('1') == 'failed' assert responses.response_code_to_notification_statistics_status('1') == 'failure'
assert not responses.response_code_to_notification_success('1') assert not responses.response_code_to_notification_success('1')

View File

@@ -221,7 +221,6 @@ def sample_job(notify_db,
} }
job = Job(**data) job = Job(**data)
dao_create_job(job) dao_create_job(job)
print(job.created_at)
return job return job

View File

@@ -32,26 +32,98 @@ def test_should_by_able_to_update_reference_by_id(sample_notification):
assert Notification.query.get(sample_notification.id).reference == 'reference' assert Notification.query.get(sample_notification.id).reference == 'reference'
def test_should_by_able_to_update_status_by_reference(sample_notification): def test_should_by_able_to_update_status_by_reference(sample_email_template):
assert Notification.query.get(sample_notification.id).status == "sent" data = {
update_notification_reference_by_id(sample_notification.id, 'reference') 'to': '+44709123456',
update_notification_status_by_reference('reference', 'delivered') 'service': sample_email_template.service,
assert Notification.query.get(sample_notification.id).status == 'delivered' 'service_id': sample_email_template.service.id,
'template': sample_email_template,
'created_at': datetime.utcnow()
}
notification = Notification(**data)
dao_create_notification(notification, sample_email_template.template_type)
assert Notification.query.get(notification.id).status == "sent"
update_notification_reference_by_id(notification.id, 'reference')
update_notification_status_by_reference('reference', 'delivered', 'delivered')
assert Notification.query.get(notification.id).status == 'delivered'
assert NotificationStatistics.query.filter_by(
service_id=sample_email_template.service.id
).one().emails_delivered == 1
assert NotificationStatistics.query.filter_by(
service_id=sample_email_template.service.id
).one().emails_requested == 1
assert NotificationStatistics.query.filter_by(
service_id=sample_email_template.service.id
).one().emails_error == 0
def test_should_by_able_to_update_status_by_id(sample_notification): def test_should_by_able_to_update_status_by_id(sample_notification):
assert Notification.query.get(sample_notification.id).status == 'sent' assert Notification.query.get(sample_notification.id).status == 'sent'
count = update_notification_status_by_id(sample_notification.id, 'delivered') count = update_notification_status_by_id(sample_notification.id, 'delivered', 'delivered')
assert count == 1 assert count == 1
assert Notification.query.get(sample_notification.id).status == 'delivered' assert Notification.query.get(sample_notification.id).status == 'delivered'
assert NotificationStatistics.query.filter_by(
service_id=sample_notification.service.id
).one().sms_delivered == 1
assert NotificationStatistics.query.filter_by(
service_id=sample_notification.service.id
).one().sms_requested == 1
assert NotificationStatistics.query.filter_by(
service_id=sample_notification.service.id
).one().sms_error == 0
def test_should_be_able_to_record_statistics_failure_for_sms(sample_notification):
assert Notification.query.get(sample_notification.id).status == 'sent'
count = update_notification_status_by_id(sample_notification.id, 'delivered', 'failure')
assert count == 1
assert Notification.query.get(sample_notification.id).status == 'delivered'
assert NotificationStatistics.query.filter_by(
service_id=sample_notification.service.id
).one().sms_delivered == 0
assert NotificationStatistics.query.filter_by(
service_id=sample_notification.service.id
).one().sms_requested == 1
assert NotificationStatistics.query.filter_by(
service_id=sample_notification.service.id
).one().sms_error == 1
def test_should_be_able_to_record_statistics_failure_for_email(sample_email_template):
data = {
'to': '+44709123456',
'service': sample_email_template.service,
'service_id': sample_email_template.service.id,
'template': sample_email_template,
'created_at': datetime.utcnow()
}
notification = Notification(**data)
dao_create_notification(notification, sample_email_template.template_type)
update_notification_reference_by_id(notification.id, 'reference')
count = update_notification_status_by_reference('reference', 'bounce', 'failure')
assert count == 1
assert Notification.query.get(notification.id).status == 'bounce'
assert NotificationStatistics.query.filter_by(
service_id=notification.service.id
).one().emails_delivered == 0
assert NotificationStatistics.query.filter_by(
service_id=notification.service.id
).one().emails_requested == 1
assert NotificationStatistics.query.filter_by(
service_id=notification.service.id
).one().emails_error == 1
def test_should_return_zero_count_if_no_notification_with_id(): def test_should_return_zero_count_if_no_notification_with_id():
assert update_notification_status_by_id(str(uuid.uuid4()), 'delivered') == 0 assert update_notification_status_by_id(str(uuid.uuid4()), 'delivered', 'delivered') == 0
def test_should_return_zero_count_if_no_notification_with_reference(): def test_should_return_zero_count_if_no_notification_with_reference():
assert update_notification_status_by_reference('something', 'delivered') == 0 assert update_notification_status_by_reference('something', 'delivered', 'delivered') == 0
def test_should_be_able_to_get_statistics_for_a_service(sample_template): def test_should_be_able_to_get_statistics_for_a_service(sample_template):

View File

@@ -7,7 +7,7 @@ from flask import json
from app.models import Service from app.models import Service
from app.dao.templates_dao import dao_get_all_templates_for_service from app.dao.templates_dao import dao_get_all_templates_for_service
from app.dao.services_dao import dao_update_service from app.dao.services_dao import dao_update_service
from app.dao.notifications_dao import get_notification_by_id from app.dao.notifications_dao import get_notification_by_id, dao_get_notification_statistics_for_service
from freezegun import freeze_time from freezegun import freeze_time
@@ -982,7 +982,7 @@ def test_firetext_callback_should_return_404_if_cannot_find_notification_id(noti
assert json_resp['result'] == 'error' assert json_resp['result'] == 'error'
assert json_resp['message'] == 'Firetext callback failed: notification {} not found. Status {}'.format( assert json_resp['message'] == 'Firetext callback failed: notification {} not found. Status {}'.format(
missing_notification_id, missing_notification_id,
'delivered' 'Delivered'
) )
@@ -1007,6 +1007,10 @@ def test_firetext_callback_should_update_notification_status(notify_api, sample_
) )
updated = get_notification_by_id(sample_notification.id) updated = get_notification_by_id(sample_notification.id)
assert updated.status == 'delivered' assert updated.status == 'delivered'
assert get_notification_by_id(sample_notification.id).status == 'delivered'
assert dao_get_notification_statistics_for_service(sample_notification.service_id)[0].sms_delivered == 1
assert dao_get_notification_statistics_for_service(sample_notification.service_id)[0].sms_requested == 1
assert dao_get_notification_statistics_for_service(sample_notification.service_id)[0].sms_error == 0
def test_firetext_callback_should_update_notification_status_failed(notify_api, sample_notification): def test_firetext_callback_should_update_notification_status_failed(notify_api, sample_notification):
@@ -1030,6 +1034,9 @@ def test_firetext_callback_should_update_notification_status_failed(notify_api,
) )
updated = get_notification_by_id(sample_notification.id) updated = get_notification_by_id(sample_notification.id)
assert updated.status == 'failed' assert updated.status == 'failed'
assert dao_get_notification_statistics_for_service(sample_notification.service_id)[0].sms_delivered == 0
assert dao_get_notification_statistics_for_service(sample_notification.service_id)[0].sms_requested == 1
assert dao_get_notification_statistics_for_service(sample_notification.service_id)[0].sms_error == 1
def test_firetext_callback_should_update_notification_status_sent(notify_api, notify_db, notify_db_session): def test_firetext_callback_should_update_notification_status_sent(notify_api, notify_db, notify_db_session):
@@ -1054,6 +1061,42 @@ def test_firetext_callback_should_update_notification_status_sent(notify_api, no
) )
updated = get_notification_by_id(notification.id) updated = get_notification_by_id(notification.id)
assert updated.status == 'sent' assert updated.status == 'sent'
assert dao_get_notification_statistics_for_service(notification.service_id)[0].sms_delivered == 0
assert dao_get_notification_statistics_for_service(notification.service_id)[0].sms_requested == 1
assert dao_get_notification_statistics_for_service(notification.service_id)[0].sms_error == 0
def test_firetext_callback_should_update_multiple_notification_status_sent(notify_api, notify_db, notify_db_session):
with notify_api.test_request_context():
with notify_api.test_client() as client:
notification1 = sample_notification(notify_db, notify_db_session, status='delivered')
notification2 = sample_notification(notify_db, notify_db_session, status='delivered')
notification3 = sample_notification(notify_db, notify_db_session, status='delivered')
client.post(
path='/notifications/sms/firetext',
data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format(
notification1.id
),
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
client.post(
path='/notifications/sms/firetext',
data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format(
notification2.id
),
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
client.post(
path='/notifications/sms/firetext',
data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format(
notification3.id
),
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
assert dao_get_notification_statistics_for_service(notification1.service_id)[0].sms_delivered == 3
assert dao_get_notification_statistics_for_service(notification1.service_id)[0].sms_requested == 3
assert dao_get_notification_statistics_for_service(notification1.service_id)[0].sms_error == 0
def test_ses_callback_should_not_need_auth(notify_api): def test_ses_callback_should_not_need_auth(notify_api):
@@ -1126,11 +1169,20 @@ def test_ses_callback_should_fail_if_notification_cannot_be_found(notify_db, not
assert json_resp['message'] == 'SES callback failed: notification not found. Status delivered' assert json_resp['message'] == 'SES callback failed: notification not found. Status delivered'
def test_ses_callback_should_update_notification_status(notify_api, notify_db, notify_db_session): def test_ses_callback_should_update_notification_status(
notify_api,
notify_db,
notify_db_session,
sample_email_template):
with notify_api.test_request_context(): with notify_api.test_request_context():
with notify_api.test_client() as client: with notify_api.test_client() as client:
notification = sample_notification(notify_db, notify_db_session, reference='ref') notification = sample_notification(
notify_db,
notify_db_session,
template=sample_email_template,
reference='ref'
)
assert get_notification_by_id(notification.id).status == 'sent' assert get_notification_by_id(notification.id).status == 'sent'
@@ -1144,6 +1196,64 @@ def test_ses_callback_should_update_notification_status(notify_api, notify_db, n
assert json_resp['result'] == 'success' assert json_resp['result'] == 'success'
assert json_resp['message'] == 'SES callback succeeded' assert json_resp['message'] == 'SES callback succeeded'
assert get_notification_by_id(notification.id).status == 'delivered' assert get_notification_by_id(notification.id).status == 'delivered'
assert dao_get_notification_statistics_for_service(notification.service_id)[0].emails_delivered == 1
assert dao_get_notification_statistics_for_service(notification.service_id)[0].emails_requested == 1
assert dao_get_notification_statistics_for_service(notification.service_id)[0].emails_error == 0
def test_ses_callback_should_update_multiple_notification_status_sent(
notify_api,
notify_db,
notify_db_session,
sample_email_template):
with notify_api.test_request_context():
with notify_api.test_client() as client:
notification1 = sample_notification(
notify_db,
notify_db_session,
template=sample_email_template,
reference='ref1',
status='delivered')
notification2 = sample_notification(
notify_db,
notify_db_session,
template=sample_email_template,
reference='ref2',
status='delivered')
notification3 = sample_notification(
notify_db,
notify_db_session,
template=sample_email_template,
reference='ref2',
status='delivered')
client.post(
path='/notifications/sms/firetext',
data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format(
notification1.id
),
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
client.post(
path='/notifications/sms/firetext',
data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format(
notification2.id
),
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
client.post(
path='/notifications/sms/firetext',
data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format(
notification3.id
),
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
assert dao_get_notification_statistics_for_service(notification1.service_id)[0].emails_delivered == 3
assert dao_get_notification_statistics_for_service(notification1.service_id)[0].emails_requested == 3
assert dao_get_notification_statistics_for_service(notification1.service_id)[0].emails_error == 0
def test_should_handle_invite_email_callbacks(notify_api, notify_db, notify_db_session): def test_should_handle_invite_email_callbacks(notify_api, notify_db, notify_db_session):
@@ -1203,3 +1313,7 @@ def ses_missing_notification_id_callback():
def ses_invalid_notification_type_callback(): def ses_invalid_notification_type_callback():
return b'{\n "Type" : "Notification",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Unknown\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa return b'{\n "Type" : "Notification",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Unknown\\",\\"mail\\":{\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa
{\n "Type" : "Notification",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{"notificationType{\n "Type" : "Notification",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{"notificationType":"Unknown","mail":{"timestamp":"2016-03-14T12:35:25.909Z","source":"test@test-domain.com","sourceArn":"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify","sendingAccountId":"123456789012","destination":["testing@digital.cabinet-office.gov.uk"]},"delivery":{"timestamp":"2016-03-14T12:35:26.567Z","processingTimeMillis":658,"recipients":["testing@digital.cabinet-office.gov.uk"],"smtpResponse":"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp","reportingMTA":"a6-238.smtp-out.eu-west-1.amazonses.com"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}":"Unknown","mail":{"timestamp":"2016-03-14T12:35:25.909Z","source":"test@test-domain.com","sourceArn":"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify","sendingAccountId":"123456789012","destination":["testing@digital.cabinet-office.gov.uk"]},"delivery":{"timestamp":"2016-03-14T12:35:26.567Z","processingTimeMillis":658,"recipients":["testing@digital.cabinet-office.gov.uk"],"smtpResponse":"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp","reportingMTA":"a6-238.smtp-out.eu-west-1.amazonses.com"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}