diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py new file mode 100644 index 000000000..c82dc519e --- /dev/null +++ b/app/notifications/process_notifications.py @@ -0,0 +1,14 @@ +def persist_notification(): + ''' + persist the notification + :return: + ''' + pass + + +def send_notificaiton_to_queue(): + ''' + send the notification to the queue + :return: + ''' + pass diff --git a/app/notifications/validators.py b/app/notifications/validators.py new file mode 100644 index 000000000..47bc847ba --- /dev/null +++ b/app/notifications/validators.py @@ -0,0 +1,25 @@ +from app.dao import services_dao +from app.errors import InvalidRequest +from app.models import KEY_TYPE_TEST + + +def check_service_message_limit(key_type, service): + if all((key_type != KEY_TYPE_TEST, + service.restricted)): + service_stats = services_dao.fetch_todays_total_message_count(service.id) + if service_stats >= service.message_limit: + error = 'Exceeded send limits ({}) for today'.format(service.message_limit) + + raise InvalidRequest(error, status_code=429) + + +def check_template_is_for_notification_type(notification_type, template_type): + if notification_type != template_type: + raise InvalidRequest("{0} template is not suitable for {1} notification".format(template_type, + notification_type), + status_code=400) + + +def check_template_is_active(template): + if template.archived: + raise InvalidRequest('Template has been deleted', status_code=400) diff --git a/app/schema_validation/__init__.py b/app/schema_validation/__init__.py index 98f308637..84144601b 100644 --- a/app/schema_validation/__init__.py +++ b/app/schema_validation/__init__.py @@ -23,4 +23,4 @@ def build_error_message(errors, schema): "fields": fields } - return json.dumps(message) \ No newline at end of file + return json.dumps(message) diff --git a/app/v2/__init__.py b/app/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/v2/errors.py b/app/v2/errors.py new file mode 100644 index 000000000..01577b456 --- /dev/null +++ b/app/v2/errors.py @@ -0,0 +1,29 @@ +from flask import jsonify + +from app.errors import InvalidRequest + + +class BadRequestError(Exception): + status_code = 400 + + def __init__(self, message, fields, code): + self.code = code + self.message = message + self.fields = fields + + +def register_errors(blueprint): + @blueprint.app_errorhandler(Exception) + def authentication_error(error): + # v2 error format - NOT this + return jsonify(result='error', message=error.message), error.code + + @blueprint.app_errorhandler(InvalidRequest) + def handle_invalid_request(error): + # { + # "code", + # "link", + # "message" , + # "fields": + # } + return "build_error_message" diff --git a/app/v2/notifications/__init__.py b/app/v2/notifications/__init__.py new file mode 100644 index 000000000..f805256c0 --- /dev/null +++ b/app/v2/notifications/__init__.py @@ -0,0 +1,7 @@ +from flask import Blueprint + +from app.v2.errors import register_errors + +notification_blueprint = Blueprint(__name__, __name__, url_prefix='/v2/notifications') + +register_errors(notification_blueprint) diff --git a/app/v2/notifications/get_notifications.py b/app/v2/notifications/get_notifications.py new file mode 100644 index 000000000..b2fee08e0 --- /dev/null +++ b/app/v2/notifications/get_notifications.py @@ -0,0 +1,14 @@ +from app.v2.notifications import notification_blueprint + + +@notification_blueprint.route("/", methods=['GET']) +def get_notification_by_id(id): + pass + + +@notification_blueprint.route("/", methods=['GET']) +def get_notifications(): + # validate notifications request arguments + # fetch all notifications + # return notifications_response schema + pass diff --git a/app/v2/notifications/post_notifications.py b/app/v2/notifications/post_notifications.py new file mode 100644 index 000000000..130716fbd --- /dev/null +++ b/app/v2/notifications/post_notifications.py @@ -0,0 +1,49 @@ +from app.v2.notifications import notification_blueprint + + +@notification_blueprint.route('/sms', methods=['POST']) +def post_sms_notification(): + # # get service + # service = services_dao.dao_fetch_service_by_id(api_user.service_id) + # # validate input against json schema (not marshmallow) + # form = validate(request.get_json(), post_sms_request) + # + # # following checks will be in a common function for all versions of the endpoint. + # # check service has not exceeded the sending limit + # check_service_message_limit(api_user.key_type, service) + # template = templates_dao.dao_get_template_by_id_and_service_id( + # template_id=form['template_id'], + # service_id=service.id + # ) + # # check template is for sms + # check_template_is_for_notification_type(SMS_TYPE, template.template_type) + # # check template is not archived + # check_template_is_active(template) + # # check service is allowed to send + # service_can_send_to_recipient(form['phone_number'], api_user.key_type, service) + # # create body of message (create_template_object_for_notification) + # create_template_object_for_notification(template, form.get('personalisation', {})) + # # persist notification + # # send sms to provider queue for research mode queue + # + + # validate post form against post_sms_request schema + # validate service + # validate template + # create content + # persist notification + # send notification to queue + # return post_sms_response schema + return "post_sms_response schema", 201 + + +@notification_blueprint.route('/email', methods=['POST']) +def post_email_notification(): + # validate post form against post_email_request schema + # validate service + # validate template + # persist notification + # send notification to queue + # create content + # return post_email_response schema + pass diff --git a/tests/app/notifications/test_validators.py b/tests/app/notifications/test_validators.py new file mode 100644 index 000000000..374b31765 --- /dev/null +++ b/tests/app/notifications/test_validators.py @@ -0,0 +1,60 @@ +import pytest + +from app.errors import InvalidRequest +from app.notifications.validators import check_service_message_limit, check_template_is_for_notification_type, \ + check_template_is_active +from tests.app.conftest import (sample_notification as create_notification, + sample_service as create_service) + + +@pytest.mark.parametrize('key_type', ['test', 'team', 'live']) +def test_check_service_message_limit_with_unrestricted_service_passes(key_type, + sample_service, + sample_notification): + assert check_service_message_limit(key_type, sample_service) is None + + +@pytest.mark.parametrize('key_type', ['test', 'team', 'live']) +def test_check_service_message_limit_under_message_limit_passes(key_type, + sample_service, + sample_notification): + assert check_service_message_limit(key_type, sample_service) is None + + +@pytest.mark.parametrize('key_type', ['team', 'live']) +def test_check_service_message_limit_over_message_limit_fails(key_type, notify_db, notify_db_session): + service = create_service(notify_db, notify_db_session, restricted=True, limit=4) + for x in range(5): + create_notification(notify_db, notify_db_session, service=service) + with pytest.raises(InvalidRequest): + check_service_message_limit(key_type, service) + + +@pytest.mark.parametrize('template_type, notification_type', + [('email', 'email'), + ('sms', 'sms')]) +def test_check_template_is_for_notification_type_pass(template_type, notification_type): + assert check_template_is_for_notification_type(notification_type=notification_type, + template_type=template_type) is None + + +@pytest.mark.parametrize('template_type, notification_type', + [('sms', 'email'), + ('email', 'sms')]) +def test_check_template_is_for_notification_type_fails_when_template_type_does_not_match_notification_type( + template_type, notification_type): + with pytest.raises(InvalidRequest): + check_template_is_for_notification_type(notification_type=notification_type, + template_type=template_type) + + +def test_check_template_is_active_passes(sample_template): + assert check_template_is_active(sample_template) is None + + +def test_check_template_is_active_passes(sample_template): + sample_template.archived = True + from app.dao.templates_dao import dao_update_template + dao_update_template(sample_template) + with pytest.raises(InvalidRequest): + check_template_is_active(sample_template)