diff --git a/app/__init__.py b/app/__init__.py index 0c2734f77..8d8e18270 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -95,6 +95,7 @@ def register_blueprint(application): from app.notifications.receive_notifications import receive_notifications_blueprint from app.notifications.notifications_ses_callback import ses_callback_blueprint from app.notifications.notifications_sms_callback import sms_callback_blueprint + from app.notifications.notifications_letter_callback import letter_callback_blueprint from app.authentication.auth import requires_admin_auth, requires_auth, requires_no_auth from app.letters.send_letter_jobs import letter_job @@ -155,6 +156,9 @@ def register_blueprint(application): letter_job.before_request(requires_admin_auth) application.register_blueprint(letter_job) + letter_callback_blueprint.before_request(requires_no_auth) + application.register_blueprint(letter_callback_blueprint) + def register_v2_blueprints(application): from app.v2.notifications.post_notifications import v2_notification_blueprint as post_notifications diff --git a/app/models.py b/app/models.py index a4bb1d5cf..44487c334 100644 --- a/app/models.py +++ b/app/models.py @@ -443,6 +443,7 @@ class ProviderDetails(db.Model): updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow) created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=True) created_by = db.relationship('User') + supports_international = db.Column(db.Boolean, nullable=False, default=False) class ProviderDetailsHistory(db.Model, HistoryModel): @@ -458,6 +459,7 @@ class ProviderDetailsHistory(db.Model, HistoryModel): updated_at = db.Column(db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow) created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=True) created_by = db.relationship('User') + supports_international = db.Column(db.Boolean, nullable=False, default=False) JOB_STATUS_PENDING = 'pending' @@ -664,6 +666,12 @@ class Notification(db.Model): foreign(template_version) == remote(TemplateHistory.version) )) + client_reference = db.Column(db.String, index=True, nullable=True) + + international = db.Column(db.Boolean, nullable=False, default=False) + phone_prefix = db.Column(db.String, nullable=True) + rate_multiplier = db.Column(db.Numeric(), nullable=True) + @property def personalisation(self): if self._personalisation: @@ -837,6 +845,10 @@ class NotificationHistory(db.Model, HistoryModel): reference = db.Column(db.String, nullable=True, index=True) client_reference = db.Column(db.String, nullable=True) + international = db.Column(db.Boolean, nullable=False, default=False) + phone_prefix = db.Column(db.String, nullable=True) + rate_multiplier = db.Column(db.Numeric(), nullable=True) + @classmethod def from_original(cls, notification): history = super().from_original(notification) diff --git a/app/notifications/notifications_letter_callback.py b/app/notifications/notifications_letter_callback.py new file mode 100644 index 000000000..f7c357055 --- /dev/null +++ b/app/notifications/notifications_letter_callback.py @@ -0,0 +1,39 @@ +from datetime import datetime + +from flask import ( + Blueprint, + jsonify, + request, + current_app, + json +) + +from app import statsd_client +from app.clients.email.aws_ses import get_aws_responses +from app.dao import ( + notifications_dao +) + +from app.notifications.process_client_response import validate_callback_data + +letter_callback_blueprint = Blueprint('notifications_letter_callback', __name__) + +from app.errors import ( + register_errors, + InvalidRequest +) + +register_errors(letter_callback_blueprint) + + +@letter_callback_blueprint.route('/notifications/letter/dvla', methods=['POST']) +def process_letter_response(): + try: + dvla_request = json.loads(request.data) + current_app.logger.info(dvla_request) + return jsonify( + result="success", message="DVLA callback succeeded" + ), 200 + except ValueError: + error = "DVLA callback failed: invalid json" + raise InvalidRequest(error, status_code=400) diff --git a/migrations/versions/0076_add_intl_flag_to_provider.py b/migrations/versions/0076_add_intl_flag_to_provider.py new file mode 100644 index 000000000..921bb85dd --- /dev/null +++ b/migrations/versions/0076_add_intl_flag_to_provider.py @@ -0,0 +1,27 @@ +"""empty message + +Revision ID: 0076_add_intl_flag_to_provider +Revises: 0075_create_rates_table +Create Date: 2017-04-25 09:44:13.194164 + +""" + +# revision identifiers, used by Alembic. +revision = '0076_add_intl_flag_to_provider' +down_revision = '0075_create_rates_table' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('provider_details', sa.Column('supports_international', sa.Boolean(), nullable=False, server_default=sa.false())) + op.add_column('provider_details_history', sa.Column('supports_international', sa.Boolean(), nullable=False, server_default=sa.false())) + + op.execute("UPDATE provider_details SET supports_international=True WHERE identifier='mmg'") + op.execute("UPDATE provider_details_history SET supports_international=True WHERE identifier='mmg'") + + +def downgrade(): + op.drop_column('provider_details_history', 'supports_international') + op.drop_column('provider_details', 'supports_international') diff --git a/migrations/versions/0077_add_intl_notification.py b/migrations/versions/0077_add_intl_notification.py new file mode 100644 index 000000000..0e1d513c5 --- /dev/null +++ b/migrations/versions/0077_add_intl_notification.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: 0077_add_intl_notification +Revises: 0076_add_intl_flag_to_provider +Create Date: 2017-04-25 11:34:43.229494 + +""" + +# revision identifiers, used by Alembic. +revision = '0077_add_intl_notification' +down_revision = '0076_add_intl_flag_to_provider' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('notification_history', sa.Column('international', sa.Boolean(), nullable=True)) + op.add_column('notification_history', sa.Column('phone_prefix', sa.String(), nullable=True)) + op.add_column('notification_history', sa.Column('rate_multiplier', sa.Numeric(), nullable=True)) + op.add_column('notifications', sa.Column('international', sa.Boolean(), nullable=True)) + op.add_column('notifications', sa.Column('phone_prefix', sa.String(), nullable=True)) + op.add_column('notifications', sa.Column('rate_multiplier', sa.Numeric(), nullable=True)) + + +def downgrade(): + op.drop_column('notifications', 'rate_multiplier') + op.drop_column('notifications', 'phone_prefix') + op.drop_column('notifications', 'international') + op.drop_column('notification_history', 'rate_multiplier') + op.drop_column('notification_history', 'phone_prefix') + op.drop_column('notification_history', 'international') diff --git a/tests/app/notifications/rest/test_callbacks.py b/tests/app/notifications/rest/test_callbacks.py index c24bb76d3..780076b34 100644 --- a/tests/app/notifications/rest/test_callbacks.py +++ b/tests/app/notifications/rest/test_callbacks.py @@ -13,6 +13,16 @@ from app.models import NotificationStatistics from tests.app.conftest import sample_notification as create_sample_notification +def test_dvla_callback_should_not_need_auth(client): + data = json.dumps({"somekey": "somevalue"}) + response = client.post( + path='/notifications/letter/dvla', + data=data, + headers=[('Content-Type', 'application/json')]) + + assert response.status_code == 200 + + def test_firetext_callback_should_not_need_auth(client, mocker): mocker.patch('app.statsd_client.incr') response = client.post( diff --git a/tests/app/notifications/test_process_notification.py b/tests/app/notifications/test_process_notification.py index 9e428a0ab..33ebdc727 100644 --- a/tests/app/notifications/test_process_notification.py +++ b/tests/app/notifications/test_process_notification.py @@ -169,18 +169,21 @@ def test_persist_notification_with_optionals(sample_job, sample_api_key, mocker) 'app.notifications.process_notifications.redis_store.get_all_from_hash') n_id = uuid.uuid4() created_at = datetime.datetime(2016, 11, 11, 16, 8, 18) - persist_notification(template_id=sample_job.template.id, - template_version=sample_job.template.version, - recipient='+447111111111', - service=sample_job.service, - personalisation=None, notification_type='sms', - api_key_id=sample_api_key.id, - key_type=sample_api_key.key_type, - created_at=created_at, - job_id=sample_job.id, - job_row_number=10, - client_reference="ref from client", - notification_id=n_id) + persist_notification( + template_id=sample_job.template.id, + template_version=sample_job.template.version, + recipient='+447111111111', + service=sample_job.service, + personalisation=None, + notification_type='sms', + api_key_id=sample_api_key.id, + key_type=sample_api_key.key_type, + created_at=created_at, + job_id=sample_job.id, + job_row_number=10, + client_reference="ref from client", + notification_id=n_id + ) assert Notification.query.count() == 1 assert NotificationHistory.query.count() == 1 persisted_notification = Notification.query.all()[0] @@ -192,6 +195,9 @@ def test_persist_notification_with_optionals(sample_job, sample_api_key, mocker) mock_service_template_cache.assert_called_once_with(cache_key_for_service_template_counter(sample_job.service_id)) assert persisted_notification.client_reference == "ref from client" assert persisted_notification.reference is None + assert persisted_notification.international is False + assert persisted_notification.phone_prefix is None + assert persisted_notification.rate_multiplier is None @freeze_time("2016-01-01 11:09:00.061258") diff --git a/tests/app/provider_details/test_rest.py b/tests/app/provider_details/test_rest.py index 15e2381be..36a40d2a0 100644 --- a/tests/app/provider_details/test_rest.py +++ b/tests/app/provider_details/test_rest.py @@ -47,7 +47,7 @@ def test_get_provider_details_contains_correct_fields(client, notify_db): allowed_keys = { "id", "created_by", "display_name", "identifier", "priority", 'notification_type', - "active", "version", "updated_at" + "active", "version", "updated_at", "supports_international" } assert allowed_keys == set(json_resp[0].keys()) @@ -116,7 +116,7 @@ def test_get_provider_versions_contains_correct_fields(client, notify_db): allowed_keys = { "id", "created_by", "display_name", "identifier", "priority", 'notification_type', - "active", "version", "updated_at" + "active", "version", "updated_at", "supports_international" } assert allowed_keys == set(json_resp[0].keys()) diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index 43edbc040..2cf666be4 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -224,6 +224,7 @@ def test_create_service(client, sample_user): assert json_resp['data']['name'] == 'created service' assert not json_resp['data']['research_mode'] assert not json_resp['data']['can_send_letters'] + assert not json_resp['data']['can_send_international_sms'] def test_should_not_create_service_with_missing_user_id_field(notify_api, fake_uuid): @@ -419,10 +420,12 @@ def test_update_service_flags(notify_api, sample_service): assert json_resp['data']['name'] == sample_service.name assert json_resp['data']['research_mode'] is False assert json_resp['data']['can_send_letters'] is False + assert json_resp['data']['can_send_international_sms'] is False data = { 'research_mode': True, - 'can_send_letters': True + 'can_send_letters': True, + 'can_send_international_sms': True, } auth_header = create_authorization_header() @@ -436,6 +439,7 @@ def test_update_service_flags(notify_api, sample_service): assert resp.status_code == 200 assert result['data']['research_mode'] is True assert result['data']['can_send_letters'] is True + assert result['data']['can_send_international_sms'] is True def test_update_service_research_mode_throws_validation_error(notify_api, sample_service):