From 22ffc56520a6427547fba529bd3d0c76da240d8d Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 30 May 2024 12:27:07 -0700 Subject: [PATCH] remove v2 --- app/__init__.py | 29 - app/celery/tasks.py | 2 +- app/errors.py | 43 + app/models.py | 4 +- app/notifications/process_notifications.py | 2 +- app/notifications/validators.py | 2 +- app/service/send_notification.py | 2 +- app/v2/__init__.py | 0 app/v2/errors.py | 125 -- app/v2/inbound_sms/__init__.py | 9 - app/v2/inbound_sms/get_inbound_sms.py | 46 - app/v2/inbound_sms/inbound_sms_schemas.py | 61 - app/v2/notifications/__init__.py | 9 - app/v2/notifications/create_response.py | 66 - app/v2/notifications/get_notifications.py | 88 - app/v2/notifications/notification_schemas.py | 211 --- app/v2/notifications/post_notifications.py | 338 ---- app/v2/template/__init__.py | 7 - app/v2/template/get_template.py | 22 - app/v2/template/post_template.py | 50 - app/v2/template/template_schemas.py | 84 - app/v2/templates/__init__.py | 7 - app/v2/templates/get_templates.py | 21 - app/v2/templates/templates_schemas.py | 24 - app/v2/utils.py | 14 - .../versions/0336_broadcast_msg_content_2.py | 3 +- notifications_utils/recipients.py | 4 +- notifications_utils/template.py | 5 +- poetry.lock | 8 +- .../test_process_notification.py | 2 +- tests/app/notifications/test_validators.py | 2 +- .../public_contracts/test_GET_notification.py | 46 +- .../test_send_notification.py | 3 +- .../test_send_one_off_notification.py | 2 +- tests/app/user/test_rest_verify.py | 8 +- tests/app/v2/__init__.py | 0 tests/app/v2/inbound_sms/__init__.py | 0 .../v2/inbound_sms/test_get_inbound_sms.py | 222 --- .../inbound_sms/test_inbound_sms_schemas.py | 99 -- tests/app/v2/notifications/__init__.py | 0 .../notifications/test_get_notifications.py | 742 --------- .../test_notification_schemas.py | 381 ----- .../notifications/test_post_notifications.py | 1431 ----------------- tests/app/v2/template/__init__.py | 0 tests/app/v2/template/test_get_template.py | 146 -- tests/app/v2/template/test_post_template.py | 222 --- .../app/v2/template/test_template_schemas.py | 159 -- tests/app/v2/templates/test_get_templates.py | 130 -- .../v2/templates/test_templates_schemas.py | 301 ---- tests/app/v2/test_errors.py | 159 -- 50 files changed, 64 insertions(+), 5277 deletions(-) delete mode 100644 app/v2/__init__.py delete mode 100644 app/v2/errors.py delete mode 100644 app/v2/inbound_sms/__init__.py delete mode 100644 app/v2/inbound_sms/get_inbound_sms.py delete mode 100644 app/v2/inbound_sms/inbound_sms_schemas.py delete mode 100644 app/v2/notifications/__init__.py delete mode 100644 app/v2/notifications/create_response.py delete mode 100644 app/v2/notifications/get_notifications.py delete mode 100644 app/v2/notifications/notification_schemas.py delete mode 100644 app/v2/notifications/post_notifications.py delete mode 100644 app/v2/template/__init__.py delete mode 100644 app/v2/template/get_template.py delete mode 100644 app/v2/template/post_template.py delete mode 100644 app/v2/template/template_schemas.py delete mode 100644 app/v2/templates/__init__.py delete mode 100644 app/v2/templates/get_templates.py delete mode 100644 app/v2/templates/templates_schemas.py delete mode 100644 app/v2/utils.py delete mode 100644 tests/app/v2/__init__.py delete mode 100644 tests/app/v2/inbound_sms/__init__.py delete mode 100644 tests/app/v2/inbound_sms/test_get_inbound_sms.py delete mode 100644 tests/app/v2/inbound_sms/test_inbound_sms_schemas.py delete mode 100644 tests/app/v2/notifications/__init__.py delete mode 100644 tests/app/v2/notifications/test_get_notifications.py delete mode 100644 tests/app/v2/notifications/test_notification_schemas.py delete mode 100644 tests/app/v2/notifications/test_post_notifications.py delete mode 100644 tests/app/v2/template/__init__.py delete mode 100644 tests/app/v2/template/test_get_template.py delete mode 100644 tests/app/v2/template/test_post_template.py delete mode 100644 tests/app/v2/template/test_template_schemas.py delete mode 100644 tests/app/v2/templates/test_get_templates.py delete mode 100644 tests/app/v2/templates/test_templates_schemas.py delete mode 100644 tests/app/v2/test_errors.py diff --git a/app/__init__.py b/app/__init__.py index f9ce7a9ee..c08c4ae0a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -117,7 +117,6 @@ def create_app(application): document_download_client.init_app(application) register_blueprint(application) - register_v2_blueprints(application) # avoid circular imports by importing this file later from app.commands import setup_commands @@ -252,34 +251,6 @@ def register_blueprint(application): application.register_blueprint(upload_blueprint) -def register_v2_blueprints(application): - from app.authentication.auth import requires_auth - from app.v2.inbound_sms.get_inbound_sms import v2_inbound_sms_blueprint - from app.v2.notifications import ( # noqa - get_notifications, - post_notifications, - v2_notification_blueprint, - ) - from app.v2.template import ( # noqa - get_template, - post_template, - v2_template_blueprint, - ) - from app.v2.templates.get_templates import v2_templates_blueprint - - v2_notification_blueprint.before_request(requires_auth) - application.register_blueprint(v2_notification_blueprint) - - v2_templates_blueprint.before_request(requires_auth) - application.register_blueprint(v2_templates_blueprint) - - v2_template_blueprint.before_request(requires_auth) - application.register_blueprint(v2_template_blueprint) - - v2_inbound_sms_blueprint.before_request(requires_auth) - application.register_blueprint(v2_inbound_sms_blueprint) - - def init_app(app): @app.before_request def record_request_details(): diff --git a/app/celery/tasks.py b/app/celery/tasks.py index c94b93789..82fe7ea0e 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -20,12 +20,12 @@ from app.dao.service_inbound_api_dao import get_service_inbound_api_for_service from app.dao.service_sms_sender_dao import dao_get_service_sms_senders_by_id from app.dao.templates_dao import dao_get_template_by_id from app.enums import JobStatus, KeyType, NotificationType +from app.errors import TotalRequestsError from app.notifications.process_notifications import persist_notification from app.notifications.validators import check_service_over_total_message_limit from app.serialised_models import SerialisedService, SerialisedTemplate from app.service.utils import service_allowed_to_send_to from app.utils import DATETIME_FORMAT -from app.v2.errors import TotalRequestsError from notifications_utils.recipients import RecipientCSV diff --git a/app/errors.py b/app/errors.py index 1278d3253..6ed311dba 100644 --- a/app/errors.py +++ b/app/errors.py @@ -5,6 +5,7 @@ from sqlalchemy.exc import DataError from sqlalchemy.orm.exc import NoResultFound from app.authentication.auth import AuthError +from app.enums import KeyType from app.exceptions import ArchiveValidationError from notifications_utils.recipients import InvalidEmailError @@ -113,3 +114,45 @@ def register_errors(blueprint): e = getattr(e, "original_exception", e) current_app.logger.exception(e) return jsonify(result="error", message="Internal server error"), 500 + + +class TooManyRequestsError(InvalidRequest): + status_code = 429 + message_template = "Exceeded send limits ({}) for today" + + def __init__(self, sending_limit): + self.message = self.message_template.format(sending_limit) + + +class TotalRequestsError(InvalidRequest): + status_code = 429 + message_template = "Exceeded total application limits ({}) for today" + + def __init__(self, sending_limit): + self.message = self.message_template.format(sending_limit) + + +class RateLimitError(InvalidRequest): + status_code = 429 + message_template = ( + "Exceeded rate limit for key type {} of {} requests per {} seconds" + ) + + def __init__(self, sending_limit, interval, key_type): + # normal keys are spoken of as "live" in the documentation + # so using this in the error messaging + if key_type == KeyType.NORMAL: + key_type = "live" + + self.message = self.message_template.format( + key_type.upper(), sending_limit, interval + ) + + +class BadRequestError(InvalidRequest): + message = "An error occurred" + + def __init__(self, fields=None, message=None, status_code=400): + self.status_code = status_code + self.fields = fields or [] + self.message = message if message else self.message diff --git a/app/models.py b/app/models.py index 71eea3295..7f0a37698 100644 --- a/app/models.py +++ b/app/models.py @@ -1215,7 +1215,6 @@ class Template(TemplateBase): ) def get_link(self): - # TODO: use "/v2/" route once available return url_for( "template.get_template_by_id_and_service_id", service_id=self.service_id, @@ -1284,8 +1283,9 @@ class TemplateHistory(TemplateBase): def get_link(self): return url_for( - "v2_template.get_template_by_id", + "template.get_template_by_id_and_service_id", template_id=self.id, + service_id=self.service.id, version=self.version, _external=True, ) diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index 8f542d31a..b7832969e 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -11,8 +11,8 @@ from app.dao.notifications_dao import ( dao_delete_notifications_by_id, ) from app.enums import KeyType, NotificationStatus, NotificationType +from app.errors import BadRequestError from app.models import Notification -from app.v2.errors import BadRequestError from notifications_utils.recipients import ( format_email_address, get_international_phone_info, diff --git a/app/notifications/validators.py b/app/notifications/validators.py index 25cc7eb66..f0a7f2a8f 100644 --- a/app/notifications/validators.py +++ b/app/notifications/validators.py @@ -6,12 +6,12 @@ from app.dao.notifications_dao import dao_get_notification_count_for_service from app.dao.service_email_reply_to_dao import dao_get_reply_to_by_id from app.dao.service_sms_sender_dao import dao_get_service_sms_senders_by_id from app.enums import KeyType, NotificationType, ServicePermissionType, TemplateType +from app.errors import BadRequestError, RateLimitError, TotalRequestsError from app.models import ServicePermission from app.notifications.process_notifications import create_content_for_notification from app.serialised_models import SerialisedTemplate from app.service.utils import service_allowed_to_send_to from app.utils import get_public_notify_type_text -from app.v2.errors import BadRequestError, RateLimitError, TotalRequestsError from notifications_utils import SMS_CHAR_COUNT_LIMIT from notifications_utils.clients.redis import ( rate_limit_cache_key, diff --git a/app/service/send_notification.py b/app/service/send_notification.py index 66bc88358..4459ded3c 100644 --- a/app/service/send_notification.py +++ b/app/service/send_notification.py @@ -7,6 +7,7 @@ from app.dao.services_dao import dao_fetch_service_by_id from app.dao.templates_dao import dao_get_template_by_id_and_service_id from app.dao.users_dao import get_user_by_id from app.enums import KeyType, NotificationType, TemplateProcessType +from app.errors import BadRequestError from app.notifications.process_notifications import ( persist_notification, send_notification_to_queue, @@ -16,7 +17,6 @@ from app.notifications.validators import ( validate_and_format_recipient, validate_template, ) -from app.v2.errors import BadRequestError def validate_created_by(service, created_by_id): diff --git a/app/v2/__init__.py b/app/v2/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/v2/errors.py b/app/v2/errors.py deleted file mode 100644 index 0888e2073..000000000 --- a/app/v2/errors.py +++ /dev/null @@ -1,125 +0,0 @@ -import json - -from flask import current_app, jsonify, request -from jsonschema import ValidationError as JsonSchemaValidationError -from sqlalchemy.exc import DataError -from sqlalchemy.orm.exc import NoResultFound - -from app.authentication.auth import AuthError -from app.enums import KeyType -from app.errors import InvalidRequest -from notifications_utils.recipients import InvalidEmailError - - -class TooManyRequestsError(InvalidRequest): - status_code = 429 - message_template = "Exceeded send limits ({}) for today" - - def __init__(self, sending_limit): - self.message = self.message_template.format(sending_limit) - - -class TotalRequestsError(InvalidRequest): - status_code = 429 - message_template = "Exceeded total application limits ({}) for today" - - def __init__(self, sending_limit): - self.message = self.message_template.format(sending_limit) - - -class RateLimitError(InvalidRequest): - status_code = 429 - message_template = ( - "Exceeded rate limit for key type {} of {} requests per {} seconds" - ) - - def __init__(self, sending_limit, interval, key_type): - # normal keys are spoken of as "live" in the documentation - # so using this in the error messaging - if key_type == KeyType.NORMAL: - key_type = "live" - - self.message = self.message_template.format( - key_type.upper(), sending_limit, interval - ) - - -class BadRequestError(InvalidRequest): - message = "An error occurred" - - def __init__(self, fields=None, message=None, status_code=400): - self.status_code = status_code - self.fields = fields or [] - self.message = message if message else self.message - - -class ValidationError(InvalidRequest): - message = "Your notification has failed validation" - - def __init__(self, fields=None, message=None, status_code=400): - self.status_code = status_code - self.fields = fields or [] - self.message = message if message else self.message - - -def register_errors(blueprint): - @blueprint.errorhandler(InvalidEmailError) - def invalid_format(error): - # Please not that InvalidEmailError is re-raised for InvalidEmail or InvalidPhone, - # work should be done in the utils app to tidy up these errors. - current_app.logger.info(error) - return ( - jsonify( - status_code=400, - errors=[{"error": error.__class__.__name__, "message": str(error)}], - ), - 400, - ) - - @blueprint.errorhandler(InvalidRequest) - def invalid_data(error): - current_app.logger.info(error) - response = jsonify(error.to_dict_v2()), error.status_code - return response - - @blueprint.errorhandler(JsonSchemaValidationError) - def validation_error(error): - current_app.logger.info(error) - return jsonify(json.loads(error.message)), 400 - - @blueprint.errorhandler(NoResultFound) - @blueprint.errorhandler(DataError) - def no_result_found(e): - current_app.logger.info(e) - return ( - jsonify( - status_code=404, - errors=[{"error": e.__class__.__name__, "message": "No result found"}], - ), - 404, - ) - - @blueprint.errorhandler(AuthError) - def auth_error(error): - current_app.logger.info( - "API AuthError, client: {} error: {}".format( - request.headers.get("User-Agent"), error - ) - ) - return jsonify(error.to_dict_v2()), error.code - - @blueprint.errorhandler(Exception) - def internal_server_error(error): - current_app.logger.exception(error) - return ( - jsonify( - status_code=500, - errors=[ - { - "error": error.__class__.__name__, - "message": "Internal server error", - } - ], - ), - 500, - ) diff --git a/app/v2/inbound_sms/__init__.py b/app/v2/inbound_sms/__init__.py deleted file mode 100644 index efe56995c..000000000 --- a/app/v2/inbound_sms/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from flask import Blueprint - -from app.v2.errors import register_errors - -v2_inbound_sms_blueprint = Blueprint( - "v2_inbound_sms", __name__, url_prefix="/v2/received-text-messages" -) - -register_errors(v2_inbound_sms_blueprint) diff --git a/app/v2/inbound_sms/get_inbound_sms.py b/app/v2/inbound_sms/get_inbound_sms.py deleted file mode 100644 index 79051a95d..000000000 --- a/app/v2/inbound_sms/get_inbound_sms.py +++ /dev/null @@ -1,46 +0,0 @@ -from flask import current_app, jsonify, request, url_for - -from app import authenticated_service -from app.dao import inbound_sms_dao -from app.schema_validation import validate -from app.v2.inbound_sms import v2_inbound_sms_blueprint -from app.v2.inbound_sms.inbound_sms_schemas import get_inbound_sms_request - - -@v2_inbound_sms_blueprint.route("", methods=["GET"]) -def get_inbound_sms(): - data = validate(request.args.to_dict(), get_inbound_sms_request) - - paginated_inbound_sms = ( - inbound_sms_dao.dao_get_paginated_inbound_sms_for_service_for_public_api( - authenticated_service.id, - older_than=data.get("older_than", None), - page_size=current_app.config.get("API_PAGE_SIZE"), - ) - ) - - return ( - jsonify( - received_text_messages=[i.serialize() for i in paginated_inbound_sms], - links=_build_links(paginated_inbound_sms), - ), - 200, - ) - - -def _build_links(inbound_sms_list): - _links = { - "current": url_for( - "v2_inbound_sms.get_inbound_sms", - _external=True, - ), - } - - if inbound_sms_list: - _links["next"] = url_for( - "v2_inbound_sms.get_inbound_sms", - older_than=inbound_sms_list[-1].id, - _external=True, - ) - - return _links diff --git a/app/v2/inbound_sms/inbound_sms_schemas.py b/app/v2/inbound_sms/inbound_sms_schemas.py deleted file mode 100644 index 928886d55..000000000 --- a/app/v2/inbound_sms/inbound_sms_schemas.py +++ /dev/null @@ -1,61 +0,0 @@ -from app.schema_validation.definitions import uuid - -get_inbound_sms_request = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "schema for query parameters allowed when getting list of received text messages", - "type": "object", - "properties": { - "older_than": uuid, - }, - "additionalProperties": False, -} - - -get_inbound_sms_single_response = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "GET inbound sms schema response", - "type": "object", - "title": "GET response v2/inbound_sms", - "properties": { - "user_number": {"type": "string"}, - "created_at": { - "format": "date-time", - "type": "string", - "description": "Date+time created at", - }, - "service_id": uuid, - "id": uuid, - "notify_number": {"type": "string"}, - "content": {"type": "string"}, - }, - "required": [ - "id", - "user_number", - "created_at", - "service_id", - "notify_number", - "content", - ], - "additionalProperties": False, -} - -get_inbound_sms_response = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "GET list of inbound sms response schema", - "type": "object", - "properties": { - "received_text_messages": { - "type": "array", - "items": {"type": "object", "$ref": "#/definitions/inbound_sms"}, - }, - "links": { - "type": "object", - "properties": {"current": {"type": "string"}, "next": {"type": "string"}}, - "additionalProperties": False, - "required": ["current"], - }, - }, - "required": ["received_text_messages", "links"], - "definitions": {"inbound_sms": get_inbound_sms_single_response}, - "additionalProperties": False, -} diff --git a/app/v2/notifications/__init__.py b/app/v2/notifications/__init__.py deleted file mode 100644 index 6f61a93ba..000000000 --- a/app/v2/notifications/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from flask import Blueprint - -from app.v2.errors import register_errors - -v2_notification_blueprint = Blueprint( - "v2_notifications", __name__, url_prefix="/v2/notifications" -) - -register_errors(v2_notification_blueprint) diff --git a/app/v2/notifications/create_response.py b/app/v2/notifications/create_response.py deleted file mode 100644 index bf295bbef..000000000 --- a/app/v2/notifications/create_response.py +++ /dev/null @@ -1,66 +0,0 @@ -def create_post_sms_response_from_notification( - notification_id, - client_reference, - template_id, - template_version, - service_id, - content, - from_number, - url_root, -): - resp = __create_notification_response( - notification_id, - client_reference, - template_id, - template_version, - service_id, - url_root, - ) - resp["content"] = {"from_number": from_number, "body": content} - return resp - - -def create_post_email_response_from_notification( - notification_id, - client_reference, - template_id, - template_version, - service_id, - content, - subject, - email_from, - url_root, -): - resp = __create_notification_response( - notification_id, - client_reference, - template_id, - template_version, - service_id, - url_root, - ) - resp["content"] = {"from_email": email_from, "body": content, "subject": subject} - return resp - - -def __create_notification_response( - notification_id, - client_reference, - template_id, - template_version, - service_id, - url_root, -): - return { - "id": notification_id, - "reference": client_reference, - "uri": "{}v2/notifications/{}".format(url_root, str(notification_id)), - "template": { - "id": template_id, - "version": template_version, - "uri": "{}services/{}/templates/{}".format( - url_root, str(service_id), str(template_id) - ), - }, - "scheduled_for": None, - } diff --git a/app/v2/notifications/get_notifications.py b/app/v2/notifications/get_notifications.py deleted file mode 100644 index d801b8528..000000000 --- a/app/v2/notifications/get_notifications.py +++ /dev/null @@ -1,88 +0,0 @@ -from flask import current_app, jsonify, request, url_for - -from app import api_user, authenticated_service -from app.aws.s3 import get_personalisation_from_s3 -from app.dao import notifications_dao -from app.schema_validation import validate -from app.v2.notifications import v2_notification_blueprint -from app.v2.notifications.notification_schemas import ( - get_notifications_request, - notification_by_id, -) - - -@v2_notification_blueprint.route("/", methods=["GET"]) -def get_notification_by_id(notification_id): - _data = {"notification_id": notification_id} - validate(_data, notification_by_id) - notification = notifications_dao.get_notification_with_personalisation( - authenticated_service.id, notification_id, key_type=None - ) - notification.personalisation = get_personalisation_from_s3( - notification.service_id, - notification.job_id, - notification.job_row_number, - ) - return jsonify(notification.serialize()), 200 - - -@v2_notification_blueprint.route("", methods=["GET"]) -def get_notifications(): - _data = request.args.to_dict(flat=False) - - # flat=False makes everything a list, but we only ever allow one value for "older_than" - if "older_than" in _data: - _data["older_than"] = _data["older_than"][0] - - # and client reference - if "reference" in _data: - _data["reference"] = _data["reference"][0] - - if "include_jobs" in _data: - _data["include_jobs"] = _data["include_jobs"][0] - - data = validate(_data, get_notifications_request) - - paginated_notifications = notifications_dao.get_notifications_for_service( - str(authenticated_service.id), - filter_dict=data, - key_type=api_user.key_type, - personalisation=True, - older_than=data.get("older_than"), - client_reference=data.get("reference"), - page_size=current_app.config.get("API_PAGE_SIZE"), - include_jobs=data.get("include_jobs"), - count_pages=False, - ) - - for notification in paginated_notifications.items: - if notification.job_id is not None: - notification.personalisation = get_personalisation_from_s3( - notification.service_id, - notification.job_id, - notification.job_row_number, - ) - - def _build_links(notifications): - _links = { - "current": url_for(".get_notifications", _external=True, **data), - } - - if len(notifications): - next_query_params = dict(data, older_than=notifications[-1].id) - _links["next"] = url_for( - ".get_notifications", _external=True, **next_query_params - ) - - return _links - - return ( - jsonify( - notifications=[ - notification.serialize() - for notification in paginated_notifications.items - ], - links=_build_links(paginated_notifications.items), - ), - 200, - ) diff --git a/app/v2/notifications/notification_schemas.py b/app/v2/notifications/notification_schemas.py deleted file mode 100644 index c66ecf6c2..000000000 --- a/app/v2/notifications/notification_schemas.py +++ /dev/null @@ -1,211 +0,0 @@ -from app.enums import NotificationStatus, TemplateType -from app.schema_validation.definitions import personalisation, uuid - -template = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "template schema", - "type": "object", - "title": "notification content", - "properties": { - "id": uuid, - "version": {"type": "integer"}, - "uri": {"type": "string", "format": "uri"}, - }, - "required": ["id", "version", "uri"], -} - -notification_by_id = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "GET notification response schema", - "type": "object", - "title": "response v2/notification", - "properties": {"notification_id": uuid}, - "required": ["notification_id"], -} - - -get_notification_response = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "GET notification response schema", - "type": "object", - "title": "response v2/notification", - "properties": { - "id": uuid, - "reference": {"type": ["string", "null"]}, - "email_address": {"type": ["string", "null"]}, - "phone_number": {"type": ["string", "null"]}, - "line_1": {"type": ["string", "null"]}, - "line_2": {"type": ["string", "null"]}, - "line_3": {"type": ["string", "null"]}, - "line_4": {"type": ["string", "null"]}, - "line_5": {"type": ["string", "null"]}, - "line_6": {"type": ["string", "null"]}, - "postcode": {"type": ["string", "null"]}, - "type": {"enum": list(TemplateType)}, - "status": {"type": "string"}, - "template": template, - "body": {"type": "string"}, - "subject": {"type": ["string", "null"]}, - "created_at": {"type": "string"}, - "sent_at": {"type": ["string", "null"]}, - "completed_at": {"type": ["string", "null"]}, - "scheduled_for": {"type": ["string", "null"]}, - }, - "required": [ - # technically, all keys are required since we always have all of them - "id", - "reference", - "email_address", - "phone_number", - "line_1", - "line_2", - "line_3", - "line_4", - "line_5", - "line_6", - "postcode", - "type", - "status", - "template", - "body", - "created_at", - "sent_at", - "completed_at", - ], -} - -get_notifications_request = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "schema for query parameters allowed when getting list of notifications", - "type": "object", - "properties": { - "reference": {"type": "string"}, - "status": { - "type": "array", - "items": {"enum": list(NotificationStatus)}, - }, - "template_type": { - "type": "array", - "items": {"enum": list(TemplateType)}, - }, - "include_jobs": {"enum": ["true", "True"]}, - "older_than": uuid, - }, - "additionalProperties": False, -} - -get_notifications_response = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "GET list of notifications response schema", - "type": "object", - "properties": { - "notifications": { - "type": "array", - "items": {"type": "object", "$ref": "#/definitions/notification"}, - }, - "links": { - "type": "object", - "properties": {"current": {"type": "string"}, "next": {"type": "string"}}, - "additionalProperties": False, - "required": ["current"], - }, - }, - "additionalProperties": False, - "required": ["notifications", "links"], - "definitions": {"notification": get_notification_response}, -} - -post_sms_request = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "POST sms notification schema", - "type": "object", - "title": "POST v2/notifications/sms", - "properties": { - "reference": {"type": "string"}, - "phone_number": {"type": "string", "format": "phone_number"}, - "template_id": uuid, - "personalisation": personalisation, - "scheduled_for": { - "type": ["string", "null"], - "format": "datetime_within_next_day", - }, - "sms_sender_id": uuid, - }, - "required": ["phone_number", "template_id"], - "additionalProperties": False, -} - -sms_content = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "content schema for SMS notification response schema", - "type": "object", - "title": "notification content", - "properties": {"body": {"type": "string"}, "from_number": {"type": "string"}}, - "required": ["body", "from_number"], -} - -post_sms_response = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "POST sms notification response schema", - "type": "object", - "title": "response v2/notifications/sms", - "properties": { - "id": uuid, - "reference": {"type": ["string", "null"]}, - "content": sms_content, - "uri": {"type": "string", "format": "uri"}, - "template": template, - "scheduled_for": {"type": ["string", "null"]}, - }, - "required": ["id", "content", "uri", "template"], -} - - -post_email_request = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "POST email notification schema", - "type": "object", - "title": "POST v2/notifications/email", - "properties": { - "reference": {"type": "string"}, - "email_address": {"type": "string", "format": "email_address"}, - "template_id": uuid, - "personalisation": personalisation, - "scheduled_for": { - "type": ["string", "null"], - "format": "datetime_within_next_day", - }, - "email_reply_to_id": uuid, - }, - "required": ["email_address", "template_id"], - "additionalProperties": False, -} - -email_content = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "Email content for POST email notification", - "type": "object", - "title": "notification email content", - "properties": { - "from_email": {"type": "string", "format": "email_address"}, - "body": {"type": "string"}, - "subject": {"type": "string"}, - }, - "required": ["body", "from_email", "subject"], -} - -post_email_response = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "POST email notification response schema", - "type": "object", - "title": "response v2/notifications/email", - "properties": { - "id": uuid, - "reference": {"type": ["string", "null"]}, - "content": email_content, - "uri": {"type": "string", "format": "uri"}, - "template": template, - "scheduled_for": {"type": ["string", "null"]}, - }, - "required": ["id", "content", "uri", "template"], -} diff --git a/app/v2/notifications/post_notifications.py b/app/v2/notifications/post_notifications.py deleted file mode 100644 index 3c8fa1fdb..000000000 --- a/app/v2/notifications/post_notifications.py +++ /dev/null @@ -1,338 +0,0 @@ -import functools -import uuid -from datetime import datetime - -import botocore -from flask import abort, current_app, jsonify, request - -from app import api_user, authenticated_service, document_download_client, encryption -from app.celery.tasks import save_api_email, save_api_sms -from app.clients.document_download import DocumentDownloadError -from app.config import QueueNames -from app.enums import KeyType, NotificationStatus, NotificationType, TemplateProcessType -from app.models import Notification -from app.notifications.process_notifications import ( - persist_notification, - send_notification_to_queue_detached, - simulated_recipient, -) -from app.notifications.validators import ( - check_if_service_can_send_files_by_email, - check_is_message_too_long, - check_rate_limiting, - check_service_email_reply_to_id, - check_service_has_permission, - check_service_sms_sender_id, - validate_and_format_recipient, - validate_template, -) -from app.schema_validation import validate -from app.utils import DATETIME_FORMAT -from app.v2.errors import BadRequestError -from app.v2.notifications import v2_notification_blueprint -from app.v2.notifications.create_response import ( - create_post_email_response_from_notification, - create_post_sms_response_from_notification, -) -from app.v2.notifications.notification_schemas import ( - post_email_request, - post_sms_request, -) -from app.v2.utils import get_valid_json -from notifications_utils.recipients import try_validate_and_format_phone_number - - -@v2_notification_blueprint.route("/", methods=["POST"]) -def post_notification(notification_type): - request_json = get_valid_json() - - if notification_type == NotificationType.EMAIL: - form = validate(request_json, post_email_request) - elif notification_type == NotificationType.SMS: - form = validate(request_json, post_sms_request) - else: - abort(404) - - check_service_has_permission(notification_type, authenticated_service.permissions) - - check_rate_limiting(authenticated_service, api_user) - - template, template_with_content = validate_template( - form["template_id"], - form.get("personalisation", {}), - authenticated_service, - notification_type, - check_char_count=False, - ) - - reply_to = get_reply_to_text(notification_type, form, template) - - notification = process_sms_or_email_notification( - form=form, - notification_type=notification_type, - template=template, - template_with_content=template_with_content, - template_process_type=template.process_type, - service=authenticated_service, - reply_to_text=reply_to, - ) - - return jsonify(notification), 201 - - -def process_sms_or_email_notification( - *, - form, - notification_type, - template, - template_with_content, - template_process_type, - service, - reply_to_text=None, -): - notification_id = uuid.uuid4() - form_send_to = ( - form["email_address"] - if notification_type == NotificationType.EMAIL - else form["phone_number"] - ) - - send_to = validate_and_format_recipient( - send_to=form_send_to, - key_type=api_user.key_type, - service=service, - notification_type=notification_type, - ) - - # Do not persist or send notification to the queue if it is a simulated recipient - simulated = simulated_recipient(send_to, notification_type) - - personalisation, document_download_count = process_document_uploads( - form.get("personalisation"), service, simulated=simulated - ) - if document_download_count: - # We changed personalisation which means we need to update the content - template_with_content.values = personalisation - - # validate content length after url is replaced in personalisation. - check_is_message_too_long(template_with_content) - - resp = create_response_for_post_notification( - notification_id=notification_id, - client_reference=form.get("reference", None), - template_id=template.id, - template_version=template.version, - service_id=service.id, - notification_type=notification_type, - reply_to=reply_to_text, - template_with_content=template_with_content, - ) - - if ( - service.high_volume - and api_user.key_type == KeyType.NORMAL - and notification_type in {NotificationType.EMAIL, NotificationType.SMS} - ): - # Put service with high volumes of notifications onto a queue - # To take the pressure off the db for API requests put the notification for our high volume service onto a queue - # the task will then save the notification, then call send_notification_to_queue. - # NOTE: The high volume service should be aware that the notification is not immediately - # available by a GET request, it is recommend they use callbacks to keep track of status updates. - try: - save_email_or_sms_to_queue( - form=form, - notification_id=str(notification_id), - notification_type=notification_type, - api_key=api_user, - template=template, - service_id=service.id, - personalisation=personalisation, - document_download_count=document_download_count, - reply_to_text=reply_to_text, - ) - return resp - except (botocore.exceptions.ClientError, botocore.parsers.ResponseParserError): - # If SQS cannot put the task on the queue, it's probably because the notification body was too long and it - # went over SQS's 256kb message limit. If the body is very large, it may exceed the HTTP max content length; - # the exception we get here isn't handled correctly by botocore - we get a ResponseParserError instead. - # Hopefully this is no longer an issue with Redis as celery's backing store - current_app.logger.info( - f"Notification {notification_id} failed to save to high volume queue. Using normal flow instead" - ) - - persist_notification( - notification_id=notification_id, - template_id=template.id, - template_version=template.version, - recipient=form_send_to, - service=service, - personalisation=personalisation, - notification_type=notification_type, - api_key_id=api_user.id, - key_type=api_user.key_type, - client_reference=form.get("reference", None), - simulated=simulated, - reply_to_text=reply_to_text, - document_download_count=document_download_count, - ) - - if not simulated: - queue_name = ( - QueueNames.PRIORITY - if template_process_type == TemplateProcessType.PRIORITY - else None - ) - send_notification_to_queue_detached( - key_type=api_user.key_type, - notification_type=notification_type, - notification_id=notification_id, - queue=queue_name, - ) - else: - current_app.logger.debug( - "POST simulated notification for id: {}".format(notification_id) - ) - - return resp - - -def save_email_or_sms_to_queue( - *, - notification_id, - form, - notification_type, - api_key, - template, - service_id, - personalisation, - document_download_count, - reply_to_text=None, -): - data = { - "id": notification_id, - "template_id": str(template.id), - "template_version": template.version, - "to": ( - form["email_address"] - if notification_type == NotificationType.EMAIL - else form["phone_number"] - ), - "service_id": str(service_id), - "personalisation": personalisation, - "notification_type": notification_type, - "api_key_id": str(api_key.id), - "key_type": api_key.key_type, - "client_reference": form.get("reference", None), - "reply_to_text": reply_to_text, - "document_download_count": document_download_count, - "status": NotificationStatus.CREATED, - "created_at": datetime.utcnow().strftime(DATETIME_FORMAT), - } - encrypted = encryption.encrypt(data) - - if notification_type == NotificationType.EMAIL: - save_api_email.apply_async([encrypted], queue=QueueNames.SAVE_API_EMAIL) - elif notification_type == NotificationType.SMS: - save_api_sms.apply_async([encrypted], queue=QueueNames.SAVE_API_SMS) - - return Notification(**data) - - -def process_document_uploads(personalisation_data, service, simulated=False): - """ - Returns modified personalisation dict and a count of document uploads. If there are no document uploads, returns - a count of `None` rather than `0`. - """ - file_keys = [ - k - for k, v in (personalisation_data or {}).items() - if isinstance(v, dict) and "file" in v - ] - if not file_keys: - return personalisation_data, None - - personalisation_data = personalisation_data.copy() - - check_if_service_can_send_files_by_email( - service_contact_link=authenticated_service.contact_link, - service_id=authenticated_service.id, - ) - - for key in file_keys: - if simulated: - personalisation_data[key] = ( - document_download_client.get_upload_url(service.id) + "/test-document" - ) - else: - try: - personalisation_data[key] = document_download_client.upload_document( - service.id, - personalisation_data[key]["file"], - personalisation_data[key].get("is_csv"), - ) - except DocumentDownloadError as e: - raise BadRequestError(message=e.message, status_code=e.status_code) - - return personalisation_data, len(file_keys) - - -def get_reply_to_text(notification_type, form, template): - reply_to = None - if notification_type == NotificationType.EMAIL: - service_email_reply_to_id = form.get("email_reply_to_id", None) - reply_to = ( - check_service_email_reply_to_id( - str(authenticated_service.id), - service_email_reply_to_id, - notification_type, - ) - or template.reply_to_text - ) - - elif notification_type == NotificationType.SMS: - service_sms_sender_id = form.get("sms_sender_id", None) - sms_sender_id = check_service_sms_sender_id( - str(authenticated_service.id), service_sms_sender_id, notification_type - ) - if sms_sender_id: - reply_to = try_validate_and_format_phone_number(sms_sender_id) - else: - reply_to = template.reply_to_text - - return reply_to - - -def create_response_for_post_notification( - notification_id, - client_reference, - template_id, - template_version, - service_id, - notification_type, - reply_to, - template_with_content, -): - if notification_type == NotificationType.SMS: - create_resp_partial = functools.partial( - create_post_sms_response_from_notification, - from_number=reply_to, - ) - elif notification_type == NotificationType.EMAIL: - create_resp_partial = functools.partial( - create_post_email_response_from_notification, - subject=template_with_content.subject, - email_from="{}@{}".format( - authenticated_service.email_from, - current_app.config["NOTIFY_EMAIL_DOMAIN"], - ), - ) - resp = create_resp_partial( - notification_id, - client_reference, - template_id, - template_version, - service_id, - url_root=request.url_root, - content=template_with_content.content_with_placeholders_filled_in, - ) - return resp diff --git a/app/v2/template/__init__.py b/app/v2/template/__init__.py deleted file mode 100644 index ca40df2e4..000000000 --- a/app/v2/template/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from flask import Blueprint - -from app.v2.errors import register_errors - -v2_template_blueprint = Blueprint("v2_template", __name__, url_prefix="/v2/template") - -register_errors(v2_template_blueprint) diff --git a/app/v2/template/get_template.py b/app/v2/template/get_template.py deleted file mode 100644 index 1c86f3043..000000000 --- a/app/v2/template/get_template.py +++ /dev/null @@ -1,22 +0,0 @@ -from flask import jsonify - -from app import authenticated_service -from app.dao import templates_dao -from app.schema_validation import validate -from app.v2.template import v2_template_blueprint -from app.v2.template.template_schemas import get_template_by_id_request - - -@v2_template_blueprint.route("/", methods=["GET"]) -@v2_template_blueprint.route("//version/", methods=["GET"]) -def get_template_by_id(template_id, version=None): - _data = {"id": template_id} - if version: - _data["version"] = version - - data = validate(_data, get_template_by_id_request) - - template = templates_dao.dao_get_template_by_id_and_service_id( - template_id, authenticated_service.id, data.get("version") - ) - return jsonify(template.serialize_for_v2()), 200 diff --git a/app/v2/template/post_template.py b/app/v2/template/post_template.py deleted file mode 100644 index ed38d8f15..000000000 --- a/app/v2/template/post_template.py +++ /dev/null @@ -1,50 +0,0 @@ -from flask import jsonify, request - -from app import authenticated_service -from app.dao import templates_dao -from app.schema_validation import validate -from app.v2.errors import BadRequestError -from app.v2.template import v2_template_blueprint -from app.v2.template.template_schemas import ( - create_post_template_preview_response, - post_template_preview_request, -) -from app.v2.utils import get_valid_json - - -@v2_template_blueprint.route("//preview", methods=["POST"]) -def post_template_preview(template_id): - # The payload is empty when there are no place holders in the template. - _data = request.get_data(as_text=True) - if not _data: - _data = {} - else: - _data = get_valid_json() - - _data["id"] = template_id - - data = validate(_data, post_template_preview_request) - - template = templates_dao.dao_get_template_by_id_and_service_id( - template_id, authenticated_service.id - ) - - template_object = template._as_utils_template_with_personalisation( - data.get("personalisation") - ) - - check_placeholders(template_object) - - resp = create_post_template_preview_response( - template=template, template_object=template_object - ) - - return jsonify(resp), 200 - - -def check_placeholders(template_object): - if template_object.missing_data: - message = "Missing personalisation: {}".format( - ", ".join(template_object.missing_data) - ) - raise BadRequestError(message=message, fields=[{"template": message}]) diff --git a/app/v2/template/template_schemas.py b/app/v2/template/template_schemas.py deleted file mode 100644 index 7461f3fe4..000000000 --- a/app/v2/template/template_schemas.py +++ /dev/null @@ -1,84 +0,0 @@ -from app.enums import TemplateType -from app.schema_validation.definitions import personalisation, uuid - -get_template_by_id_request = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "schema for parameters allowed when getting template by id", - "type": "object", - "properties": {"id": uuid, "version": {"type": ["integer", "null"], "minimum": 1}}, - "required": ["id"], - "additionalProperties": False, -} - -get_template_by_id_response = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "GET template by id schema response", - "type": "object", - "title": "reponse v2/template", - "properties": { - "id": uuid, - "type": {"enum": list(TemplateType)}, - "created_at": { - "format": "date-time", - "type": "string", - "description": "Date+time created", - }, - "updated_at": { - "format": "date-time", - "type": ["string", "null"], - "description": "Date+time updated", - }, - "created_by": {"type": "string"}, - "version": {"type": "integer"}, - "body": {"type": "string"}, - "subject": {"type": ["string", "null"]}, - "name": {"type": "string"}, - }, - "required": [ - "id", - "type", - "created_at", - "updated_at", - "version", - "created_by", - "body", - "name", - ], -} - -post_template_preview_request = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "POST template schema", - "type": "object", - "title": "POST v2/template/{id}/preview", - "properties": {"id": uuid, "personalisation": personalisation}, - "required": ["id"], -} - -post_template_preview_response = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "POST template preview schema response", - "type": "object", - "title": "reponse v2/template/{id}/preview", - "properties": { - "id": uuid, - "type": {"enum": list(TemplateType)}, - "version": {"type": "integer"}, - "body": {"type": "string"}, - "subject": {"type": ["string", "null"]}, - "html": {"type": ["string", "null"]}, - }, - "required": ["id", "type", "version", "body"], - "additionalProperties": False, -} - - -def create_post_template_preview_response(template, template_object): - return { - "id": template.id, - "type": template.template_type, - "version": template.version, - "body": template_object.content_with_placeholders_filled_in, - "html": getattr(template_object, "html_body", None), - "subject": getattr(template_object, "subject", None), - } diff --git a/app/v2/templates/__init__.py b/app/v2/templates/__init__.py deleted file mode 100644 index f80ea2d05..000000000 --- a/app/v2/templates/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from flask import Blueprint - -from app.v2.errors import register_errors - -v2_templates_blueprint = Blueprint("v2_templates", __name__, url_prefix="/v2/templates") - -register_errors(v2_templates_blueprint) diff --git a/app/v2/templates/get_templates.py b/app/v2/templates/get_templates.py deleted file mode 100644 index 1bf273e8a..000000000 --- a/app/v2/templates/get_templates.py +++ /dev/null @@ -1,21 +0,0 @@ -from flask import jsonify, request - -from app import authenticated_service -from app.dao import templates_dao -from app.schema_validation import validate -from app.v2.templates import v2_templates_blueprint -from app.v2.templates.templates_schemas import get_all_template_request - - -@v2_templates_blueprint.route("", methods=["GET"]) -def get_templates(): - data = validate(request.args.to_dict(), get_all_template_request) - - templates = templates_dao.dao_get_all_templates_for_service( - authenticated_service.id, data.get("type") - ) - - return ( - jsonify(templates=[template.serialize_for_v2() for template in templates]), - 200, - ) diff --git a/app/v2/templates/templates_schemas.py b/app/v2/templates/templates_schemas.py deleted file mode 100644 index 90cb6e01d..000000000 --- a/app/v2/templates/templates_schemas.py +++ /dev/null @@ -1,24 +0,0 @@ -from app.enums import TemplateType -from app.v2.template.template_schemas import get_template_by_id_response as template - -get_all_template_request = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "request schema for parameters allowed when getting all templates", - "type": "object", - "properties": {"type": {"enum": list(TemplateType)}}, - "additionalProperties": False, -} - -get_all_template_response = { - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "GET response schema when getting all templates", - "type": "object", - "properties": { - "templates": { - "type": "array", - "items": {"type": "object", "$ref": "#/definitions/template"}, - } - }, - "required": ["templates"], - "definitions": {"template": template}, -} diff --git a/app/v2/utils.py b/app/v2/utils.py deleted file mode 100644 index 72b277eca..000000000 --- a/app/v2/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -from flask import request -from werkzeug.exceptions import BadRequest - -from app.v2.errors import BadRequestError - - -def get_valid_json(): - try: - request_json = request.get_json(force=True) - except BadRequest: - raise BadRequestError( - message="Invalid JSON supplied in POST data", status_code=400 - ) - return request_json or {} diff --git a/migrations/versions/0336_broadcast_msg_content_2.py b/migrations/versions/0336_broadcast_msg_content_2.py index a42cbc24e..014059d00 100644 --- a/migrations/versions/0336_broadcast_msg_content_2.py +++ b/migrations/versions/0336_broadcast_msg_content_2.py @@ -8,10 +8,11 @@ Create Date: 2020-12-04 15:06:22.544803 import sqlalchemy as sa from alembic import op -from notifications_utils.template import BroadcastMessageTemplate from sqlalchemy.dialects import postgresql from sqlalchemy.orm.session import Session +from notifications_utils.template import BroadcastMessageTemplate + revision = "0336_broadcast_msg_content_2" down_revision = "0335_broadcast_msg_content" diff --git a/notifications_utils/recipients.py b/notifications_utils/recipients.py index 0d8536c33..68e2cb101 100644 --- a/notifications_utils/recipients.py +++ b/notifications_utils/recipients.py @@ -17,9 +17,7 @@ from notifications_utils.formatters import ( strip_and_remove_obscure_whitespace, ) from notifications_utils.insensitive_dict import InsensitiveDict -from notifications_utils.international_billing_rates import ( - INTERNATIONAL_BILLING_RATES, -) +from notifications_utils.international_billing_rates import INTERNATIONAL_BILLING_RATES from notifications_utils.postal_address import ( address_line_7_key, address_lines_1_to_6_and_postcode_keys, diff --git a/notifications_utils/template.py b/notifications_utils/template.py index 302fd3899..ec112173f 100644 --- a/notifications_utils/template.py +++ b/notifications_utils/template.py @@ -43,10 +43,7 @@ from notifications_utils.markdown import ( notify_letter_preview_markdown, notify_plain_text_email_markdown, ) -from notifications_utils.postal_address import ( - PostalAddress, - address_lines_1_to_7_keys, -) +from notifications_utils.postal_address import PostalAddress, address_lines_1_to_7_keys from notifications_utils.sanitise_text import SanitiseSMS from notifications_utils.take import Take from notifications_utils.template_change import TemplateChange diff --git a/poetry.lock b/poetry.lock index cd63bad37..5a82415f7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2098,13 +2098,9 @@ files = [ {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, - {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, - {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, - {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, - {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, @@ -2493,6 +2489,7 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] @@ -3478,7 +3475,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, diff --git a/tests/app/notifications/test_process_notification.py b/tests/app/notifications/test_process_notification.py index 8c80ad7a7..d7caf5bb1 100644 --- a/tests/app/notifications/test_process_notification.py +++ b/tests/app/notifications/test_process_notification.py @@ -8,6 +8,7 @@ from freezegun import freeze_time from sqlalchemy.exc import SQLAlchemyError from app.enums import KeyType, NotificationType, ServicePermissionType, TemplateType +from app.errors import BadRequestError from app.models import Notification, NotificationHistory from app.notifications.process_notifications import ( create_content_for_notification, @@ -16,7 +17,6 @@ from app.notifications.process_notifications import ( simulated_recipient, ) from app.serialised_models import SerialisedTemplate -from app.v2.errors import BadRequestError from notifications_utils.recipients import ( validate_and_format_email_address, validate_and_format_phone_number, diff --git a/tests/app/notifications/test_validators.py b/tests/app/notifications/test_validators.py index c6c317744..f9df6fb91 100644 --- a/tests/app/notifications/test_validators.py +++ b/tests/app/notifications/test_validators.py @@ -5,6 +5,7 @@ from freezegun import freeze_time import app from app.dao import templates_dao from app.enums import KeyType, NotificationType, ServicePermissionType, TemplateType +from app.errors import BadRequestError, RateLimitError, TotalRequestsError from app.notifications.process_notifications import create_content_for_notification from app.notifications.sns_cert_validator import ( VALID_SNS_TOPICS, @@ -35,7 +36,6 @@ from app.serialised_models import ( ) from app.service.utils import service_allowed_to_send_to from app.utils import get_template_instance -from app.v2.errors import BadRequestError, RateLimitError, TotalRequestsError from notifications_utils import SMS_CHAR_COUNT_LIMIT from tests.app.db import ( create_api_key, diff --git a/tests/app/public_contracts/test_GET_notification.py b/tests/app/public_contracts/test_GET_notification.py index 7a671ff50..e7447bfa5 100644 --- a/tests/app/public_contracts/test_GET_notification.py +++ b/tests/app/public_contracts/test_GET_notification.py @@ -3,13 +3,9 @@ import pytest from app.dao.api_key_dao import save_model_api_key from app.enums import KeyType from app.models import ApiKey -from app.v2.notifications.notification_schemas import ( - get_notification_response, - get_notifications_response, -) from tests import create_service_authorization_header -from . import return_json_from_response, validate, validate_v0 +from . import return_json_from_response, validate_v0 def _get_notification(client, notification, url): @@ -27,46 +23,6 @@ def _get_notification(client, notification, url): return client.get(url, headers=[auth_header]) -# v2 - - -def test_get_v2_sms_contract(client, sample_notification, mocker): - mock_s3_personalisation = mocker.patch( - "app.v2.notifications.get_notifications.get_personalisation_from_s3" - ) - mock_s3_personalisation.return_value = {} - response_json = return_json_from_response( - _get_notification( - client, - sample_notification, - "/v2/notifications/{}".format(sample_notification.id), - ) - ) - validate(response_json, get_notification_response) - - -def test_get_v2_email_contract(client, sample_email_notification, mocker): - mock_s3_personalisation = mocker.patch( - "app.v2.notifications.get_notifications.get_personalisation_from_s3" - ) - mock_s3_personalisation.return_value = {} - response_json = return_json_from_response( - _get_notification( - client, - sample_email_notification, - "/v2/notifications/{}".format(sample_email_notification.id), - ) - ) - validate(response_json, get_notification_response) - - -def test_get_v2_notifications_contract(client, sample_notification): - response_json = return_json_from_response( - _get_notification(client, sample_notification, "/v2/notifications") - ) - validate(response_json, get_notifications_response) - - # v0 diff --git a/tests/app/service/send_notification/test_send_notification.py b/tests/app/service/send_notification/test_send_notification.py index 1997c2bf3..9b833bfd0 100644 --- a/tests/app/service/send_notification/test_send_notification.py +++ b/tests/app/service/send_notification/test_send_notification.py @@ -12,10 +12,9 @@ from app.dao.api_key_dao import save_model_api_key from app.dao.services_dao import dao_update_service from app.dao.templates_dao import dao_get_all_templates_for_service, dao_update_template from app.enums import KeyType, NotificationType, TemplateProcessType, TemplateType -from app.errors import InvalidRequest +from app.errors import InvalidRequest, RateLimitError from app.models import ApiKey, Notification, NotificationHistory, Template from app.service.send_notification import send_one_off_notification -from app.v2.errors import RateLimitError from notifications_utils import SMS_CHAR_COUNT_LIMIT from tests import create_service_authorization_header from tests.app.db import ( diff --git a/tests/app/service/send_notification/test_send_one_off_notification.py b/tests/app/service/send_notification/test_send_one_off_notification.py index 7c510fb8c..9983515c7 100644 --- a/tests/app/service/send_notification/test_send_one_off_notification.py +++ b/tests/app/service/send_notification/test_send_one_off_notification.py @@ -13,9 +13,9 @@ from app.enums import ( TemplateProcessType, TemplateType, ) +from app.errors import BadRequestError from app.models import Notification, ServiceGuestList from app.service.send_notification import send_one_off_notification -from app.v2.errors import BadRequestError from notifications_utils import SMS_CHAR_COUNT_LIMIT from notifications_utils.recipients import InvalidPhoneError from tests.app.db import ( diff --git a/tests/app/user/test_rest_verify.py b/tests/app/user/test_rest_verify.py index 26eb085a4..db99e602b 100644 --- a/tests/app/user/test_rest_verify.py +++ b/tests/app/user/test_rest_verify.py @@ -499,10 +499,10 @@ def test_send_user_email_code_with_urlencoded_next_param( mock_redis_get = mocker.patch("app.celery.scheduled_tasks.redis_store.raw_get") mock_redis_get.return_value = "foo" - mock_s3_personalisation = mocker.patch( - "app.v2.notifications.get_notifications.get_personalisation_from_s3" - ) - mock_s3_personalisation.return_value = {"name": "Bob"} + # mock_s3_personalisation = mocker.patch( + # "app.v2.notifications.get_notifications.get_personalisation_from_s3" + # ) + # mock_s3_personalisation.return_value = {"name": "Bob"} mocker.patch("app.celery.scheduled_tasks.redis_store.raw_set") diff --git a/tests/app/v2/__init__.py b/tests/app/v2/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/app/v2/inbound_sms/__init__.py b/tests/app/v2/inbound_sms/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/app/v2/inbound_sms/test_get_inbound_sms.py b/tests/app/v2/inbound_sms/test_get_inbound_sms.py deleted file mode 100644 index 172c04f51..000000000 --- a/tests/app/v2/inbound_sms/test_get_inbound_sms.py +++ /dev/null @@ -1,222 +0,0 @@ -from flask import json, url_for - -from tests import create_service_authorization_header -from tests.app.db import ( - create_inbound_sms, - create_service_callback_api, - create_service_inbound_api, -) - - -def test_get_inbound_sms_returns_200(client, sample_service): - all_inbound_sms = [ - create_inbound_sms( - service=sample_service, user_number="447700900111", content="Hi" - ), - create_inbound_sms(service=sample_service, user_number="447700900112"), - create_inbound_sms( - service=sample_service, user_number="447700900111", content="Bye" - ), - create_inbound_sms(service=sample_service, user_number="07700900113"), - ] - - auth_header = create_service_authorization_header(service_id=sample_service.id) - response = client.get( - path="/v2/received-text-messages", - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True))[ - "received_text_messages" - ] - - reversed_all_inbound_sms = sorted( - all_inbound_sms, key=lambda sms: sms.created_at, reverse=True - ) - - expected_response = [i.serialize() for i in reversed_all_inbound_sms] - - assert json_response == expected_response - - -def test_get_inbound_sms_returns_200_when_service_has_callbacks(client, sample_service): - create_service_inbound_api( - service=sample_service, - url="https://inbound.example.com", - ) - create_service_callback_api( - service=sample_service, - url="https://inbound.example.com", - ) - - auth_header = create_service_authorization_header(service_id=sample_service.id) - response = client.get( - path="/v2/received-text-messages", - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - - -def test_get_inbound_sms_generate_page_links(client, sample_service, mocker): - mocker.patch.dict( - "app.v2.inbound_sms.get_inbound_sms.current_app.config", {"API_PAGE_SIZE": 2} - ) - all_inbound_sms = [ - create_inbound_sms( - service=sample_service, user_number="447700900111", content="Hi" - ), - create_inbound_sms(service=sample_service, user_number="447700900111"), - create_inbound_sms( - service=sample_service, user_number="447700900111", content="End" - ), - ] - - reversed_inbound_sms = sorted( - all_inbound_sms, key=lambda sms: sms.created_at, reverse=True - ) - - auth_header = create_service_authorization_header(service_id=sample_service.id) - response = client.get( - url_for("v2_inbound_sms.get_inbound_sms"), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - - json_response = json.loads(response.get_data(as_text=True)) - expected_inbound_sms_list = [i.serialize() for i in reversed_inbound_sms[:2]] - - assert json_response["received_text_messages"] == expected_inbound_sms_list - assert ( - url_for("v2_inbound_sms.get_inbound_sms", _external=True) - == json_response["links"]["current"] - ) - assert ( - url_for( - "v2_inbound_sms.get_inbound_sms", - older_than=reversed_inbound_sms[1].id, - _external=True, - ) - == json_response["links"]["next"] - ) - - -def test_get_next_inbound_sms_will_get_correct_inbound_sms_list( - client, sample_service, mocker -): - mocker.patch.dict( - "app.v2.inbound_sms.get_inbound_sms.current_app.config", {"API_PAGE_SIZE": 2} - ) - all_inbound_sms = [ - create_inbound_sms( - service=sample_service, user_number="447700900111", content="1" - ), - create_inbound_sms( - service=sample_service, user_number="447700900111", content="2" - ), - create_inbound_sms( - service=sample_service, user_number="447700900111", content="3" - ), - create_inbound_sms( - service=sample_service, user_number="447700900111", content="4" - ), - ] - reversed_inbound_sms = sorted( - all_inbound_sms, key=lambda sms: sms.created_at, reverse=True - ) - - auth_header = create_service_authorization_header(service_id=sample_service.id) - response = client.get( - path=url_for( - "v2_inbound_sms.get_inbound_sms", older_than=reversed_inbound_sms[1].id - ), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - - json_response = json.loads(response.get_data(as_text=True)) - expected_inbound_sms_list = [i.serialize() for i in reversed_inbound_sms[2:]] - - assert json_response["received_text_messages"] == expected_inbound_sms_list - assert ( - url_for("v2_inbound_sms.get_inbound_sms", _external=True) - == json_response["links"]["current"] - ) - assert ( - url_for( - "v2_inbound_sms.get_inbound_sms", - older_than=reversed_inbound_sms[3].id, - _external=True, - ) - == json_response["links"]["next"] - ) - - -def test_get_next_inbound_sms_at_end_will_return_empty_inbound_sms_list( - client, sample_service, mocker -): - inbound_sms = create_inbound_sms(service=sample_service) - mocker.patch.dict( - "app.v2.inbound_sms.get_inbound_sms.current_app.config", {"API_PAGE_SIZE": 1} - ) - - auth_header = create_service_authorization_header(service_id=inbound_sms.service.id) - response = client.get( - path=url_for("v2_inbound_sms.get_inbound_sms", older_than=inbound_sms.id), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - - json_response = json.loads(response.get_data(as_text=True)) - expected_inbound_sms_list = [] - assert json_response["received_text_messages"] == expected_inbound_sms_list - assert ( - url_for("v2_inbound_sms.get_inbound_sms", _external=True) - == json_response["links"]["current"] - ) - assert "next" not in json_response["links"].keys() - - -def test_get_inbound_sms_for_no_inbound_sms_returns_empty_list(client, sample_service): - auth_header = create_service_authorization_header(service_id=sample_service.id) - response = client.get( - path="/v2/received-text-messages", - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True))[ - "received_text_messages" - ] - - expected_response = [] - - assert json_response == expected_response - - -def test_get_inbound_sms_with_invalid_query_string_returns_400(client, sample_service): - auth_header = create_service_authorization_header(service_id=sample_service.id) - response = client.get( - path="/v2/received-text-messages?user_number=447700900000", - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - assert json_response["status_code"] == 400 - assert json_response["errors"][0]["error"] == "ValidationError" - assert ( - json_response["errors"][0]["message"] - == "Additional properties are not allowed (user_number was unexpected)" - ) diff --git a/tests/app/v2/inbound_sms/test_inbound_sms_schemas.py b/tests/app/v2/inbound_sms/test_inbound_sms_schemas.py deleted file mode 100644 index bbf72f4c6..000000000 --- a/tests/app/v2/inbound_sms/test_inbound_sms_schemas.py +++ /dev/null @@ -1,99 +0,0 @@ -import pytest -from flask import json, url_for -from jsonschema.exceptions import ValidationError - -from app.schema_validation import validate -from app.v2.inbound_sms.inbound_sms_schemas import ( - get_inbound_sms_request, - get_inbound_sms_response, - get_inbound_sms_single_response, -) -from tests import create_service_authorization_header -from tests.app.db import create_inbound_sms - -valid_inbound_sms = { - "user_number": "447700900111", - "created_at": "2017-11-02T15:07:57.197546Z", - "service_id": "a5149c32-f03b-4711-af49-ad6993797d45", - "id": "342786aa-23ce-4695-9aad-7f79e68ee29a", - "notify_number": "testing", - "content": "Hello", -} - -valid_inbound_sms_list = { - "received_text_messages": [valid_inbound_sms], - "links": {"current": valid_inbound_sms["id"]}, -} - -invalid_inbound_sms = { - "user_number": "447700900111", - "created_at": "2017-11-02T15:07:57.197546", - "service_id": "a5149c32-f03b-4711-af49-ad6993797d45", - "id": "342786aa-23ce-4695-9aad-7f79e68ee29a", - "notify_number": "testing", -} - -invalid_inbound_sms_list = {"received_text_messages": [invalid_inbound_sms]} - - -def test_get_inbound_sms_contract(client, sample_service): - all_inbound_sms = [ - create_inbound_sms(service=sample_service, user_number="447700900113"), - create_inbound_sms(service=sample_service, user_number="447700900112"), - create_inbound_sms(service=sample_service, user_number="447700900111"), - ] - reversed_inbound_sms = sorted( - all_inbound_sms, key=lambda sms: sms.created_at, reverse=True - ) - - auth_header = create_service_authorization_header( - service_id=all_inbound_sms[0].service_id - ) - response = client.get("/v2/received-text-messages", headers=[auth_header]) - response_json = json.loads(response.get_data(as_text=True)) - - validated_resp = validate(response_json, get_inbound_sms_response) - assert validated_resp["received_text_messages"] == [ - i.serialize() for i in reversed_inbound_sms - ] - assert validated_resp["links"]["current"] == url_for( - "v2_inbound_sms.get_inbound_sms", _external=True - ) - assert validated_resp["links"]["next"] == url_for( - "v2_inbound_sms.get_inbound_sms", - older_than=all_inbound_sms[0].id, - _external=True, - ) - - -@pytest.mark.parametrize( - "request_args", [{"older_than": "6ce466d0-fd6a-11e5-82f5-e0accb9d11a6"}, {}] -) -def test_valid_inbound_sms_request_json(client, request_args): - validate(request_args, get_inbound_sms_request) - - -def test_invalid_inbound_sms_request_json(client): - with pytest.raises(expected_exception=ValidationError): - validate({"user_number": "447700900111"}, get_inbound_sms_request) - - -def test_valid_inbound_sms_response_json(): - assert ( - validate(valid_inbound_sms, get_inbound_sms_single_response) - == valid_inbound_sms - ) - - -def test_valid_inbound_sms_list_response_json(): - validate(valid_inbound_sms_list, get_inbound_sms_response) - - -def test_invalid_inbound_sms_response_json(): - with pytest.raises(expected_exception=ValidationError): - validate(invalid_inbound_sms, get_inbound_sms_single_response) - - -def test_invalid_inbound_sms_list_response_json(): - with pytest.raises(expected_exception=ValidationError): - validate(invalid_inbound_sms_list, get_inbound_sms_response) diff --git a/tests/app/v2/notifications/__init__.py b/tests/app/v2/notifications/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/app/v2/notifications/test_get_notifications.py b/tests/app/v2/notifications/test_get_notifications.py deleted file mode 100644 index dd597404d..000000000 --- a/tests/app/v2/notifications/test_get_notifications.py +++ /dev/null @@ -1,742 +0,0 @@ -import pytest -from flask import json, url_for - -from app.enums import NotificationStatus, NotificationType, TemplateType -from app.utils import DATETIME_FORMAT -from tests import create_service_authorization_header -from tests.app.db import create_notification, create_template - - -@pytest.mark.parametrize( - "billable_units, provider", [(1, "sns"), (0, "sns"), (1, None)] -) -def test_get_notification_by_id_returns_200( - client, billable_units, provider, sample_template, mocker -): - mock_s3_personalisation = mocker.patch( - "app.v2.notifications.get_notifications.get_personalisation_from_s3" - ) - mock_s3_personalisation.return_value = {} - - sample_notification = create_notification( - template=sample_template, - billable_units=billable_units, - sent_by=provider, - ) - - # another - create_notification( - template=sample_template, - billable_units=billable_units, - sent_by=provider, - ) - - auth_header = create_service_authorization_header( - service_id=sample_notification.service_id - ) - response = client.get( - path="/v2/notifications/{}".format(sample_notification.id), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - expected_template_response = { - "id": "{}".format(sample_notification.serialize()["template"]["id"]), - "version": sample_notification.serialize()["template"]["version"], - "uri": sample_notification.serialize()["template"]["uri"], - } - - expected_response = { - "id": "{}".format(sample_notification.id), - "reference": None, - "email_address": None, - "phone_number": "{}".format(sample_notification.to), - "line_1": None, - "line_2": None, - "line_3": None, - "line_4": None, - "line_5": None, - "line_6": None, - "postcode": None, - "type": "{}".format(sample_notification.notification_type), - "status": "{}".format(sample_notification.status), - "template": expected_template_response, - "created_at": sample_notification.created_at.strftime(DATETIME_FORMAT), - "created_by_name": None, - "body": sample_notification.template.content, - "subject": None, - "sent_at": sample_notification.sent_at, - "completed_at": sample_notification.completed_at(), - "scheduled_for": None, - "provider_response": None, - "carrier": None, - } - - assert json_response == expected_response - - -def test_get_notification_by_id_with_placeholders_returns_200( - client, sample_email_template_with_placeholders, mocker -): - mock_s3_personalisation = mocker.patch( - "app.v2.notifications.get_notifications.get_personalisation_from_s3" - ) - mock_s3_personalisation.return_value = {"name": "Bob"} - - sample_notification = create_notification( - template=sample_email_template_with_placeholders, - personalisation={"name": "Bob"}, - ) - - auth_header = create_service_authorization_header( - service_id=sample_notification.service_id - ) - response = client.get( - path="/v2/notifications/{}".format(sample_notification.id), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - expected_template_response = { - "id": "{}".format(sample_notification.serialize()["template"]["id"]), - "version": sample_notification.serialize()["template"]["version"], - "uri": sample_notification.serialize()["template"]["uri"], - } - - expected_response = { - "id": "{}".format(sample_notification.id), - "reference": None, - "email_address": "{}".format(sample_notification.to), - "phone_number": None, - "line_1": None, - "line_2": None, - "line_3": None, - "line_4": None, - "line_5": None, - "line_6": None, - "postcode": None, - "type": "{}".format(sample_notification.notification_type), - "status": "{}".format(sample_notification.status), - "template": expected_template_response, - "created_at": sample_notification.created_at.strftime(DATETIME_FORMAT), - "created_by_name": None, - "body": "Hello Bob\nThis is an email from GOV.UK", - "subject": "Bob", - "sent_at": sample_notification.sent_at, - "completed_at": sample_notification.completed_at(), - "scheduled_for": None, - "provider_response": None, - "carrier": None, - } - - assert json_response == expected_response - - -def test_get_notification_by_reference_returns_200(client, sample_template, mocker): - sample_notification_with_reference = create_notification( - template=sample_template, client_reference="some-client-reference" - ) - - mock_s3_personalisation = mocker.patch( - "app.v2.notifications.get_notifications.get_personalisation_from_s3" - ) - mock_s3_personalisation.return_value = {} - - auth_header = create_service_authorization_header( - service_id=sample_notification_with_reference.service_id - ) - response = client.get( - path="/v2/notifications?reference={}".format( - sample_notification_with_reference.client_reference - ), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - assert len(json_response["notifications"]) == 1 - - assert json_response["notifications"][0]["id"] == str( - sample_notification_with_reference.id - ) - assert json_response["notifications"][0]["reference"] == "some-client-reference" - - -def test_get_notification_by_id_returns_created_by_name_if_notification_created_by_id( - client, sample_user, sample_template, mocker -): - mock_s3_personalisation = mocker.patch( - "app.v2.notifications.get_notifications.get_personalisation_from_s3" - ) - mock_s3_personalisation.return_value = {"name": "Bob"} - - sms_notification = create_notification(template=sample_template) - sms_notification.created_by_id = sample_user.id - - auth_header = create_service_authorization_header( - service_id=sms_notification.service_id - ) - response = client.get( - path=url_for( - "v2_notifications.get_notification_by_id", - notification_id=sms_notification.id, - ), - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = response.get_json() - assert json_response["created_by_name"] == "Test User" - - -def test_get_notification_by_reference_nonexistent_reference_returns_no_notifications( - client, sample_service -): - auth_header = create_service_authorization_header(service_id=sample_service.id) - response = client.get( - path="/v2/notifications?reference={}".format("nonexistent-reference"), - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert len(json_response["notifications"]) == 0 - - -def test_get_notification_by_id_nonexistent_id(client, sample_notification): - auth_header = create_service_authorization_header( - service_id=sample_notification.service_id - ) - response = client.get( - path="/v2/notifications/dd4b8b9d-d414-4a83-9256-580046bf18f9", - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 404 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - assert json_response == { - "errors": [{"error": "NoResultFound", "message": "No result found"}], - "status_code": 404, - } - - -@pytest.mark.parametrize("id", ["1234-badly-formatted-id-7890", "0"]) -def test_get_notification_by_id_invalid_id(client, sample_notification, id): - auth_header = create_service_authorization_header( - service_id=sample_notification.service_id - ) - response = client.get( - path="/v2/notifications/{}".format(id), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - assert json_response == { - "errors": [ - { - "error": "ValidationError", - "message": "notification_id is not a valid UUID", - } - ], - "status_code": 400, - } - - -@pytest.mark.parametrize("template_type", [TemplateType.SMS, TemplateType.EMAIL]) -def test_get_notification_doesnt_have_delivery_estimate_for_non_letters( - client, sample_service, template_type, mocker -): - mock_s3_personalisation = mocker.patch( - "app.v2.notifications.get_notifications.get_personalisation_from_s3" - ) - mock_s3_personalisation.return_value = {"name": "Bob"} - - template = create_template(service=sample_service, template_type=template_type) - mocked_notification = create_notification(template=template) - - auth_header = create_service_authorization_header( - service_id=mocked_notification.service_id - ) - response = client.get( - path="/v2/notifications/{}".format(mocked_notification.id), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 200 - assert "estimated_delivery" not in json.loads(response.get_data(as_text=True)) - - -def test_get_all_notifications_except_job_notifications_returns_200( - client, sample_template, sample_job -): - create_notification( - template=sample_template, job=sample_job - ) # should not return this job notification - notifications = [create_notification(template=sample_template) for _ in range(2)] - notification = notifications[-1] - - auth_header = create_service_authorization_header( - service_id=notification.service_id - ) - response = client.get( - path="/v2/notifications", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert json_response["links"]["current"].endswith("/v2/notifications") - assert "next" in json_response["links"].keys() - assert len(json_response["notifications"]) == 2 - - assert json_response["notifications"][0]["id"] == str(notification.id) - assert json_response["notifications"][0]["status"] == NotificationStatus.CREATED - assert json_response["notifications"][0]["template"] == { - "id": str(notification.template.id), - "uri": notification.template.get_link(), - "version": 1, - } - assert json_response["notifications"][0]["phone_number"] == "1" - assert json_response["notifications"][0]["type"] == NotificationType.SMS - assert not json_response["notifications"][0]["scheduled_for"] - - -def test_get_all_notifications_with_include_jobs_arg_returns_200( - client, sample_template, sample_job, mocker -): - mock_s3_personalisation = mocker.patch( - "app.v2.notifications.get_notifications.get_personalisation_from_s3" - ) - mock_s3_personalisation.return_value = {} - - notifications = [ - create_notification(template=sample_template, job=sample_job), - create_notification(template=sample_template), - ] - notification = notifications[-1] - - auth_header = create_service_authorization_header( - service_id=notification.service_id - ) - response = client.get( - path="/v2/notifications?include_jobs=true", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert json_response["links"]["current"].endswith( - "/v2/notifications?include_jobs=true" - ) - assert "next" in json_response["links"].keys() - assert len(json_response["notifications"]) == 2 - - assert json_response["notifications"][0]["id"] == str(notification.id) - assert json_response["notifications"][0]["status"] == notification.status - assert "1" == notification.to - assert ( - json_response["notifications"][0]["type"] == notification.template.template_type - ) - assert not json_response["notifications"][0]["scheduled_for"] - - -def test_get_all_notifications_no_notifications_if_no_notifications( - client, sample_service -): - auth_header = create_service_authorization_header(service_id=sample_service.id) - response = client.get( - path="/v2/notifications", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert json_response["links"]["current"].endswith("/v2/notifications") - assert "next" not in json_response["links"].keys() - assert len(json_response["notifications"]) == 0 - - -def test_get_all_notifications_filter_by_template_type(client, sample_service): - email_template = create_template( - service=sample_service, template_type=TemplateType.EMAIL - ) - sms_template = create_template( - service=sample_service, template_type=TemplateType.SMS - ) - - notification = create_notification( - template=email_template, to_field="don.draper@scdp.biz" - ) - create_notification(template=sms_template) - - auth_header = create_service_authorization_header( - service_id=notification.service_id - ) - response = client.get( - path="/v2/notifications?template_type=email", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert json_response["links"]["current"].endswith( - "/v2/notifications?template_type=email" - ) - assert "next" in json_response["links"].keys() - assert len(json_response["notifications"]) == 1 - - assert json_response["notifications"][0]["id"] == str(notification.id) - assert json_response["notifications"][0]["status"] == NotificationStatus.CREATED - assert json_response["notifications"][0]["template"] == { - "id": str(email_template.id), - "uri": notification.template.get_link(), - "version": 1, - } - assert json_response["notifications"][0]["email_address"] == "1" - assert json_response["notifications"][0]["type"] == NotificationType.EMAIL - - -def test_get_all_notifications_filter_by_template_type_invalid_template_type( - client, sample_notification -): - auth_header = create_service_authorization_header( - service_id=sample_notification.service_id - ) - response = client.get( - path="/v2/notifications?template_type=orange", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 400 - assert response.headers["Content-type"] == "application/json" - - assert json_response["status_code"] == 400 - assert len(json_response["errors"]) == 1 - type_str = ", ".join( - [f"<{type(e).__name__}.{e.name}: {e.value}>" for e in TemplateType] - ) - assert ( - json_response["errors"][0]["message"] - == f"template_type orange is not one of [{type_str}]" - ) - - -def test_get_all_notifications_filter_by_single_status(client, sample_template): - notification = create_notification( - template=sample_template, - status=NotificationStatus.PENDING, - ) - create_notification(template=sample_template) - - auth_header = create_service_authorization_header( - service_id=notification.service_id - ) - response = client.get( - path="/v2/notifications?status=pending", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert json_response["links"]["current"].endswith( - "/v2/notifications?status=pending" - ) - assert "next" in json_response["links"].keys() - assert len(json_response["notifications"]) == 1 - - assert json_response["notifications"][0]["id"] == str(notification.id) - assert json_response["notifications"][0]["status"] == NotificationStatus.PENDING - - -def test_get_all_notifications_filter_by_status_invalid_status( - client, sample_notification -): - auth_header = create_service_authorization_header( - service_id=sample_notification.service_id - ) - response = client.get( - path="/v2/notifications?status=elephant", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 400 - assert response.headers["Content-type"] == "application/json" - - assert json_response["status_code"] == 400 - assert len(json_response["errors"]) == 1 - type_str = ", ".join( - [f"<{type(e).__name__}.{e.name}: {e.value}>" for e in NotificationStatus] - ) - assert ( - json_response["errors"][0]["message"] - == f"status elephant is not one of [{type_str}]" - ) - - -def test_get_all_notifications_filter_by_multiple_statuses(client, sample_template): - notifications = [ - create_notification(template=sample_template, status=_status) - for _status in [ - NotificationStatus.CREATED, - NotificationStatus.PENDING, - NotificationStatus.SENDING, - ] - ] - failed_notification = create_notification( - template=sample_template, - status=NotificationStatus.PERMANENT_FAILURE, - ) - - auth_header = create_service_authorization_header( - service_id=notifications[0].service_id - ) - response = client.get( - path="/v2/notifications?status=created&status=pending&status=sending", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert json_response["links"]["current"].endswith( - "/v2/notifications?status=created&status=pending&status=sending" - ) - assert "next" in json_response["links"].keys() - assert len(json_response["notifications"]) == 3 - - returned_notification_ids = [_n["id"] for _n in json_response["notifications"]] - for _id in [_notification.id for _notification in notifications]: - assert str(_id) in returned_notification_ids - - assert failed_notification.id not in returned_notification_ids - - -def test_get_all_notifications_filter_by_failed_status(client, sample_template): - created_notification = create_notification( - template=sample_template, - status=NotificationStatus.CREATED, - ) - failed_notifications = [ - create_notification(template=sample_template, status=NotificationStatus.FAILED) - ] - auth_header = create_service_authorization_header( - service_id=created_notification.service_id - ) - response = client.get( - path="/v2/notifications?status=failed", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert json_response["links"]["current"].endswith("/v2/notifications?status=failed") - assert "next" in json_response["links"].keys() - assert len(json_response["notifications"]) == 1 - - returned_notification_ids = [n["id"] for n in json_response["notifications"]] - for _id in [_notification.id for _notification in failed_notifications]: - assert str(_id) in returned_notification_ids - - assert created_notification.id not in returned_notification_ids - - -def test_get_all_notifications_filter_by_id(client, sample_template): - older_notification = create_notification(template=sample_template) - newer_notification = create_notification(template=sample_template) - - auth_header = create_service_authorization_header( - service_id=newer_notification.service_id - ) - response = client.get( - path="/v2/notifications?older_than={}".format(newer_notification.id), - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert json_response["links"]["current"].endswith( - "/v2/notifications?older_than={}".format(newer_notification.id) - ) - assert "next" in json_response["links"].keys() - assert len(json_response["notifications"]) == 1 - - assert json_response["notifications"][0]["id"] == str(older_notification.id) - - -def test_get_all_notifications_filter_by_id_invalid_id(client, sample_notification): - auth_header = create_service_authorization_header( - service_id=sample_notification.service_id - ) - response = client.get( - path="/v2/notifications?older_than=1234-badly-formatted-id-7890", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert json_response["status_code"] == 400 - assert len(json_response["errors"]) == 1 - assert json_response["errors"][0]["message"] == "older_than is not a valid UUID" - - -def test_get_all_notifications_filter_by_id_no_notifications_if_nonexistent_id( - client, sample_template -): - notification = create_notification(template=sample_template) - - auth_header = create_service_authorization_header( - service_id=notification.service_id - ) - response = client.get( - path="/v2/notifications?older_than=dd4b8b9d-d414-4a83-9256-580046bf18f9", - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert json_response["links"]["current"].endswith( - "/v2/notifications?older_than=dd4b8b9d-d414-4a83-9256-580046bf18f9" - ) - assert "next" not in json_response["links"].keys() - assert len(json_response["notifications"]) == 0 - - -def test_get_all_notifications_filter_by_id_no_notifications_if_last_notification( - client, sample_template -): - notification = create_notification(template=sample_template) - - auth_header = create_service_authorization_header( - service_id=notification.service_id - ) - response = client.get( - path="/v2/notifications?older_than={}".format(notification.id), - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - assert json_response["links"]["current"].endswith( - "/v2/notifications?older_than={}".format(notification.id) - ) - assert "next" not in json_response["links"].keys() - assert len(json_response["notifications"]) == 0 - - -def test_get_all_notifications_filter_multiple_query_parameters( - client, sample_email_template -): - # TODO had to change pending to sending. Is that correct? - # this is the notification we are looking for - older_notification = create_notification( - template=sample_email_template, - status=NotificationStatus.SENDING, - ) - - # wrong status - create_notification(template=sample_email_template) - wrong_template = create_template( - sample_email_template.service, template_type=TemplateType.SMS - ) - # wrong template - create_notification(template=wrong_template, status=NotificationStatus.SENDING) - - # we only want notifications created before this one - newer_notification = create_notification(template=sample_email_template) - - # this notification was created too recently - create_notification( - template=sample_email_template, - status=NotificationStatus.SENDING, - ) - - auth_header = create_service_authorization_header( - service_id=newer_notification.service_id - ) - response = client.get( - path="/v2/notifications?status=sending&template_type=email&older_than={}".format( - newer_notification.id - ), - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - # query parameters aren't returned in order - for url_part in [ - "/v2/notifications?", - "template_type=email", - "status=sending", - "older_than={}".format(newer_notification.id), - ]: - assert url_part in json_response["links"]["current"] - - assert "next" in json_response["links"].keys() - assert len(json_response["notifications"]) == 1 - - assert json_response["notifications"][0]["id"] == str(older_notification.id) - - -def test_get_all_notifications_renames_letter_statuses( - client, - sample_notification, - sample_email_notification, -): - auth_header = create_service_authorization_header( - service_id=sample_email_notification.service_id - ) - response = client.get( - path=url_for("v2_notifications.get_notifications"), - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - assert response.status_code == 200 - - for noti in json_response["notifications"]: - if ( - noti["type"] == NotificationType.SMS - or noti["type"] == NotificationType.EMAIL - ): - assert noti["status"] == NotificationStatus.CREATED - else: - pytest.fail() diff --git a/tests/app/v2/notifications/test_notification_schemas.py b/tests/app/v2/notifications/test_notification_schemas.py deleted file mode 100644 index 253faeaae..000000000 --- a/tests/app/v2/notifications/test_notification_schemas.py +++ /dev/null @@ -1,381 +0,0 @@ -import uuid - -import pytest -from flask import json -from freezegun import freeze_time -from jsonschema import ValidationError - -from app.enums import NotificationStatus, TemplateType -from app.schema_validation import validate -from app.v2.notifications.notification_schemas import get_notifications_request -from app.v2.notifications.notification_schemas import ( - post_email_request as post_email_request_schema, -) -from app.v2.notifications.notification_schemas import ( - post_sms_request as post_sms_request_schema, -) - -valid_get_json = {} - -valid_get_with_optionals_json = { - "reference": "test reference", - "status": [NotificationStatus.CREATED], - "template_type": [TemplateType.EMAIL], - "include_jobs": "true", - "older_than": "a5149c32-f03b-4711-af49-ad6993797d45", -} - - -@pytest.mark.parametrize("input", [valid_get_json, valid_get_with_optionals_json]) -def test_get_notifications_valid_json(input): - assert validate(input, get_notifications_request) == input - - -@pytest.mark.parametrize( - "invalid_statuses, valid_statuses", - [ - # one invalid status - (["elephant"], []), - # multiple invalid statuses - (["elephant", "giraffe", "cheetah"], []), - # one bad status and one good status - (["elephant"], [NotificationStatus.CREATED]), - ], -) -def test_get_notifications_request_invalid_statuses(invalid_statuses, valid_statuses): - type_str = ", ".join( - [f"<{type(e).__name__}.{e.name}: {e.value}>" for e in NotificationStatus] - ) - partial_error_status = f"is not one of [{type_str}]" - - with pytest.raises(ValidationError) as e: - validate( - {"status": invalid_statuses + valid_statuses}, get_notifications_request - ) - - errors = json.loads(str(e.value)).get("errors") - assert len(errors) == len(invalid_statuses) - for index, value in enumerate(invalid_statuses): - assert errors[index]["message"] == f"status {value} {partial_error_status}" - - -@pytest.mark.parametrize( - "invalid_template_types, valid_template_types", - [ - # one invalid template_type - (["orange"], []), - # multiple invalid template_types - (["orange", "avocado", "banana"], []), - # one bad template_type and one good template_type - (["orange"], [TemplateType.SMS]), - ], -) -def test_get_notifications_request_invalid_template_types( - invalid_template_types, valid_template_types -): - type_str = ", ".join( - [f"<{type(e).__name__}.{e.name}: {e.value}>" for e in TemplateType] - ) - partial_error_template_type = f"is not one of [{type_str}]" - - with pytest.raises(ValidationError) as e: - validate( - {"template_type": invalid_template_types + valid_template_types}, - get_notifications_request, - ) - - errors = json.loads(str(e.value)).get("errors") - assert len(errors) == len(invalid_template_types) - for index, value in enumerate(invalid_template_types): - assert errors[index]["message"] == ( - f"template_type {value} {partial_error_template_type}" - ) - - -def test_get_notifications_request_invalid_statuses_and_template_types(): - with pytest.raises(ValidationError) as e: - validate( - { - "status": [NotificationStatus.CREATED, "elephant", "giraffe"], - "template_type": [TemplateType.SMS, "orange", "avocado"], - }, - get_notifications_request, - ) - - errors = json.loads(str(e.value)).get("errors") - - assert len(errors) == 4 - - error_messages = [error["message"] for error in errors] - type_str = ", ".join( - [f"<{type(e).__name__}.{e.name}: {e.value}>" for e in NotificationStatus] - ) - for invalid_status in ["elephant", "giraffe"]: - assert f"status {invalid_status} is not one of [{type_str}]" in error_messages - - type_str = ", ".join( - [f"<{type(e).__name__}.{e.name}: {e.value}>" for e in TemplateType] - ) - for invalid_template_type in ["orange", "avocado"]: - assert ( - f"template_type {invalid_template_type} is not one of [{type_str}]" - in error_messages - ) - - -valid_json = {"phone_number": "2028675309", "template_id": str(uuid.uuid4())} -valid_json_with_optionals = { - "phone_number": "2028675309", - "template_id": str(uuid.uuid4()), - "reference": "reference from caller", - "personalisation": {"key": "value"}, -} - - -@pytest.mark.parametrize("input", [valid_json, valid_json_with_optionals]) -def test_post_sms_schema_is_valid(input): - assert validate(input, post_sms_request_schema) == input - - -@pytest.mark.parametrize( - "template_id", - [ - "2ebe4da8-17be-49fe-b02f-dff2760261a0" + "\n", - "2ebe4da8-17be-49fe-b02f-dff2760261a0" + " ", - "2ebe4da8-17be-49fe-b02f-dff2760261a0" + "\r", - "\t" + "2ebe4da8-17be-49fe-b02f-dff2760261a0", - "2ebe4da8-17be-49fe-b02f-dff2760261a0"[4:], - "bad_uuid", - ], -) -def test_post_sms_json_schema_bad_uuid(template_id): - j = {"template_id": template_id, "phone_number": "2028675309"} - with pytest.raises(ValidationError) as e: - validate(j, post_sms_request_schema) - error = json.loads(str(e.value)) - assert len(error.keys()) == 2 - assert error.get("status_code") == 400 - assert len(error.get("errors")) == 1 - assert { - "error": "ValidationError", - "message": "template_id is not a valid UUID", - } in error["errors"] - - -def test_post_sms_json_schema_bad_uuid_and_missing_phone_number(): - j = {"template_id": "notUUID"} - with pytest.raises(ValidationError) as e: - validate(j, post_sms_request_schema) - error = json.loads(str(e.value)) - assert len(error.keys()) == 2 - assert error.get("status_code") == 400 - assert len(error.get("errors")) == 2 - assert { - "error": "ValidationError", - "message": "phone_number is a required property", - } in error["errors"] - assert { - "error": "ValidationError", - "message": "template_id is not a valid UUID", - } in error["errors"] - - -def test_post_sms_schema_with_personalisation_that_is_not_a_dict(): - j = { - "phone_number": "2028675309", - "template_id": str(uuid.uuid4()), - "reference": "reference from caller", - "personalisation": "not_a_dict", - } - with pytest.raises(ValidationError) as e: - validate(j, post_sms_request_schema) - error = json.loads(str(e.value)) - assert len(error.get("errors")) == 1 - assert error["errors"] == [ - { - "error": "ValidationError", - "message": "personalisation not_a_dict is not of type object", - } - ] - assert error.get("status_code") == 400 - assert len(error.keys()) == 2 - - -@pytest.mark.parametrize( - "invalid_phone_number, err_msg", - [ - ("08515111111", "phone_number Phone number is not possible"), - ("07515111*11", "phone_number Not enough digits"), - ( - "notaphoneumber", - "phone_number The string supplied did not seem to be a phone number.", - ), - (7700900001, "phone_number 7700900001 is not of type string"), - (None, "phone_number None is not of type string"), - ([], "phone_number [] is not of type string"), - ({}, "phone_number {} is not of type string"), - ], -) -def test_post_sms_request_schema_invalid_phone_number(invalid_phone_number, err_msg): - j = {"phone_number": invalid_phone_number, "template_id": str(uuid.uuid4())} - with pytest.raises(ValidationError) as e: - validate(j, post_sms_request_schema) - errors = json.loads(str(e.value)).get("errors") - assert len(errors) == 1 - assert {"error": "ValidationError", "message": err_msg} == errors[0] - - -def test_post_sms_request_schema_invalid_phone_number_and_missing_template(): - j = { - "phone_number": "5558675309", - } - with pytest.raises(ValidationError) as e: - validate(j, post_sms_request_schema) - errors = json.loads(str(e.value)).get("errors") - assert len(errors) == 2 - assert { - "error": "ValidationError", - "message": "phone_number Phone number range is not in use", - } in errors - assert { - "error": "ValidationError", - "message": "template_id is a required property", - } in errors - - -valid_post_email_json = { - "email_address": "test@example.gov.uk", - "template_id": str(uuid.uuid4()), -} -valid_post_email_json_with_optionals = { - "email_address": "test@example.gov.uk", - "template_id": str(uuid.uuid4()), - "reference": "reference from caller", - "personalisation": {"key": "value"}, -} - - -@pytest.mark.parametrize( - "input", [valid_post_email_json, valid_post_email_json_with_optionals] -) -def test_post_email_schema_is_valid(input): - assert validate(input, post_email_request_schema) == input - - -def test_post_email_schema_bad_uuid_and_missing_email_address(): - j = {"template_id": "bad_template"} - with pytest.raises(ValidationError): - validate(j, post_email_request_schema) - - -@pytest.mark.parametrize( - "email_address, err_msg", - [ - ("example", "email_address Not a valid email address"), - (12345, "email_address 12345 is not of type string"), - ("with(brackets)@example.com", "email_address Not a valid email address"), - (None, "email_address None is not of type string"), - ([], "email_address [] is not of type string"), - ({}, "email_address {} is not of type string"), - ], -) -def test_post_email_schema_invalid_email_address(email_address, err_msg): - j = {"template_id": str(uuid.uuid4()), "email_address": email_address} - with pytest.raises(ValidationError) as e: - validate(j, post_email_request_schema) - - errors = json.loads(str(e.value)).get("errors") - assert len(errors) == 1 - assert {"error": "ValidationError", "message": err_msg} == errors[0] - - -def valid_email_response(): - return { - "id": str(uuid.uuid4()), - "content": { - "body": "the body of the message", - "subject": "subject of the message", - "from_email": "service@dig.gov.uk", - }, - "uri": "http://notify.api/v2/notifications/id", - "template": { - "id": str(uuid.uuid4()), - "version": 1, - "uri": "http://notify.api/v2/template/id", - }, - "scheduled_for": "", - } - - -@pytest.mark.parametrize("schema", [post_email_request_schema, post_sms_request_schema]) -@freeze_time("2017-05-12 13:00:00") -def test_post_schema_valid_scheduled_for(schema): - j = {"template_id": str(uuid.uuid4()), "scheduled_for": "2017-05-12 13:15"} - if schema == post_email_request_schema: - j.update({"email_address": "joe@gmail.com"}) - else: - j.update({"phone_number": "2028675309"}) - assert validate(j, schema) == j - - -@pytest.mark.parametrize( - "invalid_datetime", - ["13:00:00 2017-01-01", "2017-31-12 13:00:00", "01-01-2017T14:00:00.0000Z"], -) -@pytest.mark.parametrize("schema", [post_email_request_schema, post_sms_request_schema]) -def test_post_email_schema_invalid_scheduled_for(invalid_datetime, schema): - j = {"template_id": str(uuid.uuid4()), "scheduled_for": invalid_datetime} - if schema == post_email_request_schema: - j.update({"email_address": "joe@gmail.com"}) - else: - j.update({"phone_number": "2028675309"}) - with pytest.raises(ValidationError) as e: - validate(j, schema) - error = json.loads(str(e.value)) - assert error["status_code"] == 400 - assert error["errors"] == [ - { - "error": "ValidationError", - "message": "scheduled_for datetime format is invalid. " - "It must be a valid ISO8601 date time format, " - "https://en.wikipedia.org/wiki/ISO_8601", - } - ] - - -@freeze_time("2017-05-12 13:00:00") -def test_scheduled_for_raises_validation_error_when_in_the_past(): - j = { - "phone_number": "2028675309", - "template_id": str(uuid.uuid4()), - "scheduled_for": "2017-05-12 10:00", - } - with pytest.raises(ValidationError) as e: - validate(j, post_sms_request_schema) - error = json.loads(str(e.value)) - assert error["status_code"] == 400 - assert error["errors"] == [ - { - "error": "ValidationError", - "message": "scheduled_for datetime can not be in the past", - } - ] - - -@freeze_time("2017-05-12 13:00:00") -def test_scheduled_for_raises_validation_error_when_more_than_24_hours_in_the_future(): - j = { - "phone_number": "2028675309", - "template_id": str(uuid.uuid4()), - "scheduled_for": "2017-05-13 14:00", - } - with pytest.raises(ValidationError) as e: - validate(j, post_sms_request_schema) - error = json.loads(str(e.value)) - assert error["status_code"] == 400 - assert error["errors"] == [ - { - "error": "ValidationError", - "message": "scheduled_for datetime can only be 24 hours in the future", - } - ] diff --git a/tests/app/v2/notifications/test_post_notifications.py b/tests/app/v2/notifications/test_post_notifications.py deleted file mode 100644 index 13cb579e3..000000000 --- a/tests/app/v2/notifications/test_post_notifications.py +++ /dev/null @@ -1,1431 +0,0 @@ -import uuid -from unittest import mock -from unittest.mock import call - -import botocore -import pytest -from flask import current_app, json - -from app.dao import templates_dao -from app.dao.service_sms_sender_dao import dao_update_service_sms_sender -from app.enums import ( - KeyType, - NotificationStatus, - NotificationType, - ServicePermissionType, - TemplateProcessType, - TemplateType, -) -from app.models import Notification -from app.schema_validation import validate -from app.v2.errors import RateLimitError -from app.v2.notifications.notification_schemas import ( - post_email_response, - post_sms_response, -) -from tests import create_service_authorization_header -from tests.app.db import ( - create_api_key, - create_reply_to_email, - create_service, - create_service_sms_sender, - create_service_with_inbound_number, - create_template, -) -from tests.conftest import set_config_values - - -@pytest.mark.parametrize("reference", [None, "reference_from_client"]) -def test_post_sms_notification_returns_201( - client, sample_template_with_placeholders, mocker, reference -): - mocked = mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - data = { - "phone_number": "+12028675309", - "template_id": str(sample_template_with_placeholders.id), - "personalisation": {" Name": "Jo"}, - } - if reference: - data.update({"reference": reference}) - auth_header = create_service_authorization_header( - service_id=sample_template_with_placeholders.service_id - ) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 201 - resp_json = json.loads(response.get_data(as_text=True)) - assert validate(resp_json, post_sms_response) == resp_json - notifications = Notification.query.all() - assert len(notifications) == 1 - assert notifications[0].status == NotificationStatus.CREATED - notification_id = notifications[0].id - assert notifications[0].document_download_count is None - assert resp_json["id"] == str(notification_id) - assert resp_json["reference"] == reference - assert resp_json["content"][ - "body" - ] == sample_template_with_placeholders.content.replace("(( Name))", "Jo") - assert resp_json["content"]["from_number"] == current_app.config["FROM_NUMBER"] - assert f"v2/notifications/{notification_id}" in resp_json["uri"] - assert resp_json["template"]["id"] == str(sample_template_with_placeholders.id) - assert resp_json["template"]["version"] == sample_template_with_placeholders.version - assert ( - f"services/{sample_template_with_placeholders.service_id}/templates/" - f"{sample_template_with_placeholders.id}" - ) in resp_json["template"]["uri"] - assert not resp_json["scheduled_for"] - assert mocked.called - - -def test_post_sms_notification_uses_inbound_number_as_sender( - client, notify_db_session, mocker -): - service = create_service_with_inbound_number(inbound_number="1") - - template = create_template( - service=service, content="Hello (( Name))\nYour thing is due soon" - ) - mocked = mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - data = { - "phone_number": "+12028675309", - "template_id": str(template.id), - "personalisation": {" Name": "Jo"}, - } - auth_header = create_service_authorization_header(service_id=service.id) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 201 - resp_json = json.loads(response.get_data(as_text=True)) - assert validate(resp_json, post_sms_response) == resp_json - notifications = Notification.query.all() - assert len(notifications) == 1 - notification_id = notifications[0].id - assert resp_json["id"] == str(notification_id) - assert resp_json["content"]["from_number"] == "1" - assert notifications[0].reply_to_text == "1" - mocked.assert_called_once_with([str(notification_id)], queue="send-sms-tasks") - - -def test_post_sms_notification_uses_inbound_number_reply_to_as_sender( - client, notify_db_session, mocker -): - service = create_service_with_inbound_number(inbound_number="2028675309") - - template = create_template( - service=service, content="Hello (( Name))\nYour thing is due soon" - ) - mocked = mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - data = { - "phone_number": "+12028675309", - "template_id": str(template.id), - "personalisation": {" Name": "Jo"}, - } - auth_header = create_service_authorization_header(service_id=service.id) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 201 - resp_json = json.loads(response.get_data(as_text=True)) - assert validate(resp_json, post_sms_response) == resp_json - notifications = Notification.query.all() - assert len(notifications) == 1 - notification_id = notifications[0].id - assert resp_json["id"] == str(notification_id) - assert resp_json["content"]["from_number"] == "+12028675309" - assert notifications[0].reply_to_text == "+12028675309" - mocked.assert_called_once_with([str(notification_id)], queue="send-sms-tasks") - - -def test_post_sms_notification_returns_201_with_sms_sender_id( - client, sample_template_with_placeholders, mocker -): - sms_sender = create_service_sms_sender( - service=sample_template_with_placeholders.service, sms_sender="123456" - ) - mocked = mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - data = { - "phone_number": "+12028675309", - "template_id": str(sample_template_with_placeholders.id), - "personalisation": {" Name": "Jo"}, - "sms_sender_id": str(sms_sender.id), - } - auth_header = create_service_authorization_header( - service_id=sample_template_with_placeholders.service_id - ) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 201 - resp_json = json.loads(response.get_data(as_text=True)) - assert validate(resp_json, post_sms_response) == resp_json - assert resp_json["content"]["from_number"] == sms_sender.sms_sender - notifications = Notification.query.all() - assert len(notifications) == 1 - assert notifications[0].reply_to_text == sms_sender.sms_sender - mocked.assert_called_once_with([resp_json["id"]], queue="send-sms-tasks") - - -def test_post_sms_notification_uses_sms_sender_id_reply_to( - client, sample_template_with_placeholders, mocker -): - sms_sender = create_service_sms_sender( - service=sample_template_with_placeholders.service, sms_sender="2028675309" - ) - mocked = mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - data = { - "phone_number": "+12028675309", - "template_id": str(sample_template_with_placeholders.id), - "personalisation": {" Name": "Jo"}, - "sms_sender_id": str(sms_sender.id), - } - auth_header = create_service_authorization_header( - service_id=sample_template_with_placeholders.service_id - ) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 201 - resp_json = json.loads(response.get_data(as_text=True)) - assert validate(resp_json, post_sms_response) == resp_json - assert resp_json["content"]["from_number"] == "+12028675309" - notifications = Notification.query.all() - assert len(notifications) == 1 - assert notifications[0].reply_to_text == "+12028675309" - mocked.assert_called_once_with([resp_json["id"]], queue="send-sms-tasks") - - -def test_notification_reply_to_text_is_original_value_if_sender_is_changed_after_post_notification( - client, sample_template, mocker -): - sms_sender = create_service_sms_sender( - service=sample_template.service, sms_sender="123456", is_default=False - ) - mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - data = { - "phone_number": "+12028675309", - "template_id": str(sample_template.id), - "sms_sender_id": str(sms_sender.id), - } - auth_header = create_service_authorization_header( - service_id=sample_template.service_id - ) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - dao_update_service_sms_sender( - service_id=sample_template.service_id, - service_sms_sender_id=sms_sender.id, - is_default=sms_sender.is_default, - sms_sender="updated", - ) - - assert response.status_code == 201 - notifications = Notification.query.all() - assert len(notifications) == 1 - assert notifications[0].reply_to_text == "123456" - - -def test_should_cache_template_lookups_in_memory(mocker, client, sample_template): - mock_get_template = mocker.patch( - "app.dao.templates_dao.dao_get_template_by_id_and_service_id", - wraps=templates_dao.dao_get_template_by_id_and_service_id, - ) - mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - - data = { - "phone_number": "2028675309", - "template_id": str(sample_template.id), - } - - for _ in range(5): - auth_header = create_service_authorization_header( - service_id=sample_template.service_id - ) - client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert mock_get_template.call_count == 1 - assert mock_get_template.call_args_list == [ - call( - service_id=str(sample_template.service_id), - template_id=str(sample_template.id), - version=None, - ) - ] - assert Notification.query.count() == 5 - - -def test_should_cache_template_and_service_in_redis(mocker, client, sample_template): - from app.schemas import service_schema, template_schema - - mock_redis_get = mocker.patch( - "app.redis_store.get", - return_value=None, - ) - mock_redis_set = mocker.patch( - "app.redis_store.set", - ) - - mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - - data = { - "phone_number": "+12028675309", - "template_id": str(sample_template.id), - } - - auth_header = create_service_authorization_header( - service_id=sample_template.service_id - ) - client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - expected_service_key = f"service-{sample_template.service_id}" - expected_templates_key = f"service-{sample_template.service_id}-template-{sample_template.id}-version-None" - - assert mock_redis_get.call_args_list == [ - call(expected_service_key), - call(expected_templates_key), - ] - - service_dict = service_schema.dump(sample_template.service) - template_dict = template_schema.dump(sample_template) - - assert len(mock_redis_set.call_args_list) == 2 - - service_call, templates_call = mock_redis_set.call_args_list - - assert service_call[0][0] == expected_service_key - assert json.loads(service_call[0][1]) == {"data": service_dict} - assert service_call[1]["ex"] == 604_800 - - assert templates_call[0][0] == expected_templates_key - assert json.loads(templates_call[0][1]) == {"data": template_dict} - assert templates_call[1]["ex"] == 604_800 - - -def test_should_return_template_if_found_in_redis(mocker, client, sample_template): - from app.schemas import service_schema, template_schema - - service_dict = service_schema.dump(sample_template.service) - template_dict = template_schema.dump(sample_template) - - mocker.patch( - "app.redis_store.get", - side_effect=[ - json.dumps({"data": service_dict}).encode("utf-8"), - json.dumps({"data": template_dict}).encode("utf-8"), - ], - ) - mock_get_template = mocker.patch( - "app.dao.templates_dao.dao_get_template_by_id_and_service_id" - ) - mock_get_service = mocker.patch("app.dao.services_dao.dao_fetch_service_by_id") - - mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - - data = { - "phone_number": "+16615555555", - "template_id": str(sample_template.id), - } - - auth_header = create_service_authorization_header( - service_id=sample_template.service_id - ) - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 201 - assert mock_get_template.called is False - assert mock_get_service.called is False - - -@pytest.mark.parametrize( - "notification_type, key_send_to, send_to", - [ - (NotificationType.SMS, "phone_number", "+12028675309"), - (NotificationType.EMAIL, "email_address", "sample@email.com"), - ], -) -def test_post_notification_returns_400_and_missing_template( - client, sample_service, notification_type, key_send_to, send_to -): - data = {key_send_to: send_to, "template_id": str(uuid.uuid4())} - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400 - assert response.headers["Content-type"] == "application/json" - - error_json = json.loads(response.get_data(as_text=True)) - assert error_json["status_code"] == 400 - assert error_json["errors"] == [ - {"error": "BadRequestError", "message": "Template not found"} - ] - - -@pytest.mark.parametrize( - "notification_type, key_send_to, send_to", - [ - (NotificationType.SMS, "phone_number", "+12028675309"), - (NotificationType.EMAIL, "email_address", "sample@email.com"), - ], -) -def test_post_notification_returns_401_and_well_formed_auth_error( - client, sample_template, notification_type, key_send_to, send_to -): - data = {key_send_to: send_to, "template_id": str(sample_template.id)} - - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[("Content-Type", "application/json")], - ) - - assert response.status_code == 401 - assert response.headers["Content-type"] == "application/json" - error_resp = json.loads(response.get_data(as_text=True)) - assert error_resp["status_code"] == 401 - assert error_resp["errors"] == [ - { - "error": "AuthError", - "message": "Unauthorized: authentication token must be provided", - } - ] - - -@pytest.mark.parametrize( - "notification_type, key_send_to, send_to", - [ - (NotificationType.SMS, "phone_number", "+12028675309"), - (NotificationType.EMAIL, "email_address", "sample@email.com"), - ], -) -def test_notification_returns_400_and_for_schema_problems( - client, sample_template, notification_type, key_send_to, send_to -): - data = {key_send_to: send_to, "template": str(sample_template.id)} - auth_header = create_service_authorization_header( - service_id=sample_template.service_id - ) - - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400 - assert response.headers["Content-type"] == "application/json" - error_resp = json.loads(response.get_data(as_text=True)) - assert error_resp["status_code"] == 400 - assert { - "error": "ValidationError", - "message": "template_id is a required property", - } in error_resp["errors"] - assert { - "error": "ValidationError", - "message": "Additional properties are not allowed (template was unexpected)", - } in error_resp["errors"] - - -@pytest.mark.parametrize("reference", [None, "reference_from_client"]) -def test_post_email_notification_returns_201( - client, sample_email_template_with_placeholders, mocker, reference -): - mocked = mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") - data = { - "email_address": sample_email_template_with_placeholders.service.users[ - 0 - ].email_address, - "template_id": sample_email_template_with_placeholders.id, - "personalisation": {"name": "Bob"}, - } - if reference: - data.update({"reference": reference}) - auth_header = create_service_authorization_header( - service_id=sample_email_template_with_placeholders.service_id - ) - response = client.post( - path="v2/notifications/email", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 201 - resp_json = json.loads(response.get_data(as_text=True)) - assert validate(resp_json, post_email_response) == resp_json - notification = Notification.query.one() - assert notification.status == NotificationStatus.CREATED - assert resp_json["id"] == str(notification.id) - assert resp_json["reference"] == reference - assert notification.reference is None - assert notification.reply_to_text is None - assert notification.document_download_count is None - assert resp_json["content"][ - "body" - ] == sample_email_template_with_placeholders.content.replace("((name))", "Bob") - assert resp_json["content"][ - "subject" - ] == sample_email_template_with_placeholders.subject.replace("((name))", "Bob") - assert resp_json["content"]["from_email"] == ( - f"{sample_email_template_with_placeholders.service.email_from}@" - f"{current_app.config['NOTIFY_EMAIL_DOMAIN']}" - ) - assert f"v2/notifications/{notification.id}" in resp_json["uri"] - assert resp_json["template"]["id"] == str( - sample_email_template_with_placeholders.id - ) - assert ( - resp_json["template"]["version"] - == sample_email_template_with_placeholders.version - ) - assert ( - f"services/{sample_email_template_with_placeholders.service_id}/templates/" - f"{sample_email_template_with_placeholders.id}" - ) in resp_json["template"]["uri"] - assert not resp_json["scheduled_for"] - assert mocked.called - - -@pytest.mark.parametrize( - "recipient, notification_type", - [ - ("simulate-delivered@notifications.service.gov.uk", NotificationType.EMAIL), - ("simulate-delivered-2@notifications.service.gov.uk", NotificationType.EMAIL), - ("simulate-delivered-3@notifications.service.gov.uk", NotificationType.EMAIL), - ("+14254147167", NotificationType.SMS), - ], -) -def test_should_not_persist_or_send_notification_if_simulated_recipient( - client, recipient, notification_type, sample_email_template, sample_template, mocker -): - apply_async = mocker.patch( - f"app.celery.provider_tasks.deliver_{notification_type}.apply_async" - ) - - if notification_type == NotificationType.SMS: - data = {"phone_number": recipient, "template_id": str(sample_template.id)} - else: - data = { - "email_address": recipient, - "template_id": str(sample_email_template.id), - } - - auth_header = create_service_authorization_header( - service_id=sample_email_template.service_id - ) - - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 201 - apply_async.assert_not_called() - assert json.loads(response.get_data(as_text=True))["id"] - assert Notification.query.count() == 0 - - -@pytest.mark.parametrize( - "notification_type, key_send_to, send_to", - [ - (NotificationType.SMS, "phone_number", "2028675309"), - (NotificationType.EMAIL, "email_address", "sample@email.com"), - ], -) -def test_send_notification_uses_priority_queue_when_template_is_marked_as_priority( - client, - sample_service, - mocker, - notification_type, - key_send_to, - send_to, -): - mocker.patch(f"app.celery.provider_tasks.deliver_{notification_type}.apply_async") - - sample = create_template( - service=sample_service, - template_type=notification_type, - process_type=TemplateProcessType.PRIORITY, - ) - mocked = mocker.patch( - f"app.celery.provider_tasks.deliver_{notification_type}.apply_async" - ) - - data = {key_send_to: send_to, "template_id": str(sample.id)} - - auth_header = create_service_authorization_header(service_id=sample.service_id) - - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - notification_id = json.loads(response.data)["id"] - - assert response.status_code == 201 - mocked.assert_called_once_with([notification_id], queue="priority-tasks") - - -@pytest.mark.parametrize( - "notification_type, key_send_to, send_to", - [ - (NotificationType.SMS, "phone_number", "2028675309"), - (NotificationType.EMAIL, "email_address", "sample@email.com"), - ], -) -def test_returns_a_429_limit_exceeded_if_rate_limit_exceeded( - client, sample_service, mocker, notification_type, key_send_to, send_to -): - sample = create_template(service=sample_service, template_type=notification_type) - persist_mock = mocker.patch( - "app.v2.notifications.post_notifications.persist_notification" - ) - deliver_mock = mocker.patch( - "app.v2.notifications.post_notifications.send_notification_to_queue_detached" - ) - mocker.patch( - "app.v2.notifications.post_notifications.check_rate_limiting", - side_effect=RateLimitError("LIMIT", "INTERVAL", "TYPE"), - ) - - data = {key_send_to: send_to, "template_id": str(sample.id)} - - auth_header = create_service_authorization_header(service_id=sample.service_id) - - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - error = json.loads(response.data)["errors"][0]["error"] - message = json.loads(response.data)["errors"][0]["message"] - status_code = json.loads(response.data)["status_code"] - assert response.status_code == 429 - assert error == "RateLimitError" - assert ( - message - == "Exceeded rate limit for key type TYPE of LIMIT requests per INTERVAL seconds" - ) - assert status_code == 429 - - assert not persist_mock.called - assert not deliver_mock.called - - -@pytest.mark.skip("We don't support international at the moment") -def test_post_sms_notification_returns_400_if_not_allowed_to_send_int_sms( - client, - notify_db_session, -): - service = create_service(service_permissions=[ServicePermissionType.SMS]) - template = create_template(service=service) - - data = {"phone_number": "+20-12-1234-1234", "template_id": template.id} - auth_header = create_service_authorization_header(service_id=service.id) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400 - assert response.headers["Content-type"] == "application/json" - - error_json = json.loads(response.get_data(as_text=True)) - assert error_json["status_code"] == 400 - assert error_json["errors"] == [ - { - "error": "BadRequestError", - "message": "Cannot send to international mobile numbers", - } - ] - - -def test_post_sms_notification_with_archived_reply_to_id_returns_400( - client, sample_template, mocker -): - archived_sender = create_service_sms_sender( - sample_template.service, "12345", is_default=False, archived=True - ) - mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") - data = { - "phone_number": "+12028675309", - "template_id": sample_template.id, - "sms_sender_id": archived_sender.id, - } - auth_header = create_service_authorization_header( - service_id=sample_template.service_id - ) - response = client.post( - path="v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 400 - resp_json = json.loads(response.get_data(as_text=True)) - assert ( - f"sms_sender_id {archived_sender.id} does not exist in database for " - f"service id {sample_template.service_id}" - ) in resp_json["errors"][0]["message"] - assert "BadRequestError" in resp_json["errors"][0]["error"] - - -@pytest.mark.parametrize( - "recipient,label,permission_type, notification_type,expected_error", - [ - ( - "2028675309", - "phone_number", - ServicePermissionType.EMAIL, - NotificationType.SMS, - "text messages", - ), - ( - "someone@test.com", - "email_address", - ServicePermissionType.SMS, - NotificationType.EMAIL, - "emails", - ), - ], -) -def test_post_sms_notification_returns_400_if_not_allowed_to_send_notification( - notify_db_session, - client, - recipient, - label, - permission_type, - notification_type, - expected_error, -): - service = create_service(service_permissions=[permission_type]) - sample_template_without_permission = create_template( - service=service, template_type=notification_type - ) - data = {label: recipient, "template_id": sample_template_without_permission.id} - auth_header = create_service_authorization_header( - service_id=sample_template_without_permission.service.id - ) - - response = client.post( - path=f"/v2/notifications/{sample_template_without_permission.template_type}", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400 - assert response.headers["Content-type"] == "application/json" - - error_json = json.loads(response.get_data(as_text=True)) - assert error_json["status_code"] == 400 - assert error_json["errors"] == [ - { - "error": "BadRequestError", - "message": f"Service is not allowed to send {expected_error}", - } - ] - - -@pytest.mark.parametrize("restricted", [True, False]) -def test_post_sms_notification_returns_400_if_number_not_in_guest_list( - notify_db_session, client, restricted -): - service = create_service( - restricted=restricted, - service_permissions=[ - ServicePermissionType.SMS, - ServicePermissionType.INTERNATIONAL_SMS, - ], - ) - template = create_template(service=service) - create_api_key(service=service, key_type=KeyType.TEAM) - - data = { - "phone_number": "+16615555555", - "template_id": template.id, - } - auth_header = create_service_authorization_header( - service_id=service.id, - key_type=KeyType.TEAM, - ) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400 - error_json = json.loads(response.get_data(as_text=True)) - assert error_json["status_code"] == 400 - assert error_json["errors"] == [ - { - "error": "BadRequestError", - "message": "Can’t send to this recipient using a team-only API key", - } - ] - - -@pytest.mark.skip("We don't support international at the moment") -def test_post_sms_notification_returns_201_if_allowed_to_send_int_sms( - sample_service, - sample_template, - client, - mocker, -): - mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - - data = {"phone_number": "+20-12-1234-1234", "template_id": sample_template.id} - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 201 - assert response.headers["Content-type"] == "application/json" - - -def test_post_sms_should_persist_supplied_sms_number( - client, sample_template_with_placeholders, mocker -): - mocked = mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - data = { - "phone_number": "+16615555555", - "template_id": str(sample_template_with_placeholders.id), - "personalisation": {" Name": "Jo"}, - } - - auth_header = create_service_authorization_header( - service_id=sample_template_with_placeholders.service_id - ) - - response = client.post( - path="/v2/notifications/sms", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 201 - resp_json = json.loads(response.get_data(as_text=True)) - notifications = Notification.query.all() - assert len(notifications) == 1 - notification_id = notifications[0].id - assert "1" == notifications[0].to - assert resp_json["id"] == str(notification_id) - assert mocked.called - - -def test_post_notification_raises_bad_request_if_not_valid_notification_type( - client, sample_service -): - auth_header = create_service_authorization_header(service_id=sample_service.id) - response = client.post( - "/v2/notifications/foo", - data="{}", - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 404 - error_json = json.loads(response.get_data(as_text=True)) - assert "The requested URL was not found on the server." in error_json["message"] - - -@pytest.mark.parametrize( - "notification_type", [NotificationType.SMS, NotificationType.EMAIL] -) -def test_post_notification_with_wrong_type_of_sender( - client, sample_template, sample_email_template, notification_type, fake_uuid -): - if notification_type == NotificationType.EMAIL: - template = sample_email_template - form_label = "sms_sender_id" - data = { - "email_address": "test@test.com", - "template_id": str(sample_email_template.id), - form_label: fake_uuid, - } - elif notification_type == ServicePermissionType.SMS: - template = sample_template - form_label = "email_reply_to_id" - data = { - "phone_number": "+12028675309", - "template_id": str(template.id), - form_label: fake_uuid, - } - auth_header = create_service_authorization_header(service_id=template.service_id) - - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 400 - resp_json = json.loads(response.get_data(as_text=True)) - assert ( - f"Additional properties are not allowed ({form_label} was unexpected)" - in resp_json["errors"][0]["message"] - ) - assert "ValidationError" in resp_json["errors"][0]["error"] - - -def test_post_email_notification_with_valid_reply_to_id_returns_201( - client, sample_email_template, mocker -): - reply_to_email = create_reply_to_email( - sample_email_template.service, "test@test.com" - ) - mocked = mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") - data = { - "email_address": sample_email_template.service.users[0].email_address, - "template_id": sample_email_template.id, - "email_reply_to_id": reply_to_email.id, - } - auth_header = create_service_authorization_header( - service_id=sample_email_template.service_id - ) - response = client.post( - path="v2/notifications/email", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 201 - resp_json = json.loads(response.get_data(as_text=True)) - assert validate(resp_json, post_email_response) == resp_json - notification = Notification.query.first() - assert notification.reply_to_text == "test@test.com" - assert resp_json["id"] == str(notification.id) - assert mocked.called - - assert notification.reply_to_text == reply_to_email.email_address - - -def test_post_email_notification_with_invalid_reply_to_id_returns_400( - client, sample_email_template, mocker, fake_uuid -): - mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") - data = { - "email_address": sample_email_template.service.users[0].email_address, - "template_id": sample_email_template.id, - "email_reply_to_id": fake_uuid, - } - auth_header = create_service_authorization_header( - service_id=sample_email_template.service_id - ) - response = client.post( - path="v2/notifications/email", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 400 - resp_json = json.loads(response.get_data(as_text=True)) - assert ( - f"email_reply_to_id {fake_uuid} does not exist in database for service id " - f"{sample_email_template.service_id}" - ) in resp_json["errors"][0]["message"] - assert "BadRequestError" in resp_json["errors"][0]["error"] - - -def test_post_email_notification_with_archived_reply_to_id_returns_400( - client, sample_email_template, mocker -): - archived_reply_to = create_reply_to_email( - sample_email_template.service, - "reply_to@test.com", - is_default=False, - archived=True, - ) - mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") - data = { - "email_address": "test@test.com", - "template_id": sample_email_template.id, - "email_reply_to_id": archived_reply_to.id, - } - auth_header = create_service_authorization_header( - service_id=sample_email_template.service_id - ) - response = client.post( - path="v2/notifications/email", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 400 - resp_json = json.loads(response.get_data(as_text=True)) - assert ( - f"email_reply_to_id {archived_reply_to.id} does not exist in database for " - f"service id {sample_email_template.service_id}" - ) in resp_json["errors"][0]["message"] - assert "BadRequestError" in resp_json["errors"][0]["error"] - - -@pytest.mark.skip( - reason="We've removed personalization from db, needs refactor if we want to support this" -) -@pytest.mark.parametrize( - "csv_param", - ( - {"is_csv": None}, - {"is_csv": False}, - {"is_csv": True}, - {}, - ), -) -def test_post_notification_with_document_upload( - client, notify_db_session, mocker, csv_param -): - service = create_service(service_permissions=[ServicePermissionType.EMAIL]) - service.contact_link = "contact.me@gov.uk" - template = create_template( - service=service, - template_type=TemplateType.EMAIL, - content="Document 1: ((first_link)). Document 2: ((second_link))", - ) - - mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") - document_download_mock = mocker.patch( - "app.v2.notifications.post_notifications.document_download_client" - ) - document_download_mock.upload_document.side_effect = ( - lambda service_id, content, is_csv: f"{content}-link" - ) - - data = { - "email_address": service.users[0].email_address, - "template_id": template.id, - "personalisation": { - "first_link": {"file": "abababab", **csv_param}, - "second_link": {"file": "cdcdcdcd", **csv_param}, - }, - } - - auth_header = create_service_authorization_header(service_id=service.id) - response = client.post( - path="v2/notifications/email", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 201, response.get_data(as_text=True) - resp_json = json.loads(response.get_data(as_text=True)) - assert validate(resp_json, post_email_response) == resp_json - - assert document_download_mock.upload_document.call_args_list == [ - call(str(service.id), "abababab", csv_param.get("is_csv")), - call(str(service.id), "cdcdcdcd", csv_param.get("is_csv")), - ] - - notification = Notification.query.one() - - assert notification.status == NotificationStatus.CREATED - assert notification.personalisation == { - "first_link": "abababab-link", - "second_link": "cdcdcdcd-link", - } - assert notification.document_download_count == 2 - - assert ( - resp_json["content"]["body"] - == "Document 1: abababab-link. Document 2: cdcdcdcd-link" - ) - - -def test_post_notification_with_document_upload_simulated( - client, notify_db_session, mocker -): - service = create_service(service_permissions=[ServicePermissionType.EMAIL]) - service.contact_link = "contact.me@gov.uk" - template = create_template( - service=service, - template_type=TemplateType.EMAIL, - content="Document: ((document))", - ) - - mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") - document_download_mock = mocker.patch( - "app.v2.notifications.post_notifications.document_download_client" - ) - document_download_mock.get_upload_url.return_value = "https://document-url" - - data = { - "email_address": "simulate-delivered@notifications.service.gov.uk", - "template_id": template.id, - "personalisation": {"document": {"file": "abababab"}}, - } - - auth_header = create_service_authorization_header(service_id=service.id) - response = client.post( - path="v2/notifications/email", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 201, response.get_data(as_text=True) - resp_json = json.loads(response.get_data(as_text=True)) - assert validate(resp_json, post_email_response) == resp_json - - assert ( - resp_json["content"]["body"] == "Document: https://document-url/test-document" - ) - - -def test_post_notification_without_document_upload_permission( - client, notify_db_session, mocker -): - service = create_service(service_permissions=[ServicePermissionType.EMAIL]) - template = create_template( - service=service, - template_type=TemplateType.EMAIL, - content="Document: ((document))", - ) - - mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") - document_download_mock = mocker.patch( - "app.v2.notifications.post_notifications.document_download_client" - ) - document_download_mock.upload_document.return_value = "https://document-url/" - - data = { - "email_address": service.users[0].email_address, - "template_id": template.id, - "personalisation": {"document": {"file": "abababab"}}, - } - - auth_header = create_service_authorization_header(service_id=service.id) - response = client.post( - path="v2/notifications/email", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400, response.get_data(as_text=True) - - -def test_post_notification_returns_400_when_get_json_throws_exception( - client, sample_email_template -): - auth_header = create_service_authorization_header( - service_id=sample_email_template.service_id - ) - response = client.post( - path="v2/notifications/email", - data="[", - headers=[("Content-Type", "application/json"), auth_header], - ) - assert response.status_code == 400 - - -@pytest.mark.parametrize( - "notification_type, content_type", - [ - (NotificationType.EMAIL, "application/json"), - (NotificationType.EMAIL, "application/text"), - (NotificationType.SMS, "application/json"), - (NotificationType.SMS, "application/text"), - ], -) -def test_post_notification_when_payload_is_invalid_json_returns_400( - client, - sample_service, - notification_type, - content_type, -): - auth_header = create_service_authorization_header(service_id=sample_service.id) - payload_not_json = { - "template_id": "dont-convert-to-json", - } - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=payload_not_json, - headers=[("Content-Type", content_type), auth_header], - ) - - assert response.status_code == 400 - error_msg = json.loads(response.get_data(as_text=True))["errors"][0]["message"] - - assert error_msg == "Invalid JSON supplied in POST data" - - -@pytest.mark.parametrize( - "notification_type", - [ - NotificationType.EMAIL, - NotificationType.SMS, - ], -) -def test_post_notification_returns_201_when_content_type_is_missing_but_payload_is_valid_json( - client, sample_service, notification_type, mocker -): - template = create_template(service=sample_service, template_type=notification_type) - mocker.patch(f"app.celery.provider_tasks.deliver_{notification_type}.apply_async") - auth_header = create_service_authorization_header(service_id=sample_service.id) - - valid_json = { - "template_id": str(template.id), - } - if notification_type == NotificationType.EMAIL: - valid_json.update({"email_address": sample_service.users[0].email_address}) - else: - valid_json.update({"phone_number": "+12028675309"}) - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(valid_json), - headers=[auth_header], - ) - assert response.status_code == 201 - - -@pytest.mark.parametrize( - "notification_type", - [ - NotificationType.EMAIL, - NotificationType.SMS, - ], -) -def test_post_email_notification_when_data_is_empty_returns_400( - client, sample_service, notification_type -): - auth_header = create_service_authorization_header(service_id=sample_service.id) - data = None - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[("Content-Type", "application/json"), auth_header], - ) - error_msg = json.loads(response.get_data(as_text=True))["errors"][0]["message"] - assert response.status_code == 400 - if notification_type == NotificationType.SMS: - assert error_msg == "phone_number is a required property" - else: - assert error_msg == "email_address is a required property" - - -@pytest.mark.parametrize( - "notification_type", - ( - NotificationType.EMAIL, - NotificationType.SMS, - ), -) -def test_post_notifications_saves_email_or_sms_to_queue( - client, notify_db_session, mocker, notification_type -): - save_task = mocker.patch( - f"app.celery.tasks.save_api_{notification_type}.apply_async" - ) - mock_send_task = mocker.patch( - f"app.celery.provider_tasks.deliver_{notification_type}.apply_async" - ) - - service = create_service( - service_name="high volume service", - ) - with set_config_values( - current_app, - { - "HIGH_VOLUME_SERVICE": [str(service.id)], - }, - ): - template = create_template( - service=service, content="((message))", template_type=notification_type - ) - data = { - "template_id": template.id, - "personalisation": {"message": "Dear citizen, have a nice day"}, - } - ( - data.update({"email_address": "joe.citizen@example.com"}) - if notification_type == NotificationType.EMAIL - else data.update({"phone_number": "+12028675309"}) - ) - - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[ - ("Content-Type", "application/json"), - create_service_authorization_header(service_id=service.id), - ], - ) - - json_resp = response.get_json() - - assert response.status_code == 201 - assert json_resp["id"] - assert json_resp["content"]["body"] == "Dear citizen, have a nice day" - assert json_resp["template"]["id"] == str(template.id) - save_task.assert_called_once_with( - [mock.ANY], queue=f"save-api-{notification_type}-tasks" - ) - assert not mock_send_task.called - assert len(Notification.query.all()) == 0 - - -@pytest.mark.parametrize( - "exception", - [ - botocore.exceptions.ClientError({"some": "json"}, "some opname"), - botocore.parsers.ResponseParserError("exceeded max HTTP body length"), - ], -) -@pytest.mark.parametrize( - "notification_type", - ( - NotificationType.EMAIL, - NotificationType.SMS, - ), -) -def test_post_notifications_saves_email_or_sms_normally_if_saving_to_queue_fails( - client, notify_db_session, mocker, notification_type, exception -): - save_task = mocker.patch( - f"app.celery.tasks.save_api_{notification_type}.apply_async", - side_effect=exception, - ) - mock_send_task = mocker.patch( - f"app.celery.provider_tasks.deliver_{notification_type}.apply_async" - ) - - service = create_service( - service_name="high volume service", - ) - with set_config_values( - current_app, - { - "HIGH_VOLUME_SERVICE": [str(service.id)], - }, - ): - template = create_template( - service=service, content="((message))", template_type=notification_type - ) - data = { - "template_id": template.id, - "personalisation": {"message": "Dear citizen, have a nice day"}, - } - ( - data.update({"email_address": "joe.citizen@example.com"}) - if notification_type == NotificationType.EMAIL - else data.update({"phone_number": "+12028675309"}) - ) - - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[ - ("Content-Type", "application/json"), - create_service_authorization_header(service_id=service.id), - ], - ) - - json_resp = response.get_json() - - assert response.status_code == 201 - assert json_resp["id"] - assert json_resp["content"]["body"] == "Dear citizen, have a nice day" - assert json_resp["template"]["id"] == str(template.id) - save_task.assert_called_once_with( - [mock.ANY], queue=f"save-api-{notification_type}-tasks" - ) - mock_send_task.assert_called_once_with( - [json_resp["id"]], queue=f"send-{notification_type}-tasks" - ) - assert Notification.query.count() == 1 - - -@pytest.mark.parametrize( - "notification_type", - ( - NotificationType.EMAIL, - NotificationType.SMS, - ), -) -def test_post_notifications_doesnt_use_save_queue_for_test_notifications( - client, notify_db_session, mocker, notification_type -): - save_task = mocker.patch( - f"app.celery.tasks.save_api_{notification_type}.apply_async" - ) - mock_send_task = mocker.patch( - f"app.celery.provider_tasks.deliver_{notification_type}.apply_async" - ) - service = create_service( - service_name="high volume service", - ) - with set_config_values( - current_app, - { - "HIGH_VOLUME_SERVICE": [str(service.id)], - }, - ): - template = create_template( - service=service, content="((message))", template_type=notification_type - ) - data = { - "template_id": template.id, - "personalisation": {"message": "Dear citizen, have a nice day"}, - } - ( - data.update({"email_address": "joe.citizen@example.com"}) - if notification_type == NotificationType.EMAIL - else data.update({"phone_number": "+12028675309"}) - ) - response = client.post( - path=f"/v2/notifications/{notification_type}", - data=json.dumps(data), - headers=[ - ("Content-Type", "application/json"), - create_service_authorization_header( - service_id=service.id, - key_type=KeyType.TEST, - ), - ], - ) - - json_resp = response.get_json() - - assert response.status_code == 201 - assert json_resp["id"] - assert json_resp["content"]["body"] == "Dear citizen, have a nice day" - assert json_resp["template"]["id"] == str(template.id) - assert mock_send_task.called - assert not save_task.called - assert len(Notification.query.all()) == 1 diff --git a/tests/app/v2/template/__init__.py b/tests/app/v2/template/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/app/v2/template/test_get_template.py b/tests/app/v2/template/test_get_template.py deleted file mode 100644 index 2a14f5543..000000000 --- a/tests/app/v2/template/test_get_template.py +++ /dev/null @@ -1,146 +0,0 @@ -import pytest -from flask import json - -from app.enums import TemplateType -from app.utils import DATETIME_FORMAT -from tests import create_service_authorization_header -from tests.app.db import create_template - -valid_version_params = [None, 1] - - -@pytest.mark.parametrize( - "tmp_type, expected_name, expected_subject", - [ - (TemplateType.SMS, "sms Template Name", None), - (TemplateType.EMAIL, "email Template Name", "Template subject"), - ], -) -@pytest.mark.parametrize("version", valid_version_params) -def test_get_template_by_id_returns_200( - client, sample_service, tmp_type, expected_name, expected_subject, version -): - template = create_template(sample_service, template_type=tmp_type) - auth_header = create_service_authorization_header(service_id=sample_service.id) - - version_path = "/version/{}".format(version) if version else "" - - response = client.get( - path="/v2/template/{}{}".format(template.id, version_path), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - expected_response = { - "id": "{}".format(template.id), - "type": "{}".format(template.template_type), - "created_at": template.created_at.strftime(DATETIME_FORMAT), - "updated_at": None, - "version": template.version, - "created_by": template.created_by.email_address, - "body": template.content, - "subject": expected_subject, - "name": expected_name, - "personalisation": {}, - } - - assert json_response == expected_response - - -@pytest.mark.parametrize( - "create_template_args, expected_personalisation", - [ - ( - { - "template_type": TemplateType.SMS, - "content": "Hello ((placeholder)) ((conditional??yes))", - }, - { - "placeholder": {"required": True}, - "conditional": {"required": True}, - }, - ), - ( - { - "template_type": TemplateType.EMAIL, - "subject": "((subject))", - "content": "((content))", - }, - { - "subject": {"required": True}, - "content": {"required": True}, - }, - ), - ], -) -@pytest.mark.parametrize("version", valid_version_params) -def test_get_template_by_id_returns_placeholders( - client, - sample_service, - version, - create_template_args, - expected_personalisation, -): - template = create_template(sample_service, **create_template_args) - auth_header = create_service_authorization_header(service_id=sample_service.id) - - version_path = "/version/{}".format(version) if version else "" - - response = client.get( - path="/v2/template/{}{}".format(template.id, version_path), - headers=[("Content-Type", "application/json"), auth_header], - ) - - json_response = json.loads(response.get_data(as_text=True)) - assert json_response["personalisation"] == expected_personalisation - - -def test_get_template_with_non_existent_template_id_returns_404( - client, fake_uuid, sample_service -): - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.get( - path="/v2/template/{}".format(fake_uuid), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 404 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - assert json_response == { - "errors": [{"error": "NoResultFound", "message": "No result found"}], - "status_code": 404, - } - - -@pytest.mark.parametrize("tmp_type", TemplateType) -def test_get_template_with_non_existent_version_returns_404( - client, sample_service, tmp_type -): - template = create_template(sample_service, template_type=tmp_type) - - auth_header = create_service_authorization_header(service_id=sample_service.id) - - invalid_version = template.version + 1 - - response = client.get( - path="/v2/template/{}/version/{}".format(template.id, invalid_version), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 404 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - assert json_response == { - "errors": [{"error": "NoResultFound", "message": "No result found"}], - "status_code": 404, - } diff --git a/tests/app/v2/template/test_post_template.py b/tests/app/v2/template/test_post_template.py deleted file mode 100644 index 00d3aabfe..000000000 --- a/tests/app/v2/template/test_post_template.py +++ /dev/null @@ -1,222 +0,0 @@ -import pytest -from flask import json - -from app.models import TemplateType -from tests import create_service_authorization_header -from tests.app.db import create_template - -valid_personalisation = {"personalisation": {"Name": "Jo"}} - -valid_post = [ - ( - "Some subject", - "Some content", - None, - "Some subject", - "Some content", - ( - '

