From 0b6f1d818bf3ad68e2e6d0de2d1f84a2ecac43de Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Thu, 24 Mar 2022 15:41:00 +0000 Subject: [PATCH 1/5] Remove redundant debug logs for providers We don't log at this level and can always add them in manually if we need to debug something locally. --- app/notifications/notifications_sms_callback.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/app/notifications/notifications_sms_callback.py b/app/notifications/notifications_sms_callback.py index e0111ea5a..f0896d745 100644 --- a/app/notifications/notifications_sms_callback.py +++ b/app/notifications/notifications_sms_callback.py @@ -1,4 +1,4 @@ -from flask import Blueprint, current_app, json, jsonify, request +from flask import Blueprint, json, jsonify, request from app.celery.process_sms_client_response_tasks import ( process_sms_client_response, @@ -30,12 +30,6 @@ def process_mmg_response(): queue=QueueNames.SMS_CALLBACKS, ) - safe_to_log = data.copy() - safe_to_log.pop("MSISDN") - current_app.logger.debug( - f"Full delivery response from {client_name} for notification: {provider_reference}\n{safe_to_log}" - ) - return jsonify(result='success'), 200 @@ -52,12 +46,6 @@ def process_firetext_response(): detailed_status_code = request.form.get('code') provider_reference = request.form.get('reference') - safe_to_log = dict(request.form).copy() - safe_to_log.pop('mobile') - current_app.logger.debug( - f"Full delivery response from {client_name} for notification: {provider_reference}\n{safe_to_log}" - ) - process_sms_client_response.apply_async( [status, provider_reference, client_name, detailed_status_code], queue=QueueNames.SMS_CALLBACKS, From 32434999f5158c6d9c08c668e50e0e3d533c14c2 Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Thu, 24 Mar 2022 15:50:06 +0000 Subject: [PATCH 2/5] Remove redundant header in provider test data X-Forwarded-For isn't used for anything since [^1]. [^1]: https://github.com/alphagov/notifications-api/pull/1315 --- tests/app/notifications/rest/test_callbacks.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/app/notifications/rest/test_callbacks.py b/tests/app/notifications/rest/test_callbacks.py index 2b4dc2a60..26fc353b7 100644 --- a/tests/app/notifications/rest/test_callbacks.py +++ b/tests/app/notifications/rest/test_callbacks.py @@ -8,20 +8,14 @@ def firetext_post(client, data): return client.post( path='/notifications/sms/firetext', data=data, - headers=[ - ('Content-Type', 'application/x-www-form-urlencoded'), - ('X-Forwarded-For', '203.0.113.195, 70.41.3.18, 150.172.238.178') # fake IPs - ]) + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) def mmg_post(client, data): return client.post( path='/notifications/sms/mmg', data=data, - headers=[ - ('Content-Type', 'application/json'), - ('X-Forwarded-For', '203.0.113.195, 70.41.3.18, 150.172.238.178') # fake IPs - ]) + headers=[('Content-Type', 'application/json')]) def dvla_post(client, data): From 5bab84144ae015deb27fd73b94995f4556db5c1e Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Thu, 24 Mar 2022 16:10:41 +0000 Subject: [PATCH 3/5] Split and rename tests for SMS / letter callbacks These were hard to find by searching as the filename doesn't refer to SMS or letters. Ideally our tests should be organised to reflect the structure of files in app/, so this does that. --- .../test_notifications_letter_callbacks.py | 99 +++++++++++++++++++ ...py => test_notifications_sms_callbacks.py} | 98 ------------------ 2 files changed, 99 insertions(+), 98 deletions(-) create mode 100644 tests/app/notifications/test_notifications_letter_callbacks.py rename tests/app/notifications/{rest/test_callbacks.py => test_notifications_sms_callbacks.py} (54%) diff --git a/tests/app/notifications/test_notifications_letter_callbacks.py b/tests/app/notifications/test_notifications_letter_callbacks.py new file mode 100644 index 000000000..c0d1a1321 --- /dev/null +++ b/tests/app/notifications/test_notifications_letter_callbacks.py @@ -0,0 +1,99 @@ +import pytest +from flask import json + + +def dvla_post(client, data): + return client.post( + path='/notifications/letter/dvla', + data=data, + headers=[('Content-Type', 'application/json')] + ) + + +def test_dvla_callback_returns_400_with_invalid_request(client): + data = json.dumps({"foo": "bar"}) + response = dvla_post(client, data) + assert response.status_code == 400 + + +def test_dvla_callback_autoconfirms_subscription(client, mocker): + autoconfirm_mock = mocker.patch('app.notifications.notifications_letter_callback.autoconfirm_subscription') + + data = _sns_confirmation_callback() + response = dvla_post(client, data) + assert response.status_code == 200 + assert autoconfirm_mock.called + + +def test_dvla_callback_autoconfirm_does_not_call_update_letter_notifications_task(client, mocker): + autoconfirm_mock = mocker.patch('app.notifications.notifications_letter_callback.autoconfirm_subscription') + update_task = \ + mocker.patch('app.notifications.notifications_letter_callback.update_letter_notifications_statuses.apply_async') + + data = _sns_confirmation_callback() + response = dvla_post(client, data) + + assert response.status_code == 200 + assert autoconfirm_mock.called + assert not update_task.called + + +def test_dvla_callback_calls_does_not_update_letter_notifications_task_with_invalid_file_type(client, mocker): + update_task = \ + mocker.patch('app.notifications.notifications_letter_callback.update_letter_notifications_statuses.apply_async') + + data = _sample_sns_s3_callback("bar.txt") + response = dvla_post(client, data) + + assert response.status_code == 200 + assert not update_task.called + + +@pytest.mark.parametrize("filename", + ['Notify-20170411153023-rs.txt', 'Notify-20170411153023-rsp.txt']) +def test_dvla_rs_and_rsp_txt_file_callback_calls_update_letter_notifications_task(client, mocker, filename): + update_task = mocker.patch( + 'app.notifications.notifications_letter_callback.update_letter_notifications_statuses.apply_async') + daily_sorted_counts_task = mocker.patch( + 'app.notifications.notifications_letter_callback.record_daily_sorted_counts.apply_async') + data = _sample_sns_s3_callback(filename) + response = dvla_post(client, data) + + assert response.status_code == 200 + assert update_task.called + update_task.assert_called_with([filename], queue='notify-internal-tasks') + daily_sorted_counts_task.assert_called_with([filename], queue='notify-internal-tasks') + + +def test_dvla_ack_calls_does_not_call_letter_notifications_task(client, mocker): + update_task = mocker.patch( + 'app.notifications.notifications_letter_callback.update_letter_notifications_statuses.apply_async') + daily_sorted_counts_task = mocker.patch( + 'app.notifications.notifications_letter_callback.record_daily_sorted_counts.apply_async') + data = _sample_sns_s3_callback('bar.ack.txt') + response = dvla_post(client, data) + + assert response.status_code == 200 + update_task.assert_not_called() + daily_sorted_counts_task.assert_not_called() + + +def _sample_sns_s3_callback(filename): + message_contents = '''{"Records":[{"eventVersion":"2.0","eventSource":"aws:s3","awsRegion":"eu-west-1","eventTime":"2017-05-16T11:38:41.073Z","eventName":"ObjectCreated:Put","userIdentity":{"principalId":"some-p-id"},"requestParameters":{"sourceIPAddress":"8.8.8.8"},"responseElements":{"x-amz-request-id":"some-r-id","x-amz-id-2":"some-x-am-id"},"s3":{"s3SchemaVersion":"1.0","configurationId":"some-c-id","bucket":{"name":"some-bucket","ownerIdentity":{"principalId":"some-p-id"},"arn":"some-bucket-arn"}, + "object":{"key":"%s"}}}]}''' % (filename) # noqa + return json.dumps({ + "SigningCertURL": "foo.pem", + "UnsubscribeURL": "bar", + "Signature": "some-signature", + "Type": "Notification", + "Timestamp": "2016-05-03T08:35:12.884Z", + "SignatureVersion": "1", + "MessageId": "6adbfe0a-d610-509a-9c47-af894e90d32d", + "Subject": "Amazon S3 Notification", + "TopicArn": "sample-topic-arn", + "Message": message_contents + }) + + +def _sns_confirmation_callback(): + return b'{\n "Type": "SubscriptionConfirmation",\n "MessageId": "165545c9-2a5c-472c-8df2-7ff2be2b3b1b",\n "Token": "2336412f37fb687f5d51e6e241d09c805a5a57b30d712f794cc5f6a988666d92768dd60a747ba6f3beb71854e285d6ad02428b09ceece29417f1f02d609c582afbacc99c583a916b9981dd2728f4ae6fdb82efd087cc3b7849e05798d2d2785c03b0879594eeac82c01f235d0e717736",\n "TopicArn": "arn:aws:sns:us-west-2:123456789012:MyTopic",\n "Message": "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:123456789012:MyTopic.\\nTo confirm the subscription, visit the SubscribeURL included in this message.",\n "SubscribeURL": "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37fb687f5d51e6e241d09c805a5a57b30d712f794cc5f6a988666d92768dd60a747ba6f3beb71854e285d6ad02428b09ceece29417f1f02d609c582afbacc99c583a916b9981dd2728f4ae6fdb82efd087cc3b7849e05798d2d2785c03b0879594eeac82c01f235d0e717736",\n "Timestamp": "2012-04-26T20:45:04.751Z",\n "SignatureVersion": "1",\n "Signature": "EXAMPLEpH+DcEwjAPg8O9mY8dReBSwksfg2S7WKQcikcNKWLQjwu6A4VbeS0QHVCkhRS7fUQvi2egU3N858fiTDN6bkkOxYDVrY0Ad8L10Hs3zH81mtnPk5uvvolIC1CXGu43obcgFxeL3khZl8IKvO61GWB6jI9b5+gLPoBc1Q=",\n "SigningCertURL": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"\n}' # noqa diff --git a/tests/app/notifications/rest/test_callbacks.py b/tests/app/notifications/test_notifications_sms_callbacks.py similarity index 54% rename from tests/app/notifications/rest/test_callbacks.py rename to tests/app/notifications/test_notifications_sms_callbacks.py index 26fc353b7..b3bb7b46e 100644 --- a/tests/app/notifications/rest/test_callbacks.py +++ b/tests/app/notifications/test_notifications_sms_callbacks.py @@ -1,4 +1,3 @@ -import pytest from flask import json from app.notifications.notifications_sms_callback import validate_callback_data @@ -18,82 +17,6 @@ def mmg_post(client, data): headers=[('Content-Type', 'application/json')]) -def dvla_post(client, data): - return client.post( - path='/notifications/letter/dvla', - data=data, - headers=[('Content-Type', 'application/json')] - ) - - -def test_dvla_callback_returns_400_with_invalid_request(client): - data = json.dumps({"foo": "bar"}) - response = dvla_post(client, data) - assert response.status_code == 400 - - -def test_dvla_callback_autoconfirms_subscription(client, mocker): - autoconfirm_mock = mocker.patch('app.notifications.notifications_letter_callback.autoconfirm_subscription') - - data = _sns_confirmation_callback() - response = dvla_post(client, data) - assert response.status_code == 200 - assert autoconfirm_mock.called - - -def test_dvla_callback_autoconfirm_does_not_call_update_letter_notifications_task(client, mocker): - autoconfirm_mock = mocker.patch('app.notifications.notifications_letter_callback.autoconfirm_subscription') - update_task = \ - mocker.patch('app.notifications.notifications_letter_callback.update_letter_notifications_statuses.apply_async') - - data = _sns_confirmation_callback() - response = dvla_post(client, data) - - assert response.status_code == 200 - assert autoconfirm_mock.called - assert not update_task.called - - -def test_dvla_callback_calls_does_not_update_letter_notifications_task_with_invalid_file_type(client, mocker): - update_task = \ - mocker.patch('app.notifications.notifications_letter_callback.update_letter_notifications_statuses.apply_async') - - data = _sample_sns_s3_callback("bar.txt") - response = dvla_post(client, data) - - assert response.status_code == 200 - assert not update_task.called - - -@pytest.mark.parametrize("filename", - ['Notify-20170411153023-rs.txt', 'Notify-20170411153023-rsp.txt']) -def test_dvla_rs_and_rsp_txt_file_callback_calls_update_letter_notifications_task(client, mocker, filename): - update_task = mocker.patch( - 'app.notifications.notifications_letter_callback.update_letter_notifications_statuses.apply_async') - daily_sorted_counts_task = mocker.patch( - 'app.notifications.notifications_letter_callback.record_daily_sorted_counts.apply_async') - data = _sample_sns_s3_callback(filename) - response = dvla_post(client, data) - - assert response.status_code == 200 - assert update_task.called - update_task.assert_called_with([filename], queue='notify-internal-tasks') - daily_sorted_counts_task.assert_called_with([filename], queue='notify-internal-tasks') - - -def test_dvla_ack_calls_does_not_call_letter_notifications_task(client, mocker): - update_task = mocker.patch( - 'app.notifications.notifications_letter_callback.update_letter_notifications_statuses.apply_async') - daily_sorted_counts_task = mocker.patch( - 'app.notifications.notifications_letter_callback.record_daily_sorted_counts.apply_async') - data = _sample_sns_s3_callback('bar.ack.txt') - response = dvla_post(client, data) - - assert response.status_code == 200 - update_task.assert_not_called() - daily_sorted_counts_task.assert_not_called() - - def test_firetext_callback_should_not_need_auth(client, mocker): mocker.patch('app.notifications.notifications_sms_callback.process_sms_client_response') data = 'mobile=441234123123&status=0&reference=notification_id&time=2016-03-10 14:17:00' @@ -249,24 +172,3 @@ def test_validate_callback_data_returns_error_for_empty_string(): result = validate_callback_data(form, fields, client_name) assert result is not None assert "{} callback failed: {} missing".format(client_name, 'status') in result - - -def _sample_sns_s3_callback(filename): - message_contents = '''{"Records":[{"eventVersion":"2.0","eventSource":"aws:s3","awsRegion":"eu-west-1","eventTime":"2017-05-16T11:38:41.073Z","eventName":"ObjectCreated:Put","userIdentity":{"principalId":"some-p-id"},"requestParameters":{"sourceIPAddress":"8.8.8.8"},"responseElements":{"x-amz-request-id":"some-r-id","x-amz-id-2":"some-x-am-id"},"s3":{"s3SchemaVersion":"1.0","configurationId":"some-c-id","bucket":{"name":"some-bucket","ownerIdentity":{"principalId":"some-p-id"},"arn":"some-bucket-arn"}, - "object":{"key":"%s"}}}]}''' % (filename) # noqa - return json.dumps({ - "SigningCertURL": "foo.pem", - "UnsubscribeURL": "bar", - "Signature": "some-signature", - "Type": "Notification", - "Timestamp": "2016-05-03T08:35:12.884Z", - "SignatureVersion": "1", - "MessageId": "6adbfe0a-d610-509a-9c47-af894e90d32d", - "Subject": "Amazon S3 Notification", - "TopicArn": "sample-topic-arn", - "Message": message_contents - }) - - -def _sns_confirmation_callback(): - return b'{\n "Type": "SubscriptionConfirmation",\n "MessageId": "165545c9-2a5c-472c-8df2-7ff2be2b3b1b",\n "Token": "2336412f37fb687f5d51e6e241d09c805a5a57b30d712f794cc5f6a988666d92768dd60a747ba6f3beb71854e285d6ad02428b09ceece29417f1f02d609c582afbacc99c583a916b9981dd2728f4ae6fdb82efd087cc3b7849e05798d2d2785c03b0879594eeac82c01f235d0e717736",\n "TopicArn": "arn:aws:sns:us-west-2:123456789012:MyTopic",\n "Message": "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:123456789012:MyTopic.\\nTo confirm the subscription, visit the SubscribeURL included in this message.",\n "SubscribeURL": "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37fb687f5d51e6e241d09c805a5a57b30d712f794cc5f6a988666d92768dd60a747ba6f3beb71854e285d6ad02428b09ceece29417f1f02d609c582afbacc99c583a916b9981dd2728f4ae6fdb82efd087cc3b7849e05798d2d2785c03b0879594eeac82c01f235d0e717736",\n "Timestamp": "2012-04-26T20:45:04.751Z",\n "SignatureVersion": "1",\n "Signature": "EXAMPLEpH+DcEwjAPg8O9mY8dReBSwksfg2S7WKQcikcNKWLQjwu6A4VbeS0QHVCkhRS7fUQvi2egU3N858fiTDN6bkkOxYDVrY0Ad8L10Hs3zH81mtnPk5uvvolIC1CXGu43obcgFxeL3khZl8IKvO61GWB6jI9b5+gLPoBc1Q=",\n "SigningCertURL": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"\n}' # noqa From a962721915da36bf304860e5085e864a8179c713 Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Thu, 24 Mar 2022 16:17:03 +0000 Subject: [PATCH 4/5] Move SMS client response tests to match their app/ This makes them easier to find and removes ambiguity with other files related to "processing" in the app. --- .../test_process_sms_client_response_tasks.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/app/{notifications/test_process_client_response.py => celery/test_process_sms_client_response_tasks.py} (100%) diff --git a/tests/app/notifications/test_process_client_response.py b/tests/app/celery/test_process_sms_client_response_tasks.py similarity index 100% rename from tests/app/notifications/test_process_client_response.py rename to tests/app/celery/test_process_sms_client_response_tasks.py From b439fd071878d8b1bea1d65082fd1b2dd665fc9a Mon Sep 17 00:00:00 2001 From: Ben Thorner Date: Thu, 24 Mar 2022 16:41:08 +0000 Subject: [PATCH 5/5] Add boilerplate for Reach SMS callbacks This is enough to update a notification in DB: 1. First create a notification in the UI and sent it. 2. Then reset its attributes to pretend it's for Reach. update notifications set sent_at = null, sent_by = null, notification_status='sending' where id='some-uuid'; 3. Change "notification_id" to "" in the code. 4. Call the boilerplate endpoint for Reach callbacks. curl -X POST localhost:6011/notifications/sms/reach Interestingly there's no foreign key constraint on "sent_by" in the DB, so this just works: the notification is updated. --- .../process_sms_client_response_tasks.py | 4 ++- app/clients/sms/reach.py | 25 +++++++++++++++++++ .../notifications_sms_callback.py | 22 ++++++++++++++++ manifest.yml.j2 | 6 ++--- .../test_process_sms_client_response_tasks.py | 5 +++- tests/app/clients/test_reach.py | 1 + .../test_notifications_sms_callbacks.py | 25 +++++++++++++++++++ 7 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 app/clients/sms/reach.py create mode 100644 tests/app/clients/test_reach.py diff --git a/app/celery/process_sms_client_response_tasks.py b/app/celery/process_sms_client_response_tasks.py index e16175646..b8f1f63bb 100644 --- a/app/celery/process_sms_client_response_tasks.py +++ b/app/celery/process_sms_client_response_tasks.py @@ -8,6 +8,7 @@ from app import notify_celery, statsd_client from app.clients import ClientException from app.clients.sms.firetext import get_firetext_responses from app.clients.sms.mmg import get_mmg_responses +from app.clients.sms.reach import get_reach_responses from app.dao import notifications_dao from app.dao.templates_dao import dao_get_template_by_id from app.models import NOTIFICATION_PENDING @@ -17,7 +18,8 @@ from app.notifications.notifications_ses_callback import ( sms_response_mapper = { 'MMG': get_mmg_responses, - 'Firetext': get_firetext_responses + 'Firetext': get_firetext_responses, + 'Reach': get_reach_responses } diff --git a/app/clients/sms/reach.py b/app/clients/sms/reach.py new file mode 100644 index 000000000..d9b016e2a --- /dev/null +++ b/app/clients/sms/reach.py @@ -0,0 +1,25 @@ +from app.clients.sms import SmsClient, SmsClientResponseException + + +def get_reach_responses(status, detailed_status_code=None): + if status == 'TODO-d': + return ("delivered", "TODO: Delivered") + elif status == 'TODO-tf': + return ("temporary-failure", "TODO: Temporary failure") + elif status == 'TODO-pf': + return ("permanent-failure", "TODO: Permanent failure") + else: + raise KeyError + + +class ReachClientResponseException(SmsClientResponseException): + pass # TODO (custom exception for errors) + + +class ReachClient(SmsClient): + + def get_name(self): + pass # TODO + + def send_sms(self, to, content, reference, international, multi=True, sender=None): + pass # TODO diff --git a/app/notifications/notifications_sms_callback.py b/app/notifications/notifications_sms_callback.py index f0896d745..f7353d45c 100644 --- a/app/notifications/notifications_sms_callback.py +++ b/app/notifications/notifications_sms_callback.py @@ -54,6 +54,28 @@ def process_firetext_response(): return jsonify(result='success'), 200 +@sms_callback_blueprint.route('/reach', methods=['POST']) +def process_reach_response(): + client_name = 'Reach' + + # TODO: validate request + errors = None + + if errors: + raise InvalidRequest(errors, status_code=400) + + status = 'TODO-d' # TODO + detailed_status_code = 'something' # TODO + provider_reference = 'notification_id' # TODO + + process_sms_client_response.apply_async( + [status, provider_reference, client_name, detailed_status_code], + queue=QueueNames.SMS_CALLBACKS, + ) + + return jsonify(result='success'), 200 + + def validate_callback_data(data, fields, client_name): errors = [] for f in fields: diff --git a/manifest.yml.j2 b/manifest.yml.j2 index 3560f7b32..dbd267fd3 100644 --- a/manifest.yml.j2 +++ b/manifest.yml.j2 @@ -27,9 +27,9 @@ 'STATSD_HOST': None }, 'routes': { - 'preview': ['api.notify.works/notifications/sms/mmg', 'api.notify.works/notifications/sms/firetext'], - 'staging': ['api.staging-notify.works/notifications/sms/mmg', 'api.staging-notify.works/notifications/sms/firetext'], - 'production': ['api.notifications.service.gov.uk/notifications/sms/mmg', 'api.notifications.service.gov.uk/notifications/sms/firetext'], + 'preview': ['api.notify.works/notifications/sms/mmg', 'api.notify.works/notifications/sms/firetext', 'api.notify.works/notifications/sms/reach'], + 'staging': ['api.staging-notify.works/notifications/sms/mmg', 'api.staging-notify.works/notifications/sms/firetext', 'api.staging-notify.works/notifications/sms/reach'], + 'production': ['api.notifications.service.gov.uk/notifications/sms/mmg', 'api.notifications.service.gov.uk/notifications/sms/firetext', 'api.notifications.service.gov.uk/notifications/sms/reach'], }, 'health-check-type': 'port', 'health-check-invocation-timeout': 3, diff --git a/tests/app/celery/test_process_sms_client_response_tasks.py b/tests/app/celery/test_process_sms_client_response_tasks.py index 6005fbce3..046566906 100644 --- a/tests/app/celery/test_process_sms_client_response_tasks.py +++ b/tests/app/celery/test_process_sms_client_response_tasks.py @@ -18,7 +18,7 @@ def test_process_sms_client_response_raises_error_if_reference_is_not_a_valid_uu status='000', provider_reference='something-bad', client_name='sms-client') -@pytest.mark.parametrize('client_name', ('Firetext', 'MMG')) +@pytest.mark.parametrize('client_name', ('Firetext', 'MMG', 'Reach')) def test_process_sms_response_raises_client_exception_for_unknown_status( sample_notification, mocker, @@ -43,6 +43,9 @@ def test_process_sms_response_raises_client_exception_for_unknown_status( ('3', '2', 'MMG', 'delivered', "Delivered to operator"), ('4', '27', 'MMG', 'temporary-failure', "Absent Subscriber"), ('5', '13', 'MMG', 'permanent-failure', "Sender id blacklisted"), + ('TODO-d', None, 'Reach', 'delivered', "TODO: Delivered"), + ('TODO-tf', None, 'Reach', 'temporary-failure', "TODO: Temporary failure"), + ('TODO-pf', None, 'Reach', 'permanent-failure', "TODO: Permanent failure"), ]) def test_process_sms_client_response_updates_notification_status( sample_notification, diff --git a/tests/app/clients/test_reach.py b/tests/app/clients/test_reach.py new file mode 100644 index 000000000..00cf4f034 --- /dev/null +++ b/tests/app/clients/test_reach.py @@ -0,0 +1 @@ +# TODO: all of the tests diff --git a/tests/app/notifications/test_notifications_sms_callbacks.py b/tests/app/notifications/test_notifications_sms_callbacks.py index b3bb7b46e..1c1a02f01 100644 --- a/tests/app/notifications/test_notifications_sms_callbacks.py +++ b/tests/app/notifications/test_notifications_sms_callbacks.py @@ -17,6 +17,13 @@ def mmg_post(client, data): headers=[('Content-Type', 'application/json')]) +def reach_post(client, data): + return client.post( + path='/notifications/sms/reach', + data=data, + headers=[('Content-Type', 'application/json')]) + + def test_firetext_callback_should_not_need_auth(client, mocker): mocker.patch('app.notifications.notifications_sms_callback.process_sms_client_response') data = 'mobile=441234123123&status=0&reference=notification_id&time=2016-03-10 14:17:00' @@ -135,6 +142,24 @@ def test_mmg_callback_should_return_200_and_call_task_with_valid_data(client, mo ) +# TODO: more tests about edge cases for this provider +def test_reach_callback_should_return_200_and_call_task_with_valid_data(client, mocker): + mock_celery = mocker.patch( + 'app.notifications.notifications_sms_callback.process_sms_client_response.apply_async') + data = json.dumps({"data": "TODO"}) + + response = reach_post(client, data) + + assert response.status_code == 200 + json_data = json.loads(response.data) + assert json_data['result'] == 'success' + + mock_celery.assert_called_once_with( + ['TODO-d', 'notification_id', 'Reach', 'something'], + queue='sms-callbacks', + ) + + def test_validate_callback_data_returns_none_when_valid(): form = {'status': 'good', 'reference': 'send-sms-code'}