diff --git a/app/notifications/process_client_response.py b/app/notifications/process_client_response.py index a586a52f3..4699e0265 100644 --- a/app/notifications/process_client_response.py +++ b/app/notifications/process_client_response.py @@ -58,11 +58,11 @@ def process_sms_client_response(status, reference, client_name): # record stats notification = notifications_dao.update_notification_status_by_id(reference, notification_status) if not notification: - status_error = "{} callback failed: notification {} either not found or already updated " \ - "from sending. Status {}".format(client_name, - reference, - notification_status_message) - return success, status_error + current_app.logger.info("{} callback failed: notification {} either not found or already updated " + "from sending. Status {}".format(client_name, + reference, + notification_status_message)) + return success, errors if not notification_success: current_app.logger.info( diff --git a/app/notifications/rest.py b/app/notifications/rest.py index f981aa441..f688c5160 100644 --- a/app/notifications/rest.py +++ b/app/notifications/rest.py @@ -149,10 +149,7 @@ def process_firetext_response(): if errors: raise InvalidRequest(errors, status_code=400) - response_code = request.form.get('code') status = request.form.get('status') - current_app.logger.info('Firetext status: {}, extended error code: {}'.format(status, response_code)) - success, errors = process_sms_client_response(status=status, reference=request.form.get('reference'), client_name=client_name) diff --git a/tests/app/notifications/rest/test_callbacks.py b/tests/app/notifications/rest/test_callbacks.py index 1d8cc85ca..a1947f57b 100644 --- a/tests/app/notifications/rest/test_callbacks.py +++ b/tests/app/notifications/rest/test_callbacks.py @@ -1,9 +1,9 @@ import uuid from datetime import datetime + from flask import json from freezegun import freeze_time -from unittest.mock import call import app.celery.tasks from app.dao.notifications_dao import ( @@ -13,712 +13,670 @@ from app.models import NotificationStatistics from tests.app.conftest import sample_notification as create_sample_notification -def test_firetext_callback_should_not_need_auth(notify_api, mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=0&reference=send-sms-code&time=2016-03-10 14:17:00', - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) +def test_firetext_callback_should_not_need_auth(client, mocker): + mocker.patch('app.statsd_client.incr') + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=0&reference=send-sms-code&time=2016-03-10 14:17:00', + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - assert response.status_code == 200 + assert response.status_code == 200 -def test_firetext_callback_should_return_400_if_empty_reference(notify_api, mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=0&reference=&time=2016-03-10 14:17:00', - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) +def test_firetext_callback_should_return_400_if_empty_reference(client, mocker): + mocker.patch('app.statsd_client.incr') + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=0&reference=&time=2016-03-10 14:17:00', + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == ['Firetext callback failed: reference missing'] + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 400 + assert json_resp['result'] == 'error' + assert json_resp['message'] == ['Firetext callback failed: reference missing'] -def test_firetext_callback_should_return_400_if_no_reference(notify_api, mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=0&time=2016-03-10 14:17:00', - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) +def test_firetext_callback_should_return_400_if_no_reference(client, mocker): + mocker.patch('app.statsd_client.incr') + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=0&time=2016-03-10 14:17:00', + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == ['Firetext callback failed: reference missing'] + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 400 + assert json_resp['result'] == 'error' + assert json_resp['message'] == ['Firetext callback failed: reference missing'] -def test_firetext_callback_should_return_200_if_send_sms_reference(notify_api, mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference=send-sms-code', - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) +def test_firetext_callback_should_return_200_if_send_sms_reference(client, mocker): + mocker.patch('app.statsd_client.incr') + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference=send-sms-code', + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert json_resp['result'] == 'success' - assert json_resp['message'] == 'Firetext callback succeeded: send-sms-code' + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_resp['result'] == 'success' + assert json_resp['message'] == 'Firetext callback succeeded: send-sms-code' -def test_firetext_callback_should_return_400_if_no_status(notify_api, mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&time=2016-03-10 14:17:00&reference=send-sms-code', - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) +def test_firetext_callback_should_return_400_if_no_status(client, mocker): + mocker.patch('app.statsd_client.incr') + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&time=2016-03-10 14:17:00&reference=send-sms-code', + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == ['Firetext callback failed: status missing'] + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 400 + assert json_resp['result'] == 'error' + assert json_resp['message'] == ['Firetext callback failed: status missing'] -def test_firetext_callback_should_return_400_if_unknown_status(notify_api, mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=99&time=2016-03-10 14:17:00&reference={}'.format(uuid.uuid4()), - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) +def test_firetext_callback_should_return_400_if_unknown_status(client, mocker): + mocker.patch('app.statsd_client.incr') + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=99&time=2016-03-10 14:17:00&reference={}'.format(uuid.uuid4()), + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'Firetext callback failed: status 99 not found.' + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 400 + assert json_resp['result'] == 'error' + assert json_resp['message'] == 'Firetext callback failed: status 99 not found.' -def test_firetext_callback_should_return_400_if_invalid_guid_notification_id(notify_api, mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference=1234', - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) +def test_firetext_callback_returns_200_when_notification_id_not_found_or_already_updated(client, mocker): + mocker.patch('app.statsd_client.incr') + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference=1234', + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'Firetext callback with invalid reference 1234' + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 400 + assert json_resp['result'] == 'error' + assert json_resp['message'] == 'Firetext callback with invalid reference 1234' -def test_firetext_callback_should_return_404_if_cannot_find_notification_id( +def test_callback_should_return_200_if_cannot_find_notification_id( notify_db, notify_db_session, - notify_api, + client, mocker ): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - missing_notification_id = uuid.uuid4() - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format( - missing_notification_id - ), - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) + mocker.patch('app.statsd_client.incr') + missing_notification_id = uuid.uuid4() + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format( + missing_notification_id + ), + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'Firetext callback failed: notification {} either not found ' \ - 'or already updated from sending. Status {}'.format( - missing_notification_id, - 'Delivered' - ) + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_resp['result'] == 'success' def test_firetext_callback_should_update_notification_status( - notify_db, notify_db_session, notify_api, sample_email_template, mocker + notify_db, notify_db_session, client, sample_email_template, mocker ): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') + mocker.patch('app.statsd_client.incr') - notification = create_sample_notification( - notify_db, - notify_db_session, - template=sample_email_template, - reference='ref', - status='sending', - sent_at=datetime.utcnow()) + notification = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template, + reference='ref', + status='sending', + sent_at=datetime.utcnow()) - original = get_notification_by_id(notification.id) - assert original.status == 'sending' + original = get_notification_by_id(notification.id) + assert original.status == 'sending' - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format( - notification.id - ), - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format( + notification.id + ), + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert json_resp['result'] == 'success' - assert json_resp['message'] == 'Firetext callback succeeded. reference {} updated'.format( - notification.id - ) - updated = get_notification_by_id(notification.id) - assert updated.status == 'delivered' - assert get_notification_by_id(notification.id).status == 'delivered' + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_resp['result'] == 'success' + assert json_resp['message'] == 'Firetext callback succeeded. reference {} updated'.format( + notification.id + ) + updated = get_notification_by_id(notification.id) + assert updated.status == 'delivered' + assert get_notification_by_id(notification.id).status == 'delivered' def test_firetext_callback_should_update_notification_status_failed( - notify_db, notify_db_session, notify_api, sample_template, mocker + notify_db, notify_db_session, client, sample_template, mocker ): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') + mocker.patch('app.statsd_client.incr') - notification = create_sample_notification( - notify_db, - notify_db_session, - template=sample_template, - reference='ref', - status='sending', - sent_at=datetime.utcnow()) + notification = create_sample_notification( + notify_db, + notify_db_session, + template=sample_template, + reference='ref', + status='sending', + sent_at=datetime.utcnow()) - original = get_notification_by_id(notification.id) - assert original.status == 'sending' + original = get_notification_by_id(notification.id) + assert original.status == 'sending' - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=1&time=2016-03-10 14:17:00&reference={}'.format( - notification.id - ), - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=1&time=2016-03-10 14:17:00&reference={}'.format( + notification.id + ), + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert json_resp['result'] == 'success' - assert json_resp['message'] == 'Firetext callback succeeded. reference {} updated'.format( - notification.id - ) - assert get_notification_by_id(notification.id).status == 'permanent-failure' + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_resp['result'] == 'success' + assert json_resp['message'] == 'Firetext callback succeeded. reference {} updated'.format( + notification.id + ) + assert get_notification_by_id(notification.id).status == 'permanent-failure' -def test_firetext_callback_should_update_notification_status_pending(notify_api, notify_db, notify_db_session, mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - notification = create_sample_notification( - notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() - ) - original = get_notification_by_id(notification.id) - assert original.status == 'sending' +def test_firetext_callback_should_update_notification_status_pending(client, notify_db, notify_db_session, mocker): + mocker.patch('app.statsd_client.incr') + notification = create_sample_notification( + notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + ) + original = get_notification_by_id(notification.id) + assert original.status == 'sending' - response = client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=2&time=2016-03-10 14:17:00&reference={}'.format( - notification.id - ), - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) + response = client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=2&time=2016-03-10 14:17:00&reference={}'.format( + notification.id + ), + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert json_resp['result'] == 'success' - assert json_resp['message'] == 'Firetext callback succeeded. reference {} updated'.format( - notification.id - ) - assert get_notification_by_id(notification.id).status == 'pending' + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_resp['result'] == 'success' + assert json_resp['message'] == 'Firetext callback succeeded. reference {} updated'.format( + notification.id + ) + assert get_notification_by_id(notification.id).status == 'pending' def test_firetext_callback_should_update_multiple_notification_status_sent( - notify_api, + client, notify_db, notify_db_session, mocker ): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - mocker.patch('app.statsd_client.incr') - notification1 = create_sample_notification( - notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() - ) - notification2 = create_sample_notification( - notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() - ) - notification3 = create_sample_notification( - notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() - ) + mocker.patch('app.statsd_client.incr') + notification1 = create_sample_notification( + notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + ) + notification2 = create_sample_notification( + notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + ) + notification3 = create_sample_notification( + notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + ) - 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( + 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( + 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')]) + 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')]) -def test_process_mmg_response_return_200_when_cid_is_send_sms_code(notify_api): - with notify_api.test_request_context(): - data = '{"reference": "10100164", "CID": "send-sms-code", "MSISDN": "447775349060", "status": "3", \ +def test_process_mmg_response_return_200_when_cid_is_send_sms_code(client): + data = '{"reference": "10100164", "CID": "send-sms-code", "MSISDN": "447775349060", "status": "3", \ "deliverytime": "2016-04-05 16:01:07"}' - with notify_api.test_client() as client: - response = client.post(path='notifications/sms/mmg', - data=data, - headers=[('Content-Type', 'application/json')]) - assert response.status_code == 200 - json_data = json.loads(response.data) - assert json_data['result'] == 'success' - assert json_data['message'] == 'MMG callback succeeded: send-sms-code' + response = client.post(path='notifications/sms/mmg', + data=data, + headers=[('Content-Type', 'application/json')]) + assert response.status_code == 200 + json_data = json.loads(response.data) + assert json_data['result'] == 'success' + assert json_data['message'] == 'MMG callback succeeded: send-sms-code' def test_process_mmg_response_returns_200_when_cid_is_valid_notification_id( - notify_db, notify_db_session, notify_api + notify_db, notify_db_session, client ): - with notify_api.test_client() as client: + notification = create_sample_notification( + notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + ) + data = json.dumps({"reference": "mmg_reference", + "CID": str(notification.id), + "MSISDN": "447777349060", + "status": "3", + "deliverytime": "2016-04-05 16:01:07"}) + + response = client.post(path='notifications/sms/mmg', + data=data, + headers=[('Content-Type', 'application/json')]) + assert response.status_code == 200 + json_data = json.loads(response.data) + assert json_data['result'] == 'success' + assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) + assert get_notification_by_id(notification.id).status == 'delivered' + + +def test_process_mmg_response_status_5_updates_notification_with_permanently_failed( + notify_db, notify_db_session, client +): + notification = create_sample_notification( + notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + ) + + data = json.dumps({"reference": "mmg_reference", + "CID": str(notification.id), + "MSISDN": "447777349060", + "status": 5}) + + response = client.post(path='notifications/sms/mmg', + data=data, + headers=[('Content-Type', 'application/json')]) + assert response.status_code == 200 + json_data = json.loads(response.data) + assert json_data['result'] == 'success' + assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) + assert get_notification_by_id(notification.id).status == 'permanent-failure' + + +def test_process_mmg_response_status_2_updates_notification_with_permanently_failed( + notify_db, notify_db_session, client +): + notification = create_sample_notification( + notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + ) + data = json.dumps({"reference": "mmg_reference", + "CID": str(notification.id), + "MSISDN": "447777349060", + "status": 2}) + + response = client.post(path='notifications/sms/mmg', + data=data, + headers=[('Content-Type', 'application/json')]) + assert response.status_code == 200 + json_data = json.loads(response.data) + assert json_data['result'] == 'success' + assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) + assert get_notification_by_id(notification.id).status == 'permanent-failure' + + +def test_process_mmg_response_status_4_updates_notification_with_temporary_failed( + notify_db, notify_db_session, client +): + notification = create_sample_notification( + notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + ) + + data = json.dumps({"reference": "mmg_reference", + "CID": str(notification.id), + "MSISDN": "447777349060", + "status": 4}) + + response = client.post(path='notifications/sms/mmg', + data=data, + headers=[('Content-Type', 'application/json')]) + assert response.status_code == 200 + json_data = json.loads(response.data) + assert json_data['result'] == 'success' + assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) + assert get_notification_by_id(notification.id).status == 'temporary-failure' + + +def test_process_mmg_response_unknown_status_updates_notification_with_failed( + notify_db, notify_db_session, client +): + notification = create_sample_notification( + notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + ) + data = json.dumps({"reference": "mmg_reference", + "CID": str(notification.id), + "MSISDN": "447777349060", + "status": 10}) + + response = client.post(path='notifications/sms/mmg', + data=data, + headers=[('Content-Type', 'application/json')]) + assert response.status_code == 200 + json_data = json.loads(response.data) + assert json_data['result'] == 'success' + assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) + assert get_notification_by_id(notification.id).status == 'failed' + + +def test_process_mmg_response_returns_400_for_malformed_data(client): + data = json.dumps({"reference": "mmg_reference", + "monkey": 'random thing', + "MSISDN": "447777349060", + "no_status": 00, + "deliverytime": "2016-04-05 16:01:07"}) + + response = client.post(path='notifications/sms/mmg', + data=data, + headers=[('Content-Type', 'application/json')]) + assert response.status_code == 400 + json_data = json.loads(response.data) + assert json_data['result'] == 'error' + assert len(json_data['message']) == 2 + assert "{} callback failed: {} missing".format('MMG', 'status') in json_data['message'] + assert "{} callback failed: {} missing".format('MMG', 'CID') in json_data['message'] + + +def test_ses_callback_should_not_need_auth(client): + response = client.post( + path='/notifications/email/ses', + data=ses_notification_callback(), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + assert response.status_code == 404 + + +def test_mmg_callback_returns_200_when_notification_id_not_found_or_already_updated(client): + data = '{"reference": "10100164", "CID": "send-sms-code", "MSISDN": "447775349060", "status": "3", \ + "deliverytime": "2016-04-05 16:01:07"}' + + response = client.post(path='notifications/sms/mmg', + data=data, + headers=[('Content-Type', 'application/json')]) + assert response.status_code == 200 + + +def test_ses_callback_should_not_need_auth(client): + response = client.post( + path='/notifications/email/ses', + data=ses_notification_callback(), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + assert response.status_code == 404 + + +def test_ses_callback_should_fail_if_invalid_json(client): + response = client.post( + path='/notifications/email/ses', + data="nonsense", + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 400 + assert json_resp['result'] == 'error' + assert json_resp['message'] == 'SES callback failed: invalid json' + + +def test_ses_callback_should_fail_if_invalid_notification_type(client): + response = client.post( + path='/notifications/email/ses', + data=ses_invalid_notification_type_callback(), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 400 + assert json_resp['result'] == 'error' + assert json_resp['message'] == 'SES callback failed: status Unknown not found' + + +def test_ses_callback_should_fail_if_missing_message_id(client): + response = client.post( + path='/notifications/email/ses', + data=ses_missing_notification_id_callback(), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 400 + assert json_resp['result'] == 'error' + assert json_resp['message'] == 'SES callback failed: messageId missing' + + +def test_ses_callback_should_fail_if_notification_cannot_be_found(notify_db, notify_db_session, client): + response = client.post( + path='/notifications/email/ses', + data=ses_invalid_notification_id_callback(), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 404 + assert json_resp['result'] == 'error' + assert json_resp['message'] == 'SES callback failed: notification either not found or already updated from sending. Status delivered' # noqa + + +def test_ses_callback_should_update_notification_status( + client, + notify_db, + notify_db_session, + sample_email_template, + mocker): + with freeze_time('2001-01-01T12:00:00'): + mocker.patch('app.statsd_client.incr') + mocker.patch('app.statsd_client.timing_with_dates') + notification = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template, + reference='ref', + status='sending', + sent_at=datetime.utcnow() + ) + + assert get_notification_by_id(notification.id).status == 'sending' + + response = client.post( + path='/notifications/email/ses', + data=ses_notification_callback(), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_resp['result'] == 'success' + assert json_resp['message'] == 'SES callback succeeded' + assert get_notification_by_id(notification.id).status == 'delivered' + app.statsd_client.timing_with_dates.assert_any_call( + "callback.ses.elapsed-time", datetime.utcnow(), notification.sent_at + ) + app.statsd_client.incr.assert_any_call("callback.ses.delivered") + + +def test_ses_callback_should_update_multiple_notification_status_sent( + client, + notify_db, + notify_db_session, + sample_email_template, + mocker): + notification1 = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template, + reference='ref1', + sent_at=datetime.utcnow(), + status='sending') + + notification2 = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template, + reference='ref2', + sent_at=datetime.utcnow(), + status='sending') + + notification3 = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template, + reference='ref3', + sent_at=datetime.utcnow(), + status='sending') + + resp1 = client.post( + path='/notifications/email/ses', + data=ses_notification_callback(ref='ref1'), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + resp2 = client.post( + path='/notifications/email/ses', + data=ses_notification_callback(ref='ref2'), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + resp3 = client.post( + path='/notifications/email/ses', + data=ses_notification_callback(ref='ref3'), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + + assert resp1.status_code == 200 + assert resp2.status_code == 200 + assert resp3.status_code == 200 + + +def test_ses_callback_should_set_status_to_temporary_failure(client, + notify_db, + notify_db_session, + sample_email_template): + notification = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template, + reference='ref', + status='sending', + sent_at=datetime.utcnow() + ) + assert get_notification_by_id(notification.id).status == 'sending' + + response = client.post( + path='/notifications/email/ses', + data=ses_soft_bounce_callback(), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_resp['result'] == 'success' + assert json_resp['message'] == 'SES callback succeeded' + assert get_notification_by_id(notification.id).status == 'temporary-failure' + + +def test_ses_callback_should_not_set_status_once_status_is_delivered(client, + notify_db, + notify_db_session, + sample_email_template): + notification = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template, + reference='ref', + status='delivered', + sent_at=datetime.utcnow() + ) + + assert get_notification_by_id(notification.id).status == 'delivered' + + response = client.post( + path='/notifications/email/ses', + data=ses_soft_bounce_callback(), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 404 + assert json_resp['result'] == 'error' + assert json_resp['message'] == 'SES callback failed: notification either not found or already updated from sending. Status temporary-failure' # noqa + assert get_notification_by_id(notification.id).status == 'delivered' + + +def test_ses_callback_should_set_status_to_permanent_failure(client, + notify_db, + notify_db_session, + sample_email_template): + notification = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template, + reference='ref', + status='sending', + sent_at=datetime.utcnow() + ) + + assert get_notification_by_id(notification.id).status == 'sending' + + response = client.post( + path='/notifications/email/ses', + data=ses_hard_bounce_callback(), + headers=[('Content-Type', 'text/plain; charset=UTF-8')] + ) + json_resp = json.loads(response.get_data(as_text=True)) + assert response.status_code == 200 + assert json_resp['result'] == 'success' + assert json_resp['message'] == 'SES callback succeeded' + assert get_notification_by_id(notification.id).status == 'permanent-failure' + + +def test_process_mmg_response_records_statsd(notify_db, notify_db_session, client, mocker): + with freeze_time('2001-01-01T12:00:00'): + + mocker.patch('app.statsd_client.incr') + mocker.patch('app.statsd_client.timing_with_dates') notification = create_sample_notification( notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() ) + data = json.dumps({"reference": "mmg_reference", "CID": str(notification.id), "MSISDN": "447777349060", "status": "3", "deliverytime": "2016-04-05 16:01:07"}) - response = client.post(path='notifications/sms/mmg', - data=data, - headers=[('Content-Type', 'application/json')]) - assert response.status_code == 200 - json_data = json.loads(response.data) - assert json_data['result'] == 'success' - assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) - assert get_notification_by_id(notification.id).status == 'delivered' + client.post(path='notifications/sms/mmg', + data=data, + headers=[('Content-Type', 'application/json')]) + + app.statsd_client.incr.assert_any_call("callback.mmg.delivered") + app.statsd_client.timing_with_dates.assert_any_call( + "callback.mmg.elapsed-time", datetime.utcnow(), notification.sent_at + ) -def test_process_mmg_response_status_5_updates_notification_with_permanently_failed( - notify_db, notify_db_session, notify_api -): - with notify_api.test_client() as client: +def test_firetext_callback_should_record_statsd(client, notify_db, notify_db_session, mocker): + with freeze_time('2001-01-01T12:00:00'): + + mocker.patch('app.statsd_client.incr') + mocker.patch('app.statsd_client.timing_with_dates') notification = create_sample_notification( notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() ) - data = json.dumps({"reference": "mmg_reference", - "CID": str(notification.id), - "MSISDN": "447777349060", - "status": 5}) + client.post( + path='/notifications/sms/firetext', + data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&code=101&reference={}'.format( + notification.id + ), + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - response = client.post(path='notifications/sms/mmg', - data=data, - headers=[('Content-Type', 'application/json')]) - assert response.status_code == 200 - json_data = json.loads(response.data) - assert json_data['result'] == 'success' - assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) - assert get_notification_by_id(notification.id).status == 'permanent-failure' - - -def test_process_mmg_response_status_2_updates_notification_with_permanently_failed( - notify_db, notify_db_session, notify_api -): - with notify_api.test_client() as client: - notification = create_sample_notification( - notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() + app.statsd_client.timing_with_dates.assert_any_call( + "callback.firetext.elapsed-time", datetime.utcnow(), notification.sent_at ) - data = json.dumps({"reference": "mmg_reference", - "CID": str(notification.id), - "MSISDN": "447777349060", - "status": 2}) - - response = client.post(path='notifications/sms/mmg', - data=data, - headers=[('Content-Type', 'application/json')]) - assert response.status_code == 200 - json_data = json.loads(response.data) - assert json_data['result'] == 'success' - assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) - assert get_notification_by_id(notification.id).status == 'permanent-failure' - - -def test_process_mmg_response_status_4_updates_notification_with_temporary_failed( - notify_db, notify_db_session, notify_api -): - with notify_api.test_client() as client: - notification = create_sample_notification( - notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() - ) - - data = json.dumps({"reference": "mmg_reference", - "CID": str(notification.id), - "MSISDN": "447777349060", - "status": 4}) - - response = client.post(path='notifications/sms/mmg', - data=data, - headers=[('Content-Type', 'application/json')]) - assert response.status_code == 200 - json_data = json.loads(response.data) - assert json_data['result'] == 'success' - assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) - assert get_notification_by_id(notification.id).status == 'temporary-failure' - - -def test_process_mmg_response_unknown_status_updates_notification_with_failed( - notify_db, notify_db_session, notify_api -): - with notify_api.test_client() as client: - - notification = create_sample_notification( - notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() - ) - data = json.dumps({"reference": "mmg_reference", - "CID": str(notification.id), - "MSISDN": "447777349060", - "status": 10}) - - response = client.post(path='notifications/sms/mmg', - data=data, - headers=[('Content-Type', 'application/json')]) - assert response.status_code == 200 - json_data = json.loads(response.data) - assert json_data['result'] == 'success' - assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(notification.id) - assert get_notification_by_id(notification.id).status == 'failed' - - -def test_process_mmg_response_returns_400_for_malformed_data(notify_api): - with notify_api.test_client() as client: - data = json.dumps({"reference": "mmg_reference", - "monkey": 'random thing', - "MSISDN": "447777349060", - "no_status": 00, - "deliverytime": "2016-04-05 16:01:07"}) - - response = client.post(path='notifications/sms/mmg', - data=data, - headers=[('Content-Type', 'application/json')]) - assert response.status_code == 400 - json_data = json.loads(response.data) - assert json_data['result'] == 'error' - assert len(json_data['message']) == 2 - assert "{} callback failed: {} missing".format('MMG', 'status') in json_data['message'] - assert "{} callback failed: {} missing".format('MMG', 'CID') in json_data['message'] - - -def test_ses_callback_should_not_need_auth(notify_api): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - response = client.post( - path='/notifications/email/ses', - data=ses_notification_callback(), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - assert response.status_code == 404 - - -def test_ses_callback_should_fail_if_invalid_json(notify_api): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - response = client.post( - path='/notifications/email/ses', - data="nonsense", - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'SES callback failed: invalid json' - - -def test_ses_callback_should_fail_if_invalid_notification_type(notify_api): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - response = client.post( - path='/notifications/email/ses', - data=ses_invalid_notification_type_callback(), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'SES callback failed: status Unknown not found' - - -def test_ses_callback_should_fail_if_missing_message_id(notify_api): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - response = client.post( - path='/notifications/email/ses', - data=ses_missing_notification_id_callback(), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'SES callback failed: messageId missing' - - -def test_ses_callback_should_fail_if_notification_cannot_be_found(notify_db, notify_db_session, notify_api): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - response = client.post( - path='/notifications/email/ses', - data=ses_invalid_notification_id_callback(), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 404 - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'SES callback failed: notification either not found or already updated from sending. Status delivered' # noqa - - -def test_ses_callback_should_update_notification_status( - notify_api, - notify_db, - notify_db_session, - sample_email_template, - mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - with freeze_time('2001-01-01T12:00:00'): - mocker.patch('app.statsd_client.incr') - mocker.patch('app.statsd_client.timing_with_dates') - notification = create_sample_notification( - notify_db, - notify_db_session, - template=sample_email_template, - reference='ref', - status='sending', - sent_at=datetime.utcnow() - ) - - assert get_notification_by_id(notification.id).status == 'sending' - - response = client.post( - path='/notifications/email/ses', - data=ses_notification_callback(), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert json_resp['result'] == 'success' - assert json_resp['message'] == 'SES callback succeeded' - assert get_notification_by_id(notification.id).status == 'delivered' - app.statsd_client.timing_with_dates.assert_any_call( - "callback.ses.elapsed-time", datetime.utcnow(), notification.sent_at - ) - app.statsd_client.incr.assert_any_call("callback.ses.delivered") - - -def test_ses_callback_should_update_multiple_notification_status_sent( - notify_api, - notify_db, - notify_db_session, - sample_email_template, - mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - notification1 = create_sample_notification( - notify_db, - notify_db_session, - template=sample_email_template, - reference='ref1', - sent_at=datetime.utcnow(), - status='sending') - - notification2 = create_sample_notification( - notify_db, - notify_db_session, - template=sample_email_template, - reference='ref2', - sent_at=datetime.utcnow(), - status='sending') - - notification3 = create_sample_notification( - notify_db, - notify_db_session, - template=sample_email_template, - reference='ref3', - sent_at=datetime.utcnow(), - status='sending') - - resp1 = client.post( - path='/notifications/email/ses', - data=ses_notification_callback(ref='ref1'), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - resp2 = client.post( - path='/notifications/email/ses', - data=ses_notification_callback(ref='ref2'), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - resp3 = client.post( - path='/notifications/email/ses', - data=ses_notification_callback(ref='ref3'), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - - assert resp1.status_code == 200 - assert resp2.status_code == 200 - assert resp3.status_code == 200 - - -def test_ses_callback_should_set_status_to_temporary_failure(notify_api, - notify_db, - notify_db_session, - sample_email_template): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - notification = create_sample_notification( - notify_db, - notify_db_session, - template=sample_email_template, - reference='ref', - status='sending', - sent_at=datetime.utcnow() - ) - assert get_notification_by_id(notification.id).status == 'sending' - - response = client.post( - path='/notifications/email/ses', - data=ses_soft_bounce_callback(), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert json_resp['result'] == 'success' - assert json_resp['message'] == 'SES callback succeeded' - assert get_notification_by_id(notification.id).status == 'temporary-failure' - - -def test_ses_callback_should_not_set_status_once_status_is_delivered(notify_api, - notify_db, - notify_db_session, - sample_email_template): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - notification = create_sample_notification( - notify_db, - notify_db_session, - template=sample_email_template, - reference='ref', - status='delivered', - sent_at=datetime.utcnow() - ) - - assert get_notification_by_id(notification.id).status == 'delivered' - - response = client.post( - path='/notifications/email/ses', - data=ses_soft_bounce_callback(), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 404 - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'SES callback failed: notification either not found or already updated from sending. Status temporary-failure' # noqa - assert get_notification_by_id(notification.id).status == 'delivered' - - -def test_ses_callback_should_set_status_to_permanent_failure(notify_api, - notify_db, - notify_db_session, - sample_email_template): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - notification = create_sample_notification( - notify_db, - notify_db_session, - template=sample_email_template, - reference='ref', - status='sending', - sent_at=datetime.utcnow() - ) - - assert get_notification_by_id(notification.id).status == 'sending' - - response = client.post( - path='/notifications/email/ses', - data=ses_hard_bounce_callback(), - headers=[('Content-Type', 'text/plain; charset=UTF-8')] - ) - json_resp = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - assert json_resp['result'] == 'success' - assert json_resp['message'] == 'SES callback succeeded' - assert get_notification_by_id(notification.id).status == 'permanent-failure' - - -def test_process_mmg_response_records_statsd(notify_db, notify_db_session, notify_api, mocker): - with notify_api.test_client() as client: - with freeze_time('2001-01-01T12:00:00'): - - mocker.patch('app.statsd_client.incr') - mocker.patch('app.statsd_client.timing_with_dates') - notification = create_sample_notification( - notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() - ) - - data = json.dumps({"reference": "mmg_reference", - "CID": str(notification.id), - "MSISDN": "447777349060", - "status": "3", - "deliverytime": "2016-04-05 16:01:07"}) - - client.post(path='notifications/sms/mmg', - data=data, - headers=[('Content-Type', 'application/json')]) - - app.statsd_client.incr.assert_any_call("callback.mmg.delivered") - app.statsd_client.timing_with_dates.assert_any_call( - "callback.mmg.elapsed-time", datetime.utcnow(), notification.sent_at - ) - - -def test_firetext_callback_should_record_statsd(notify_api, notify_db, notify_db_session, mocker): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - with freeze_time('2001-01-01T12:00:00'): - - mocker.patch('app.statsd_client.incr') - mocker.patch('app.statsd_client.timing_with_dates') - notification = create_sample_notification( - notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow() - ) - - client.post( - path='/notifications/sms/firetext', - data='mobile=441234123123&status=0&time=2016-03-10 14:17:00&code=101&reference={}'.format( - notification.id - ), - headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - - app.statsd_client.timing_with_dates.assert_any_call( - "callback.firetext.elapsed-time", datetime.utcnow(), notification.sent_at - ) - app.statsd_client.incr.assert_any_call("callback.firetext.delivered") + app.statsd_client.incr.assert_any_call("callback.firetext.delivered") def ses_notification_callback(ref='ref'):