' - "Some content" - "

" - ), - ), - ( - "Some subject", - "Dear ((Name)), Hello. Yours Truly, The Government.", - valid_personalisation, - "Some subject", - "Dear Jo, Hello. Yours Truly, The Government.", - ( - '

' - "Dear Jo, Hello. Yours Truly, The Government." - "

" - ), - ), - ( - "Message for ((Name))", - "Dear ((Name)), Hello. Yours Truly, The Government.", - valid_personalisation, - "Message for Jo", - "Dear Jo, Hello. Yours Truly, The Government.", - ( - '

' - "Dear Jo, Hello. Yours Truly, The Government." - "

" - ), - ), - ( - "Message for ((Name))", - "Some content", - valid_personalisation, - "Message for Jo", - "Some content", - ( - '

' - "Some content" - "

" - ), - ), -] - - -@pytest.mark.parametrize("tmp_type", (TemplateType.SMS, TemplateType.EMAIL)) -@pytest.mark.parametrize( - "subject,content,post_data,expected_subject,expected_content,expected_html", - valid_post, -) -def test_valid_post_template_returns_200( - client, - sample_service, - tmp_type, - subject, - content, - post_data, - expected_subject, - expected_content, - expected_html, -): - template = create_template( - sample_service, template_type=tmp_type, subject=subject, content=content - ) - - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.post( - path="/v2/template/{}/preview".format(template.id), - data=json.dumps(post_data), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - - resp_json = json.loads(response.get_data(as_text=True)) - - assert resp_json["id"] == str(template.id) - - if tmp_type == TemplateType.EMAIL: - assert expected_subject in resp_json["subject"] - assert resp_json["html"] == expected_html - else: - assert resp_json["html"] is None - - assert expected_content in resp_json["body"] - - -def test_email_templates_not_rendered_into_content(client, sample_service): - template = create_template( - sample_service, - template_type=TemplateType.EMAIL, - subject="Test", - content=("Hello\n" "\r\n" "\r\n" "\n" "# This is a heading\n" "\n" "Paragraph"), - ) - - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.post( - path="/v2/template/{}/preview".format(template.id), - data=json.dumps(None), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - - resp_json = json.loads(response.get_data(as_text=True)) - - assert resp_json["body"] == template.content - - -@pytest.mark.parametrize("tmp_type", (TemplateType.SMS, TemplateType.EMAIL)) -def test_invalid_post_template_returns_400(client, sample_service, tmp_type): - template = create_template( - sample_service, - template_type=tmp_type, - content="Dear ((Name)), Hello ((Missing)). Yours Truly, The Government.", - ) - - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.post( - path="/v2/template/{}/preview".format(template.id), - data=json.dumps(valid_personalisation), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400 - - resp_json = json.loads(response.get_data(as_text=True)) - - assert resp_json["errors"][0]["error"] == "BadRequestError" - assert "Missing personalisation: Missing" in resp_json["errors"][0]["message"] - - -def test_post_template_with_non_existent_template_id_returns_404( - client, fake_uuid, sample_service -): - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.post( - path="/v2/template/{}/preview".format(fake_uuid), - data=json.dumps(valid_personalisation), - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 404 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - assert json_response == { - "errors": [{"error": "NoResultFound", "message": "No result found"}], - "status_code": 404, - } - - -def test_post_template_returns_200_without_personalisation(client, sample_template): - response = client.post( - path="/v2/template/{}/preview".format(sample_template.id), - data=None, - headers=[ - ("Content-Type", "application/json"), - create_service_authorization_header(service_id=sample_template.service_id), - ], - ) - assert response.status_code == 200 - - -def test_post_template_returns_200_without_personalisation_and_missing_content_header( - client, sample_template -): - response = client.post( - path="/v2/template/{}/preview".format(sample_template.id), - data=None, - headers=[ - create_service_authorization_header(service_id=sample_template.service_id) - ], - ) - assert response.status_code == 200 - - -def test_post_template_returns_200_without_personalisation_as_valid_json_and_missing_content_header( - client, sample_template -): - response = client.post( - path="/v2/template/{}/preview".format(sample_template.id), - data=json.dumps(None), - headers=[ - create_service_authorization_header(service_id=sample_template.service_id) - ], - ) - assert response.status_code == 200 - - -def test_post_template_returns_200_with_valid_json_and_missing_content_header( - client, sample_template -): - response = client.post( - path="/v2/template/{}/preview".format(sample_template.id), - data=json.dumps(valid_personalisation), - headers=[ - create_service_authorization_header(service_id=sample_template.service_id) - ], - ) - assert response.status_code == 200 diff --git a/tests/app/v2/template/test_template_schemas.py b/tests/app/v2/template/test_template_schemas.py deleted file mode 100644 index 198ad9bb6..000000000 --- a/tests/app/v2/template/test_template_schemas.py +++ /dev/null @@ -1,159 +0,0 @@ -import uuid - -import pytest -from flask import json -from jsonschema.exceptions import ValidationError - -from app.enums import TemplateType -from app.schema_validation import validate -from app.v2.template.template_schemas import ( - get_template_by_id_request, - get_template_by_id_response, - post_template_preview_request, - post_template_preview_response, -) - -valid_json_get_response = { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "2017-01-10T18:25:43.511Z", - "updated_at": None, - "version": 1, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", -} - -valid_json_get_response_with_optionals = { - "id": str(uuid.uuid4()), - "type": TemplateType.EMAIL, - "created_at": "2017-01-10T18:25:43.511Z", - "updated_at": None, - "version": 1, - "created_by": "someone", - "body": "some body", - "subject": "some subject", - "name": "some name", -} - -valid_request_args = [ - {"id": str(uuid.uuid4()), "version": 1}, - {"id": str(uuid.uuid4())}, -] - -invalid_request_args = [ - ( - {"id": str(uuid.uuid4()), "version": "test"}, - ["version test is not of type integer, null"], - ), - ( - {"id": str(uuid.uuid4()), "version": 0}, - ["version 0 is less than the minimum of 1"], - ), - ({"version": 1}, ["id is a required property"]), - ({"id": "invalid_uuid"}, ["id is not a valid UUID"]), - ( - {"id": "invalid_uuid", "version": 0}, - ["version 0 is less than the minimum of 1", "id is not a valid UUID"], - ), -] - -valid_json_post_args = {"id": str(uuid.uuid4()), "personalisation": {"key": "value"}} - -invalid_json_post_args = [ - ( - {"id": "invalid_uuid", "personalisation": {"key": "value"}}, - ["id is not a valid UUID"], - ), - ( - {"id": str(uuid.uuid4()), "personalisation": ["a", "b"]}, - ["personalisation [a, b] is not of type object"], - ), - ( - {"personalisation": "invalid_personalisation"}, - [ - "id is a required property", - "personalisation invalid_personalisation is not of type object", - ], - ), -] - -valid_json_post_response = { - "id": str(uuid.uuid4()), - "type": TemplateType.EMAIL, - "version": 1, - "body": "some body", -} - -valid_json_post_response_with_optionals = { - "id": str(uuid.uuid4()), - "type": TemplateType.EMAIL, - "version": 1, - "body": "some body", - "subject": "some subject", - "html": "

