From 64f077f2f4c6d74cd0c69860f687cc37f0bad9a6 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Tue, 5 Jun 2018 14:25:24 +0100 Subject: [PATCH 1/3] New endpoint to return data for complaints. --- app/__init__.py | 4 ++++ app/complaint/__init__.py | 0 app/complaint/complaint_rest.py | 16 +++++++++++++++ app/models.py | 13 ++++++++++++ tests/app/complaint/__init__.py | 0 tests/app/complaint/test_complaint_rest.py | 24 ++++++++++++++++++++++ tests/app/dao/test_complaint_dao.py | 2 +- tests/app/db.py | 24 +++++++++++++++++++++- 8 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 app/complaint/__init__.py create mode 100644 app/complaint/complaint_rest.py create mode 100644 tests/app/complaint/__init__.py create mode 100644 tests/app/complaint/test_complaint_rest.py diff --git a/app/__init__.py b/app/__init__.py index 813e6e38f..46554c32e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -112,6 +112,7 @@ def register_blueprint(application): from app.billing.rest import billing_blueprint from app.organisation.rest import organisation_blueprint from app.organisation.invite_rest import organisation_invite_blueprint + from app.complaint.complaint_rest import complaint_blueprint service_blueprint.before_request(requires_admin_auth) application.register_blueprint(service_blueprint, url_prefix='/service') @@ -188,6 +189,9 @@ def register_blueprint(application): organisation_invite_blueprint.before_request(requires_admin_auth) application.register_blueprint(organisation_invite_blueprint) + complaint_blueprint.before_request(requires_admin_auth) + application.register_blueprint(complaint_blueprint) + def register_v2_blueprints(application): from app.v2.inbound_sms.get_inbound_sms import v2_inbound_sms_blueprint as get_inbound_sms diff --git a/app/complaint/__init__.py b/app/complaint/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/complaint/complaint_rest.py b/app/complaint/complaint_rest.py new file mode 100644 index 000000000..f59b12457 --- /dev/null +++ b/app/complaint/complaint_rest.py @@ -0,0 +1,16 @@ +from flask import Blueprint, jsonify +from sqlalchemy import desc + +from app.errors import register_errors +from app.models import Complaint + +complaint_blueprint = Blueprint('complaint', __name__, url_prefix='/complaint') + +register_errors(complaint_blueprint) + + +@complaint_blueprint.route('', methods=['GET']) +def get_all_complaints(): + complaints = Complaint.query.order_by(desc(Complaint.created_at)).all() + + return jsonify([x.serialize() for x in complaints]), 200 diff --git a/app/models.py b/app/models.py index 4d1802582..20dd6b1ab 100644 --- a/app/models.py +++ b/app/models.py @@ -1819,6 +1819,7 @@ class FactNotificationStatus(db.Model): class Complaint(db.Model): __tablename__ = 'complaints' + id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) notification_id = db.Column(UUID(as_uuid=True), db.ForeignKey('notification_history.id'), index=True, nullable=False) @@ -1828,3 +1829,15 @@ class Complaint(db.Model): complaint_type = db.Column(db.Text, nullable=True) complaint_date = db.Column(db.DateTime, nullable=True) created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow) + + def serialize(self): + return { + 'id': str(self.id), + 'notification_id': str(self.notification_id), + 'service_id': str(self.service_id), + 'service_name': self.service.name, + 'ses_feedback_id': str(self.ses_feedback_id), + 'complaint_type': self.complaint_type, + 'complaint_date': self.complaint_date.strftime(DATETIME_FORMAT) if self.complaint_date else None, + 'created_at': self.created_at.strftime(DATETIME_FORMAT), + } diff --git a/tests/app/complaint/__init__.py b/tests/app/complaint/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/app/complaint/test_complaint_rest.py b/tests/app/complaint/test_complaint_rest.py new file mode 100644 index 000000000..d3b352fca --- /dev/null +++ b/tests/app/complaint/test_complaint_rest.py @@ -0,0 +1,24 @@ +import json + +from tests import create_authorization_header +from tests.app.db import create_complaint, create_service, create_template, create_notification + + +def test_get_all_complaints_returns_list_for_multiple_services_and_complaints(client, notify_db_session): + service=create_service(service_name='service1') + template = create_template(service=service) + notification = create_notification(template=template) + complaint_1 = create_complaint() # default service + complaint_2 = create_complaint(service=service, notification=notification) + + response = client.get('/complaint', headers=[create_authorization_header()]) + + assert response.status_code == 200 + assert json.loads(response.get_data(as_text=True)) == [complaint_2.serialize(), complaint_1.serialize()] + + +def test_get_all_complaints_returns_empty_list(client): + response = client.get('/complaint', headers=[create_authorization_header()]) + + assert response.status_code == 200 + assert json.loads(response.get_data(as_text=True)) == [] diff --git a/tests/app/dao/test_complaint_dao.py b/tests/app/dao/test_complaint_dao.py index 92478cc33..d1acc93f7 100644 --- a/tests/app/dao/test_complaint_dao.py +++ b/tests/app/dao/test_complaint_dao.py @@ -22,7 +22,7 @@ def test_fetch_complaint_by_service_returns_one(sample_service, sample_email_not assert complaints[0] == complaint -def test_fetch_complaint_by_service_returns_empty_list(sample_service, sample_email_notification): +def test_fetch_complaint_by_service_returns_empty_list(sample_service): complaints = fetch_complaints_by_service(service_id=sample_service.id) assert len(complaints) == 0 diff --git a/tests/app/db.py b/tests/app/db.py index 76fa27895..93ff83579 100644 --- a/tests/app/db.py +++ b/tests/app/db.py @@ -34,7 +34,8 @@ from app.models import ( AnnualBilling, LetterRate, InvitedOrganisationUser, - FactBilling + FactBilling, + Complaint ) from app.dao.users_dao import save_model_user from app.dao.notifications_dao import ( @@ -564,6 +565,27 @@ def create_ft_billing(bst_date, return data +def create_complaint(service=None, + notification=None): + + if not service: + service=create_service() + if not notification: + template = create_template(service=service, template_type='email') + notification = create_notification(template=template) + + + complaint = Complaint(notification_id=notification.id, + service_id=service.id, + ses_feedback_id=str(uuid.uuid4()), + complaint_type='abuse', + complaint_date=datetime.utcnow() + ) + db.session.add(complaint) + db.session.commit() + return complaint + + def ses_complaint_callback_malformed_message_id(): return '{\n "Type" : "Notification",\n "msgId" : "ref1",' \ '\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",' \ From c5524a3fe96fb909876dc16e78840dd59a6a35c9 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Tue, 5 Jun 2018 17:23:24 +0100 Subject: [PATCH 2/3] [WIP] --- .../notifications_ses_callback.py | 7 +- .../celery/test_process_ses_receipts_tasks.py | 8 +- tests/app/complaint/test_complaint_rest.py | 6 +- tests/app/db.py | 95 +++++++++---------- .../test_notifications_ses_callback.py | 9 +- 5 files changed, 60 insertions(+), 65 deletions(-) diff --git a/app/notifications/notifications_ses_callback.py b/app/notifications/notifications_ses_callback.py index 6e2351fb4..fb5b1ccd7 100644 --- a/app/notifications/notifications_ses_callback.py +++ b/app/notifications/notifications_ses_callback.py @@ -38,7 +38,7 @@ def process_ses_response(ses_request): if notification_type == 'Bounce': notification_type = determine_notification_bounce_type(notification_type, ses_message) elif notification_type == 'Complaint': - handle_complaint(ses_request) + handle_complaint(ses_message) return try: @@ -108,13 +108,12 @@ def remove_emails_from_bounce(bounce_dict): recip.pop('emailAddress') -def handle_complaint(ses_request): - ses_message = json.loads(ses_request['Message']) +def handle_complaint(ses_message): remove_emails_from_complaint(ses_message) current_app.logger.info("Complaint from SES: \n{}".format(ses_message)) # It is possible that the we get a key error, let this fail so we can investigate. try: - reference = ses_request['MessageId'] + reference = ses_message['mail']['messageId'] except KeyError as e: current_app.logger.exception("Complaint from SES failed to get reference from message", e) return diff --git a/tests/app/celery/test_process_ses_receipts_tasks.py b/tests/app/celery/test_process_ses_receipts_tasks.py index d8f97096b..73bdbf147 100644 --- a/tests/app/celery/test_process_ses_receipts_tasks.py +++ b/tests/app/celery/test_process_ses_receipts_tasks.py @@ -7,7 +7,7 @@ from app.notifications.notifications_ses_callback import remove_emails_from_comp from tests.app.db import ( create_notification, ses_complaint_callback, - ses_notification_callback + ses_notification_callback, ) @@ -40,8 +40,7 @@ def test_process_ses_results_retry_called(notify_db, mocker): def test_process_ses_results_in_complaint(sample_email_template, mocker): notification = create_notification(template=sample_email_template, reference='ref1') mocked = mocker.patch("app.dao.notifications_dao.update_notification_status_by_reference") - response = json.loads(ses_complaint_callback()) - process_ses_results(response=response) + process_ses_results(response=ses_complaint_callback()) assert mocked.call_count == 0 complaints = Complaint.query.all() assert len(complaints) == 1 @@ -49,7 +48,6 @@ def test_process_ses_results_in_complaint(sample_email_template, mocker): def test_remove_emails_from_complaint(): - test_message = ses_complaint_callback() - test_json = json.loads(json.loads(test_message)['Message']) + test_json = json.loads(ses_complaint_callback()['Message']) remove_emails_from_complaint(test_json) assert "recipient1@example.com" not in test_json diff --git a/tests/app/complaint/test_complaint_rest.py b/tests/app/complaint/test_complaint_rest.py index d3b352fca..274c071d4 100644 --- a/tests/app/complaint/test_complaint_rest.py +++ b/tests/app/complaint/test_complaint_rest.py @@ -5,20 +5,20 @@ from tests.app.db import create_complaint, create_service, create_template, crea def test_get_all_complaints_returns_list_for_multiple_services_and_complaints(client, notify_db_session): - service=create_service(service_name='service1') + service = create_service(service_name='service1') template = create_template(service=service) notification = create_notification(template=template) complaint_1 = create_complaint() # default service complaint_2 = create_complaint(service=service, notification=notification) - response = client.get('/complaint', headers=[create_authorization_header()]) + response = client.get('/complaint', headers=[create_authorization_header()]) assert response.status_code == 200 assert json.loads(response.get_data(as_text=True)) == [complaint_2.serialize(), complaint_1.serialize()] def test_get_all_complaints_returns_empty_list(client): - response = client.get('/complaint', headers=[create_authorization_header()]) + response = client.get('/complaint', headers=[create_authorization_header()]) assert response.status_code == 200 assert json.loads(response.get_data(as_text=True)) == [] diff --git a/tests/app/db.py b/tests/app/db.py index 93ff83579..334e4d0a9 100644 --- a/tests/app/db.py +++ b/tests/app/db.py @@ -1,3 +1,4 @@ +import json from datetime import datetime, date import uuid @@ -399,10 +400,10 @@ def create_monthly_billing_entry( def create_reply_to_email( - service, - email_address, - is_default=True, - archived=False + service, + email_address, + is_default=True, + archived=False ): data = { 'service': service, @@ -419,11 +420,11 @@ def create_reply_to_email( def create_service_sms_sender( - service, - sms_sender, - is_default=True, - inbound_number_id=None, - archived=False + service, + sms_sender, + is_default=True, + inbound_number_id=None, + archived=False ): data = { 'service_id': service.id, @@ -441,10 +442,10 @@ def create_service_sms_sender( def create_letter_contact( - service, - contact_block, - is_default=True, - archived=False + service, + contact_block, + is_default=True, + archived=False ): data = { 'service': service, @@ -567,63 +568,61 @@ def create_ft_billing(bst_date, def create_complaint(service=None, notification=None): - if not service: - service=create_service() + service = create_service() if not notification: template = create_template(service=service, template_type='email') notification = create_notification(template=template) - complaint = Complaint(notification_id=notification.id, - service_id=service.id, - ses_feedback_id=str(uuid.uuid4()), - complaint_type='abuse', - complaint_date=datetime.utcnow() - ) + service_id=service.id, + ses_feedback_id=str(uuid.uuid4()), + complaint_type='abuse', + complaint_date=datetime.utcnow() + ) db.session.add(complaint) db.session.commit() return complaint def ses_complaint_callback_malformed_message_id(): - return '{\n "Type" : "Notification",\n "msgId" : "ref1",' \ - '\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",' \ - '\n "Message" : "{\\"notificationType\\":\\"Complaint\\",' \ - '\\"complaint\\": {\\"userAgent\\":\\"AnyCompany Feedback Loop (V0.01)\\",' \ - '\\"complainedRecipients\\":[{\\"emailAddress\\":\\"recipient1@example.com\\"}],' \ - '\\"arrivalDate\\":\\"2009-12-03T04:24:21.000-05:00\\", ' \ - '\\"timestamp\\":\\"2012-05-25T14:59:38.623Z\\", ' \ - '\\"feedbackId\\":\\"someSESID\\"}}"\n}' - + return { + 'Signature': 'bb', + 'SignatureVersion': '1', 'MessageAttributes': {}, 'MessageId': '98c6e927-af5d-5f3b-9522-bab736f2cbde', + 'UnsubscribeUrl': 'https://sns.eu-west-1.amazonaws.com', + 'TopicArn': 'arn:ses_notifications', 'Type': 'Notification', + 'Timestamp': '2018-06-05T14:00:15.952Z', 'Subject': None, + 'Message': '{"notificationType":"Complaint","complaint":{"complainedRecipients":[{"emailAddress":"someone@hotmail.com"}],"timestamp":"2018-06-05T13:59:58.000Z","feedbackId":"ses_feedback_id"},"mail":{"timestamp":"2018-06-05T14:00:15.950Z","source":"\\"Some Service\\" ","sourceArn":"arn:identity/notifications.service.gov.uk","sourceIp":"52.208.24.161","sendingAccountId":"888450439860","badMessageId":"ref1","destination":["someone@hotmail.com"]}}', # noqa + 'SigningCertUrl': 'https://sns.pem' + } def ses_complaint_callback_with_missing_complaint_type(): """ https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html#complaint-object """ - return '{\n "Type" : "Notification",\n "MessageId" : "ref1",' \ - '\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",' \ - '\n "Message" : "{\\"notificationType\\":\\"Complaint\\",' \ - '\\"complaint\\": {\\"userAgent\\":\\"AnyCompany Feedback Loop (V0.01)\\",' \ - '\\"complainedRecipients\\":[{\\"emailAddress\\":\\"recipient1@example.com\\"}],' \ - '\\"arrivalDate\\":\\"2009-12-03T04:24:21.000-05:00\\", ' \ - '\\"timestamp\\":\\"2012-05-25T14:59:38.623Z\\", ' \ - '\\"feedbackId\\":\\"someSESID\\"}}"\n}' - + return { + 'Signature': 'bb', + 'SignatureVersion': '1', 'MessageAttributes': {}, 'MessageId': '98c6e927-af5d-5f3b-9522-bab736f2cbde', + 'UnsubscribeUrl': 'https://sns.eu-west-1.amazonaws.com', + 'TopicArn': 'arn:ses_notifications', 'Type': 'Notification', + 'Timestamp': '2018-06-05T14:00:15.952Z', 'Subject': None, + 'Message': '{"notificationType":"Complaint","complaint":{"complainedRecipients":[{"emailAddress":"someone@hotmail.com"}],"timestamp":"2018-06-05T13:59:58.000Z","feedbackId":"ses_feedback_id"},"mail":{"timestamp":"2018-06-05T14:00:15.950Z","source":"\\"Some Service\\" ","sourceArn":"arn:identity/notifications.service.gov.uk","sourceIp":"52.208.24.161","sendingAccountId":"888450439860","messageId":"ref1","destination":["someone@hotmail.com"]}}', # noqa + 'SigningCertUrl': 'https://sns.pem' + } def ses_complaint_callback(): """ https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html#complaint-object """ - return '{\n "Type" : "Notification",\n "MessageId" : "ref1",' \ - '\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",' \ - '\n "Message" : "{\\"notificationType\\":\\"Complaint\\",' \ - '\\"complaint\\": {\\"userAgent\\":\\"AnyCompany Feedback Loop (V0.01)\\",' \ - '\\"complainedRecipients\\":[{\\"emailAddress\\":\\"recipient1@example.com\\"}],' \ - '\\"complaintFeedbackType\\":\\"abuse\\", ' \ - '\\"arrivalDate\\":\\"2009-12-03T04:24:21.000-05:00\\", ' \ - '\\"timestamp\\":\\"2012-05-25T14:59:38.623Z\\", ' \ - '\\"feedbackId\\":\\"someSESID\\"}}"\n}' + return { + 'Signature': 'bb', + 'SignatureVersion': '1', 'MessageAttributes': {}, 'MessageId': '98c6e927-af5d-5f3b-9522-bab736f2cbde', + 'UnsubscribeUrl': 'https://sns.eu-west-1.amazonaws.com', + 'TopicArn': 'arn:ses_notifications', 'Type': 'Notification', + 'Timestamp': '2018-06-05T14:00:15.952Z', 'Subject': None, + 'Message': '{"notificationType":"Complaint","complaint":{"complaintFeedbackType": "abuse", "complainedRecipients":[{"emailAddress":"someone@hotmail.com"}],"timestamp":"2018-06-05T13:59:58.000Z","feedbackId":"ses_feedback_id"},"mail":{"timestamp":"2018-06-05T14:00:15.950Z","source":"\\"Some Service\\" ","sourceArn":"arn:identity/notifications.service.gov.uk","sourceIp":"52.208.24.161","sendingAccountId":"888450439860","messageId":"ref1","destination":["someone@hotmail.com"]}}', # noqa + 'SigningCertUrl': 'https://sns.pem' + } def ses_notification_callback(): diff --git a/tests/app/notifications/test_notifications_ses_callback.py b/tests/app/notifications/test_notifications_ses_callback.py index c508ff5b4..2e5cbe8a6 100644 --- a/tests/app/notifications/test_notifications_ses_callback.py +++ b/tests/app/notifications/test_notifications_ses_callback.py @@ -201,23 +201,22 @@ def test_remove_emails_from_bounce(): def test_process_ses_results_in_complaint(sample_email_template): notification = create_notification(template=sample_email_template, reference='ref1') - response = json.loads(ses_complaint_callback()) - handle_complaint(response) + handle_complaint(ses_complaint_callback()) complaints = Complaint.query.all() assert len(complaints) == 1 assert complaints[0].notification_id == notification.id def test_handle_complaint_does_not_raise_exception_if_reference_is_missing(notify_api): - response = json.loads(ses_complaint_callback_malformed_message_id()) + response = json.loads(ses_complaint_callback_malformed_message_id()['Message']) handle_complaint(response) assert len(Complaint.query.all()) == 0 def test_process_ses_results_in_complaint_save_complaint_with_null_complaint_type(notify_api, sample_email_template): notification = create_notification(template=sample_email_template, reference='ref1') - response = json.loads(ses_complaint_callback_with_missing_complaint_type()) - handle_complaint(response) + msg = json.loads(ses_complaint_callback_with_missing_complaint_type()['Message']) + handle_complaint(msg) complaints = Complaint.query.all() assert len(complaints) == 1 assert complaints[0].notification_id == notification.id From 4966da82890780956f28631cf5599cba0a41a489 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Wed, 6 Jun 2018 10:37:31 +0100 Subject: [PATCH 3/3] Fix to handle_complaints. The json we were getting from SES was not quite as expected, the test data now reflects what we get. New test added, fix a test that was passing regardless. --- app/notifications/notifications_ses_callback.py | 2 +- tests/app/celery/test_process_ses_receipts_tasks.py | 2 +- tests/app/db.py | 9 +++++---- .../notifications/test_notifications_ses_callback.py | 10 +++++++++- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/notifications/notifications_ses_callback.py b/app/notifications/notifications_ses_callback.py index fb5b1ccd7..9cfd80bbd 100644 --- a/app/notifications/notifications_ses_callback.py +++ b/app/notifications/notifications_ses_callback.py @@ -111,7 +111,6 @@ def remove_emails_from_bounce(bounce_dict): def handle_complaint(ses_message): remove_emails_from_complaint(ses_message) current_app.logger.info("Complaint from SES: \n{}".format(ses_message)) - # It is possible that the we get a key error, let this fail so we can investigate. try: reference = ses_message['mail']['messageId'] except KeyError as e: @@ -132,6 +131,7 @@ def handle_complaint(ses_message): def remove_emails_from_complaint(complaint_dict): complaint_dict['complaint'].pop('complainedRecipients') + complaint_dict['mail'].pop('destination') def _check_and_queue_callback_task(notification): diff --git a/tests/app/celery/test_process_ses_receipts_tasks.py b/tests/app/celery/test_process_ses_receipts_tasks.py index 73bdbf147..99eb02c2b 100644 --- a/tests/app/celery/test_process_ses_receipts_tasks.py +++ b/tests/app/celery/test_process_ses_receipts_tasks.py @@ -50,4 +50,4 @@ def test_process_ses_results_in_complaint(sample_email_template, mocker): def test_remove_emails_from_complaint(): test_json = json.loads(ses_complaint_callback()['Message']) remove_emails_from_complaint(test_json) - assert "recipient1@example.com" not in test_json + assert "recipient1@example.com" not in json.dumps(test_json) diff --git a/tests/app/db.py b/tests/app/db.py index 334e4d0a9..5970165b1 100644 --- a/tests/app/db.py +++ b/tests/app/db.py @@ -1,4 +1,3 @@ -import json from datetime import datetime, date import uuid @@ -592,10 +591,11 @@ def ses_complaint_callback_malformed_message_id(): 'UnsubscribeUrl': 'https://sns.eu-west-1.amazonaws.com', 'TopicArn': 'arn:ses_notifications', 'Type': 'Notification', 'Timestamp': '2018-06-05T14:00:15.952Z', 'Subject': None, - 'Message': '{"notificationType":"Complaint","complaint":{"complainedRecipients":[{"emailAddress":"someone@hotmail.com"}],"timestamp":"2018-06-05T13:59:58.000Z","feedbackId":"ses_feedback_id"},"mail":{"timestamp":"2018-06-05T14:00:15.950Z","source":"\\"Some Service\\" ","sourceArn":"arn:identity/notifications.service.gov.uk","sourceIp":"52.208.24.161","sendingAccountId":"888450439860","badMessageId":"ref1","destination":["someone@hotmail.com"]}}', # noqa + 'Message': '{"notificationType":"Complaint","complaint":{"complainedRecipients":[{"emailAddress":"recipient1@example.com"}],"timestamp":"2018-06-05T13:59:58.000Z","feedbackId":"ses_feedback_id"},"mail":{"timestamp":"2018-06-05T14:00:15.950Z","source":"\\"Some Service\\" ","sourceArn":"arn:identity/notifications.service.gov.uk","sourceIp":"52.208.24.161","sendingAccountId":"888450439860","badMessageId":"ref1","destination":["recipient1@example.com"]}}', # noqa 'SigningCertUrl': 'https://sns.pem' } + def ses_complaint_callback_with_missing_complaint_type(): """ https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html#complaint-object @@ -606,10 +606,11 @@ def ses_complaint_callback_with_missing_complaint_type(): 'UnsubscribeUrl': 'https://sns.eu-west-1.amazonaws.com', 'TopicArn': 'arn:ses_notifications', 'Type': 'Notification', 'Timestamp': '2018-06-05T14:00:15.952Z', 'Subject': None, - 'Message': '{"notificationType":"Complaint","complaint":{"complainedRecipients":[{"emailAddress":"someone@hotmail.com"}],"timestamp":"2018-06-05T13:59:58.000Z","feedbackId":"ses_feedback_id"},"mail":{"timestamp":"2018-06-05T14:00:15.950Z","source":"\\"Some Service\\" ","sourceArn":"arn:identity/notifications.service.gov.uk","sourceIp":"52.208.24.161","sendingAccountId":"888450439860","messageId":"ref1","destination":["someone@hotmail.com"]}}', # noqa + 'Message': '{"notificationType":"Complaint","complaint":{"complainedRecipients":[{"emailAddress":"recipient1@example.com"}],"timestamp":"2018-06-05T13:59:58.000Z","feedbackId":"ses_feedback_id"},"mail":{"timestamp":"2018-06-05T14:00:15.950Z","source":"\\"Some Service\\" ","sourceArn":"arn:identity/notifications.service.gov.uk","sourceIp":"52.208.24.161","sendingAccountId":"888450439860","messageId":"ref1","destination":["recipient1@example.com"]}}', # noqa 'SigningCertUrl': 'https://sns.pem' } + def ses_complaint_callback(): """ https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html#complaint-object @@ -620,7 +621,7 @@ def ses_complaint_callback(): 'UnsubscribeUrl': 'https://sns.eu-west-1.amazonaws.com', 'TopicArn': 'arn:ses_notifications', 'Type': 'Notification', 'Timestamp': '2018-06-05T14:00:15.952Z', 'Subject': None, - 'Message': '{"notificationType":"Complaint","complaint":{"complaintFeedbackType": "abuse", "complainedRecipients":[{"emailAddress":"someone@hotmail.com"}],"timestamp":"2018-06-05T13:59:58.000Z","feedbackId":"ses_feedback_id"},"mail":{"timestamp":"2018-06-05T14:00:15.950Z","source":"\\"Some Service\\" ","sourceArn":"arn:identity/notifications.service.gov.uk","sourceIp":"52.208.24.161","sendingAccountId":"888450439860","messageId":"ref1","destination":["someone@hotmail.com"]}}', # noqa + 'Message': '{"notificationType":"Complaint","complaint":{"complaintFeedbackType": "abuse", "complainedRecipients":[{"emailAddress":"recipient1@example.com"}],"timestamp":"2018-06-05T13:59:58.000Z","feedbackId":"ses_feedback_id"},"mail":{"timestamp":"2018-06-05T14:00:15.950Z","source":"\\"Some Service\\" ","sourceArn":"arn:identity/notifications.service.gov.uk","sourceIp":"52.208.24.161","sendingAccountId":"888450439860","messageId":"ref1","destination":["recipient1@example.com"]}}', # noqa 'SigningCertUrl': 'https://sns.pem' } diff --git a/tests/app/notifications/test_notifications_ses_callback.py b/tests/app/notifications/test_notifications_ses_callback.py index 2e5cbe8a6..82c30e547 100644 --- a/tests/app/notifications/test_notifications_ses_callback.py +++ b/tests/app/notifications/test_notifications_ses_callback.py @@ -1,7 +1,9 @@ from datetime import datetime +import pytest from flask import json from freezegun import freeze_time +from sqlalchemy.exc import SQLAlchemyError from app import statsd_client from app.dao.notifications_dao import get_notification_by_id @@ -201,7 +203,7 @@ def test_remove_emails_from_bounce(): def test_process_ses_results_in_complaint(sample_email_template): notification = create_notification(template=sample_email_template, reference='ref1') - handle_complaint(ses_complaint_callback()) + handle_complaint(json.loads(ses_complaint_callback()['Message'])) complaints = Complaint.query.all() assert len(complaints) == 1 assert complaints[0].notification_id == notification.id @@ -213,6 +215,12 @@ def test_handle_complaint_does_not_raise_exception_if_reference_is_missing(notif assert len(Complaint.query.all()) == 0 +def test_handle_complaint_does_raise_exception_if_notification_not_found(notify_api): + response = json.loads(ses_complaint_callback()['Message']) + with pytest.raises(expected_exception=SQLAlchemyError): + handle_complaint(response) + + def test_process_ses_results_in_complaint_save_complaint_with_null_complaint_type(notify_api, sample_email_template): notification = create_notification(template=sample_email_template, reference='ref1') msg = json.loads(ses_complaint_callback_with_missing_complaint_type()['Message'])