diff --git a/app/notifications/notifications_letter_callback.py b/app/notifications/notifications_letter_callback.py index 3b4a0fbb3..c7bc80d9e 100644 --- a/app/notifications/notifications_letter_callback.py +++ b/app/notifications/notifications_letter_callback.py @@ -1,4 +1,5 @@ from datetime import datetime +from functools import wraps from flask import ( Blueprint, @@ -11,37 +12,52 @@ from flask import ( from app import statsd_client from app.celery.tasks import update_letter_notifications_statuses from app.clients.email.aws_ses import get_aws_responses -from app.dao import ( - notifications_dao -) - +from app.dao import notifications_dao +from app.v2.errors import register_errors from app.notifications.process_client_response import validate_callback_data +from app.notifications.utils import autoconfirm_subscription +from app.schema_validation import validate + letter_callback_blueprint = Blueprint('notifications_letter_callback', __name__) - -from app.errors import ( - register_errors, - InvalidRequest -) - register_errors(letter_callback_blueprint) +dvla_sns_callback_schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "sns callback received on s3 update", + "type": "object", + "title": "dvla internal sns callback", + "properties": { + "Type": {"enum": ["Notification", "SubscriptionConfirmation"]}, + "MessageId": {"type": "string"}, + "Message": {"type": ["string", "object"]} + }, + "required": ["Type", "MessageId", "Message"] +} + + +def validate_schema(schema): + def decorator(f): + @wraps(f) + def wrapper(*args, **kw): + validate(request.json, schema) + return f(*args, **kw) + return wrapper + return decorator + + @letter_callback_blueprint.route('/notifications/letter/dvla', methods=['POST']) +@validate_schema(dvla_sns_callback_schema) def process_letter_response(): - try: - req_json = json.loads(request.data) + req_json = request.json + if not autoconfirm_subscription(req_json): # The callback should have one record for an S3 Put Event. filename = req_json['Message']['Records'][0]['s3']['object']['key'] - - except (ValueError, KeyError): - error = "DVLA callback failed: Invalid JSON" - raise InvalidRequest(error, status_code=400) - - else: + current_app.logger.info('Received file from DVLA: {}'.format(filename)) current_app.logger.info('DVLA callback: Calling task to update letter notifications') update_letter_notifications_statuses.apply_async([filename], queue='notify') - return jsonify( - result="success", message="DVLA callback succeeded" - ), 200 + return jsonify( + result="success", message="DVLA callback succeeded" + ), 200 diff --git a/app/notifications/utils.py b/app/notifications/utils.py index 80346c475..5f1443f09 100644 --- a/app/notifications/utils.py +++ b/app/notifications/utils.py @@ -16,3 +16,10 @@ def confirm_subscription(confirmation_request): raise e return confirmation_request['TopicArn'] + + +def autoconfirm_subscription(req_json): + if req_json.get('Type') == 'SubscriptionConfirmation': + current_app.logger.info("SNS subscription confirmation url: {}".format(req_json['SubscribeURL'])) + subscribed_topic = confirm_subscription(req_json) + return subscribed_topic diff --git a/tests/app/notifications/rest/test_callbacks.py b/tests/app/notifications/rest/test_callbacks.py index 095384836..242f18a46 100644 --- a/tests/app/notifications/rest/test_callbacks.py +++ b/tests/app/notifications/rest/test_callbacks.py @@ -12,6 +12,7 @@ from app.dao.notifications_dao import ( get_notification_by_id ) from app.models import NotificationStatistics +from tests.app.notifications.test_notifications_ses_callback import ses_confirmation_callback from tests.app.conftest import sample_notification as create_sample_notification @@ -22,24 +23,41 @@ def test_dvla_callback_returns_400_with_invalid_request(client): data=data, headers=[('Content-Type', 'application/json')] ) + json_resp = json.loads(response.get_data(as_text=True)) assert response.status_code == 400 - assert json_resp['result'] == 'error' - assert json_resp['message'] == 'DVLA callback failed: Invalid JSON' -def test_dvla_callback_returns_200_with_valid_request(client, mocker): - data = _sample_sns_s3_callback() - mocker.patch('app.notifications.notifications_letter_callback.update_letter_notifications_statuses.apply_async') +def test_dvla_callback_autoconfirms_subscription(client, mocker): + autoconfirm_mock = mocker.patch('app.notifications.notifications_letter_callback.autoconfirm_subscription') + + data = ses_confirmation_callback() response = client.post( path='/notifications/letter/dvla', data=data, headers=[('Content-Type', 'application/json')] ) - json_resp = json.loads(response.get_data(as_text=True)) 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 = ses_confirmation_callback() + response = client.post( + path='/notifications/letter/dvla', + data=data, + headers=[('Content-Type', 'application/json')] + ) + + assert response.status_code == 200 + assert autoconfirm_mock.called + assert not update_task.called def test_dvla_callback_calls_update_letter_notifications_task(client, mocker): @@ -54,7 +72,7 @@ def test_dvla_callback_calls_update_letter_notifications_task(client, mocker): json_resp = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 - assert update_task.called is True + assert update_task.called update_task.assert_called_with(['bar.txt'], queue='notify')