some body

", -} - - -@pytest.mark.parametrize("args", valid_request_args) -def test_get_template_request_schema_against_valid_args_is_valid(args): - assert validate(args, get_template_by_id_request) == args - - -@pytest.mark.parametrize("args,error_message", invalid_request_args) -def test_get_template_request_schema_against_invalid_args_is_invalid( - args, error_message -): - with pytest.raises(ValidationError) as e: - validate(args, get_template_by_id_request) - errors = json.loads(str(e.value)) - - assert errors["status_code"] == 400 - - for error in errors["errors"]: - assert error["message"] in error_message - - -@pytest.mark.parametrize("template_type", TemplateType) -@pytest.mark.parametrize( - "response", [valid_json_get_response, valid_json_get_response_with_optionals] -) -@pytest.mark.parametrize("updated_datetime", [None, "2017-01-11T18:25:43.511Z"]) -def test_get_template_response_schema_is_valid( - response, template_type, updated_datetime -): - if updated_datetime: - response["updated_at"] = updated_datetime - - response["type"] = template_type - - assert validate(response, get_template_by_id_response) == response - - -def test_post_template_preview_against_valid_args_is_valid(): - assert ( - validate(valid_json_post_args, post_template_preview_request) - == valid_json_post_args - ) - - -@pytest.mark.parametrize("args,error_messages", invalid_json_post_args) -def test_post_template_preview_against_invalid_args_is_invalid(args, error_messages): - with pytest.raises(ValidationError) as e: - validate(args, post_template_preview_request) - errors = json.loads(str(e.value)) - - assert errors["status_code"] == 400 - assert len(errors["errors"]) == len(error_messages) - for error in errors["errors"]: - assert error["message"] in error_messages - - -@pytest.mark.parametrize("template_type", TemplateType) -@pytest.mark.parametrize( - "response", [valid_json_post_response, valid_json_post_response_with_optionals] -) -def test_post_template_preview_response_schema_is_valid(response, template_type): - response["type"] = template_type - - assert validate(response, post_template_preview_response) == response diff --git a/tests/app/v2/templates/test_get_templates.py b/tests/app/v2/templates/test_get_templates.py deleted file mode 100644 index f2b7d11a0..000000000 --- a/tests/app/v2/templates/test_get_templates.py +++ /dev/null @@ -1,130 +0,0 @@ -import pytest -from flask import json - -from app.models import TemplateType -from tests import create_service_authorization_header -from tests.app.db import create_template - - -def test_get_all_templates_returns_200(client, sample_service): - templates = [ - create_template( - sample_service, - template_type=tmp_type, - subject=f"subject_{name}" if tmp_type == TemplateType.EMAIL else "", - template_name=name, - ) - for name, tmp_type in (("A", TemplateType.SMS), ("B", TemplateType.EMAIL)) - ] - - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.get( - path="/v2/templates", - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - assert len(json_response["templates"]) == len(templates) - - for index, template in enumerate(json_response["templates"]): - assert template["id"] == str(templates[index].id) - assert template["body"] == templates[index].content - assert template["type"] == templates[index].template_type - if templates[index].template_type == TemplateType.EMAIL: - assert template["subject"] == templates[index].subject - - -@pytest.mark.parametrize("tmp_type", (TemplateType.SMS, TemplateType.EMAIL)) -def test_get_all_templates_for_valid_type_returns_200(client, sample_service, tmp_type): - templates = [ - create_template( - sample_service, - template_type=tmp_type, - template_name=f"Template {i}", - subject=f"subject_{i}" if tmp_type == TemplateType.EMAIL else "", - ) - for i in range(3) - ] - - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.get( - path=f"/v2/templates?type={tmp_type}", - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - assert len(json_response["templates"]) == len(templates) - - for index, template in enumerate(json_response["templates"]): - assert template["id"] == str(templates[index].id) - assert template["body"] == templates[index].content - assert template["type"] == tmp_type - if templates[index].template_type == TemplateType.EMAIL: - assert template["subject"] == templates[index].subject - - -@pytest.mark.parametrize("tmp_type", (TemplateType.SMS, TemplateType.EMAIL)) -def test_get_correct_num_templates_for_valid_type_returns_200( - client, sample_service, tmp_type -): - num_templates = 3 - - templates = [] - for _ in range(num_templates): - templates.append(create_template(sample_service, template_type=tmp_type)) - - for other_type in TemplateType: - if other_type != tmp_type: - templates.append(create_template(sample_service, template_type=other_type)) - - auth_header = create_service_authorization_header(service_id=sample_service.id) - - response = client.get( - path=f"/v2/templates?type={tmp_type}", - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 200 - - json_response = json.loads(response.get_data(as_text=True)) - - assert len(json_response["templates"]) == num_templates - - -def test_get_all_templates_for_invalid_type_returns_400(client, sample_service): - auth_header = create_service_authorization_header(service_id=sample_service.id) - - invalid_type = "coconut" - - response = client.get( - path=f"/v2/templates?type={invalid_type}", - headers=[("Content-Type", "application/json"), auth_header], - ) - - assert response.status_code == 400 - assert response.headers["Content-type"] == "application/json" - - json_response = json.loads(response.get_data(as_text=True)) - - type_str = ", ".join( - [f"<{type(e).__name__}.{e.name}: {e.value}>" for e in TemplateType] - ) - assert json_response == { - "status_code": 400, - "errors": [ - { - "message": f"type coconut is not one of [{type_str}]", - "error": "ValidationError", - } - ], - } diff --git a/tests/app/v2/templates/test_templates_schemas.py b/tests/app/v2/templates/test_templates_schemas.py deleted file mode 100644 index 418770f5d..000000000 --- a/tests/app/v2/templates/test_templates_schemas.py +++ /dev/null @@ -1,301 +0,0 @@ -import uuid - -import pytest -from flask import json -from jsonschema.exceptions import ValidationError - -from app.enums import TemplateType -from app.schema_validation import validate -from app.v2.templates.templates_schemas import ( - get_all_template_request, - get_all_template_response, -) - -valid_json_get_all_response = [ - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "2017-01-10T18:25:43.511Z", - "updated_at": None, - "version": 1, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - }, - { - "id": str(uuid.uuid4()), - "type": TemplateType.EMAIL, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "version": 2, - "created_by": "someone@test.com", - "subject": "test subject", - "body": "some body", - "name": "some name", - }, - ] - }, - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "version": 2, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - {"templates": []}, -] - -invalid_json_get_all_response = [ - ( - { - "templates": [ - { - "id": "invalid_id", - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "version": 1, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - ["templates is not a valid UUID"], - ), - ( - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "version": "invalid_version", - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - ["templates invalid_version is not of type integer"], - ), - ( - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "invalid_created_at", - "updated_at": None, - "version": 1, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - ["templates invalid_created_at is not a date-time"], - ), - ({}, ["templates is a required property"]), - ( - { - "templates": [ - { - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "version": 1, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - ["templates id is a required property"], - ), - ( - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "version": 1, - "created_by": "someone@test.com", - "body": "some body", - } - ] - }, - ["templates name is a required property"], - ), - ( - { - "templates": [ - { - "id": str(uuid.uuid4()), - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "version": 1, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - ["templates type is a required property"], - ), - ( - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "updated_at": None, - "version": 1, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - ["templates created_at is a required property"], - ), - ( - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "version": 1, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - ["templates updated_at is a required property"], - ), - ( - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - ["templates version is a required property"], - ), - ( - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "version": 1, - "body": "some body", - "name": "some name", - } - ] - }, - ["templates created_by is a required property"], - ), - ( - { - "templates": [ - { - "id": str(uuid.uuid4()), - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "version": 1, - "created_by": "someone@test.com", - "name": "some name", - } - ] - }, - ["templates body is a required property"], - ), - ( - { - "templates": [ - { - "type": TemplateType.SMS, - "created_at": "2017-02-10T18:25:43.511Z", - "updated_at": None, - "created_by": "someone@test.com", - "body": "some body", - "name": "some name", - } - ] - }, - [ - "templates id is a required property", - "templates version is a required property", - ], - ), -] - - -@pytest.mark.parametrize("template_type", TemplateType) -def test_get_all_template_request_schema_against_no_args_is_valid(template_type): - data = {} - assert validate(data, get_all_template_request) == data - - -@pytest.mark.parametrize("template_type", TemplateType) -def test_get_all_template_request_schema_against_valid_args_is_valid(template_type): - data = {"type": template_type} - assert validate(data, get_all_template_request) == data - - -@pytest.mark.parametrize("template_type", TemplateType) -def test_get_all_template_request_schema_against_invalid_args_is_invalid(template_type): - data = {"type": "unknown"} - - with pytest.raises(ValidationError) as e: - validate(data, get_all_template_request) - errors = json.loads(str(e.value)) - - assert errors["status_code"] == 400 - assert len(errors["errors"]) == 1 - type_str = ", ".join( - [f"<{type(e).__name__}.{e.name}: {e.value}>" for e in TemplateType] - ) - assert errors["errors"][0]["message"] == f"type unknown is not one of [{type_str}]" - - -@pytest.mark.parametrize("response", valid_json_get_all_response) -def test_valid_get_all_templates_response_schema_is_valid(response): - assert validate(response, get_all_template_response) == response - - -@pytest.mark.parametrize("response,error_messages", invalid_json_get_all_response) -def test_invalid_get_all_templates_response_schema_is_invalid(response, error_messages): - with pytest.raises(ValidationError) as e: - validate(response, get_all_template_response) - errors = json.loads(str(e.value)) - - assert errors["status_code"] == 400 - assert len(errors["errors"]) == len(error_messages) - for error in errors["errors"]: - assert error["message"] in error_messages diff --git a/tests/app/v2/test_errors.py b/tests/app/v2/test_errors.py deleted file mode 100644 index 43d826ab5..000000000 --- a/tests/app/v2/test_errors.py +++ /dev/null @@ -1,159 +0,0 @@ -import pytest -from flask import url_for -from sqlalchemy.exc import DataError - -from app.v2.errors import ValidationError - - -@pytest.fixture(scope="function") -def app_for_test(): - import flask - from flask import Blueprint - - from app import init_app - from app.authentication.auth import AuthError - from app.v2.errors import BadRequestError, TooManyRequestsError - - app = flask.Flask(__name__) - app.config["TESTING"] = True - init_app(app) - - from app.v2.errors import register_errors - - blue = Blueprint("v2_under_test", __name__, url_prefix="/v2/under_test") - - @blue.route("/raise_auth_error", methods=["GET"]) - def raising_auth_error(): - raise AuthError("some message", 403) - - @blue.route("/raise_bad_request", methods=["GET"]) - def raising_bad_request(): - raise BadRequestError(message="you forgot the thing") - - @blue.route("/raise_too_many_requests", methods=["GET"]) - def raising_too_many_requests(): - raise TooManyRequestsError(sending_limit="452") - - @blue.route("/raise_validation_error", methods=["GET"]) - def raising_validation_error(): - from app.schema_validation import validate - from app.v2.notifications.notification_schemas import post_sms_request - - validate({"template_id": "bad_uuid"}, post_sms_request) - - @blue.route("raise_data_error", methods=["GET"]) - def raising_data_error(): - raise DataError("There was a db problem", "params", "orig") - - @blue.route("raise_exception", methods=["GET"]) - def raising_exception(): - raise AssertionError("Raising any old exception") - - register_errors(blue) - app.register_blueprint(blue) - - return app - - -def test_auth_error(app_for_test): - with app_for_test.test_request_context(): - with app_for_test.test_client() as client: - response = client.get(url_for("v2_under_test.raising_auth_error")) - assert response.status_code == 403 - error = response.json - assert error == { - "status_code": 403, - "errors": [{"error": "AuthError", "message": "some message"}], - } - - -def test_bad_request_error(app_for_test): - with app_for_test.test_request_context(): - with app_for_test.test_client() as client: - response = client.get(url_for("v2_under_test.raising_bad_request")) - assert response.status_code == 400 - error = response.json - assert error == { - "status_code": 400, - "errors": [ - {"error": "BadRequestError", "message": "you forgot the thing"} - ], - } - - -def test_too_many_requests_error(app_for_test): - with app_for_test.test_request_context(): - with app_for_test.test_client() as client: - response = client.get(url_for("v2_under_test.raising_too_many_requests")) - assert response.status_code == 429 - error = response.json - assert error == { - "status_code": 429, - "errors": [ - { - "error": "TooManyRequestsError", - "message": "Exceeded send limits (452) for today", - } - ], - } - - -def test_validation_error(app_for_test): - with app_for_test.test_request_context(): - with app_for_test.test_client() as client: - response = client.get(url_for("v2_under_test.raising_validation_error")) - assert response.status_code == 400 - error = response.json - assert len(error.keys()) == 2 - assert error["status_code"] == 400 - assert len(error["errors"]) == 2 - assert { - "error": "ValidationError", - "message": "phone_number is a required property", - } in error["errors"] - assert { - "error": "ValidationError", - "message": "template_id is not a valid UUID", - } in error["errors"] - ve = ValidationError("phone_number is a required property") - assert ve.message == "Your notification has failed validation" - assert ve.status_code == 400 - - -def test_data_errors(app_for_test): - with app_for_test.test_request_context(): - with app_for_test.test_client() as client: - response = client.get(url_for("v2_under_test.raising_data_error")) - assert response.status_code == 404 - error = response.json - assert error == { - "status_code": 404, - "errors": [{"error": "DataError", "message": "No result found"}], - } - - -def test_internal_server_error_handler(app_for_test): - with app_for_test.test_request_context(): - with app_for_test.test_client() as client: - response = client.get(url_for("v2_under_test.raising_exception")) - assert response.status_code == 500 - error = response.json - assert error == { - "status_code": 500, - "errors": [ - {"error": "AssertionError", "message": "Internal server error"} - ], - } - - -def test_bad_method(app_for_test): - with app_for_test.test_request_context(): - with app_for_test.test_client() as client: - response = client.post(url_for("v2_under_test.raising_exception")) - - assert response.status_code == 405 - - assert response.get_json(force=True) == { - "result": "error", - "message": "The method is not allowed for the requested URL.", - }