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 01/29] 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.", - } From 0429a5490ddd48631e0c3b01cacb460ab0b61a7b Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Tue, 4 Jun 2024 08:29:13 -0700 Subject: [PATCH 02/29] merge from main --- app/notifications/process_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index dfc4578a6..0137aba0c 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -12,7 +12,7 @@ from app.dao.notifications_dao import ( from app.enums import KeyType, NotificationStatus, NotificationType from app.errors import BadRequestError from app.models import Notification - +from app.utils import utc_now from notifications_utils.recipients import ( format_email_address, get_international_phone_info, From 3a06b1d650ea0abbb977fed358be4b7f32963f0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:25:57 +0000 Subject: [PATCH 03/29] Bump boto3 from 1.34.116 to 1.34.119 Bumps [boto3](https://github.com/boto/boto3) from 1.34.116 to 1.34.119. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.34.116...1.34.119) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8aa19a612..5715afed1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -403,17 +403,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.116" +version = "1.34.119" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.116-py3-none-any.whl", hash = "sha256:e7f5ab2d1f1b90971a2b9369760c2c6bae49dae98c084a5c3f5c78e3968ace15"}, - {file = "boto3-1.34.116.tar.gz", hash = "sha256:53cb8aeb405afa1cd2b25421e27a951aeb568026675dec020587861fac96ac87"}, + {file = "boto3-1.34.119-py3-none-any.whl", hash = "sha256:8f9c43c54b3dfaa36c4a0d7b42c417227a515bc7a2e163e62802780000a5a3e2"}, + {file = "boto3-1.34.119.tar.gz", hash = "sha256:cea2365a25b2b83a97e77f24ac6f922ef62e20636b42f9f6ee9f97188f9c1c03"}, ] [package.dependencies] -botocore = ">=1.34.116,<1.35.0" +botocore = ">=1.34.119,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "37ce0fb252a872b65bfee753a4e794cd5f11a532f3162e760f1396331f6592df" +content-hash = "b05d524b1dbd1b2a9b798478cae0ee86c9994f356cf6732e82bdcd6902bf77fb" diff --git a/pyproject.toml b/pyproject.toml index 4f8911a19..31f4dacc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ python = "^3.12.2" alembic = "==1.13.1" amqp = "==5.2.0" beautifulsoup4 = "==4.12.3" -boto3 = "^1.34.116" +boto3 = "^1.34.119" botocore = "^1.34.119" cachetools = "==5.3.3" celery = {version = "==5.4.0", extras = ["redis"]} From 920d2c4539c5701693c3202e91a8c742fbb5d3f7 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 5 Jun 2024 12:42:35 -0700 Subject: [PATCH 04/29] debug for notify-admin-1588 --- app/celery/tasks.py | 4 +++- app/clients/cloudwatch/aws_cloudwatch.py | 5 ++++- app/delivery/send_to_providers.py | 10 +++++++--- app/notifications/process_notifications.py | 3 ++- app/service/rest.py | 5 ----- app/utils.py | 13 +++++++++++++ poetry.lock | 8 ++------ tests/app/test_utils.py | 8 ++++++++ 8 files changed, 39 insertions(+), 17 deletions(-) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index e0428152a..1950712af 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -23,7 +23,7 @@ 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, utc_now +from app.utils import DATETIME_FORMAT, hilite, scrub, utc_now from app.v2.errors import TotalRequestsError from notifications_utils.recipients import RecipientCSV @@ -189,6 +189,7 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i # Return False when trial mode services try sending notifications # to non-team and non-simulated recipients. if not service_allowed_to_send_to(notification["to"], service, KeyType.NORMAL): + current_app.logger.info(hilite(scrub(f"service not allowed to send to {notification['to']}, aborting"))) current_app.logger.debug( "SMS {} failed as restricted service".format(notification_id) ) @@ -219,6 +220,7 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i ) # Kick off sns process in provider_tasks.py + current_app.logger.info(hilite(scrub(f"Going to deliver sms for recipient: {notification['to']}"))) provider_tasks.deliver_sms.apply_async( [str(saved_notification.id)], queue=QueueNames.SEND_SMS ) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index d010957ac..14d8513c5 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -9,7 +9,7 @@ from flask import current_app from app.clients import AWS_CLIENT_CONFIG, Client from app.cloudfoundry_config import cloud_config from app.exceptions import NotificationTechnicalFailureException -from app.utils import utc_now +from app.utils import hilite, scrub, utc_now class AwsCloudwatchClient(Client): @@ -124,6 +124,7 @@ class AwsCloudwatchClient(Client): self.warn_if_dev_is_opted_out( message["delivery"]["providerResponse"], notification_id ) + current_app.logger.info(hilite(scrub(f"DELIVERED: {message}"))) return ( "success", message["delivery"]["providerResponse"], @@ -140,6 +141,8 @@ class AwsCloudwatchClient(Client): self.warn_if_dev_is_opted_out( message["delivery"]["providerResponse"], notification_id ) + + current_app.logger.info(hilite(scrub(f"FAILED: {message}"))) return ( "failure", message["delivery"]["providerResponse"], diff --git a/app/delivery/send_to_providers.py b/app/delivery/send_to_providers.py index a9b90d368..a347b89ad 100644 --- a/app/delivery/send_to_providers.py +++ b/app/delivery/send_to_providers.py @@ -13,7 +13,7 @@ from app.dao.provider_details_dao import get_provider_details_by_notification_ty from app.enums import BrandType, KeyType, NotificationStatus, NotificationType from app.exceptions import NotificationTechnicalFailureException from app.serialised_models import SerialisedService, SerialisedTemplate -from app.utils import utc_now +from app.utils import hilite, scrub, utc_now from notifications_utils.template import ( HTMLEmailTemplate, PlainTextEmailTemplate, @@ -109,15 +109,19 @@ def send_sms_to_provider(notification): "international": notification.international, } db.session.close() # no commit needed as no changes to objects have been made above - current_app.logger.info("sending to sms") + message_id = provider.send_sms(**send_sms_kwargs) current_app.logger.info(f"got message_id {message_id}") except Exception as e: - current_app.logger.error(e) + msg = f"FAILED sending message for this recipient: {recipient} to sms" + current_app.logger.error(hilite(scrub(f"{msg} {e}"))) + notification.billable_units = template.fragment_count dao_update_notification(notification) raise e else: + msg = f"Sending message for this recipient: {recipient} to sms" + current_app.logger.info(hilite(scrub(msg))) notification.billable_units = template.fragment_count update_notification_to_sending(notification, provider) return message_id diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index 2a423767d..d899a8146 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -11,7 +11,7 @@ from app.dao.notifications_dao import ( ) from app.enums import KeyType, NotificationStatus, NotificationType from app.models import Notification -from app.utils import utc_now +from app.utils import hilite, scrub, utc_now from app.v2.errors import BadRequestError from notifications_utils.recipients import ( format_email_address, @@ -110,6 +110,7 @@ def persist_notification( formatted_recipient = validate_and_format_phone_number( recipient, international=True ) + current_app.logger.info(hilite(scrub(f"Persisting notification with recipient {formatted_recipient}"))) recipient_info = get_international_phone_info(formatted_recipient) notification.normalised_to = formatted_recipient notification.international = recipient_info.international diff --git a/app/service/rest.py b/app/service/rest.py index be8d55f45..0503c71d5 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -473,11 +473,6 @@ def get_all_notifications_for_service(service_id): ) current_app.logger.debug(f"number of notifications are {len(notifications)}") - if len(notifications) > 0: - current_app.logger.debug(f"first notification is {notifications[0]}") - else: - current_app.logger.debug("there are no notifications to show") - # We try and get the next page of results to work out if we need provide a pagination link to the next page # in our response if it exists. Note, this could be done instead by changing `count_pages` in the previous # call to be True which will enable us to use Flask-Sqlalchemy to tell if there is a next page of results but diff --git a/app/utils.py b/app/utils.py index e286d6539..b23a000d1 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,3 +1,4 @@ +import re from datetime import datetime, timedelta, timezone from flask import url_for @@ -144,3 +145,15 @@ def naive_utcnow(): def utc_now(): return naive_utcnow() + + +def scrub(msg): + # Eventually we want to scrub all messages in all logs for phone numbers + # and email addresses, masking them. Ultimately this will probably get + # refactored into a 'SafeLogger' subclass or something, but let's start here + # with phones. + phones = re.findall("(?:\\+ *)?\\d[\\d\\- ]{7,}\\d", msg) + phones = [phone.replace("-", "").replace(" ", "") for phone in phones] + for phone in phones: + msg = msg.replace(phone, f"1XXXXX{phone[-5:]}") + return msg diff --git a/poetry.lock b/poetry.lock index a9d3c4056..a7f4120df 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"}, ] @@ -3475,7 +3472,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/test_utils.py b/tests/app/test_utils.py index b39d3a2c0..bbe37256a 100644 --- a/tests/app/test_utils.py +++ b/tests/app/test_utils.py @@ -13,6 +13,7 @@ from app.utils import ( get_reference_from_personalisation, get_uuid_string_or_none, midnight_n_days_ago, + scrub, ) @@ -94,6 +95,13 @@ def test_get_public_notify_type_text(): ) +def test_scrub(): + result = scrub( + "This is a message with 17775554324, and also 18884449323 and also 17775554324" + ) + assert result == "This is a message with 1XXXXX54324, and also 1XXXXX49323 and also 1XXXXX54324" + + # This method is used for simulating bulk sends. We use localstack and run on a developer's machine to do the # simulation. Please see docs->bulk_testing.md for instructions. # def test_generate_csv_for_bulk_testing(): From 57d97e17cd5c2dbf18660b62c81cd1ee86976f74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:41:44 +0000 Subject: [PATCH 05/29] Bump cryptography from 42.0.7 to 42.0.8 Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.7 to 42.0.8. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.7...42.0.8) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 68 +++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5715afed1..2abe88126 100644 --- a/poetry.lock +++ b/poetry.lock @@ -939,43 +939,43 @@ files = [ [[package]] name = "cryptography" -version = "42.0.7" +version = "42.0.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"}, - {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"}, - {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"}, - {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"}, - {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"}, - {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"}, - {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"}, - {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, ] [package.dependencies] @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "b05d524b1dbd1b2a9b798478cae0ee86c9994f356cf6732e82bdcd6902bf77fb" +content-hash = "6f2d1cd2be9beafc39efe140daf6fa60ac31a7f7c83f01b7a9f364ad317c382a" diff --git a/pyproject.toml b/pyproject.toml index 31f4dacc4..70dce0b6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ shapely = "^2.0.4" smartypants = "^2.0.1" mistune = "0.8.4" blinker = "^1.8.2" -cryptography = "^42.0.7" +cryptography = "^42.0.8" idna = "^3.7" jmespath = "^1.0.1" markupsafe = "^2.1.5" From 7103f8850eb131fb3e3a21652c75d385597fdb25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:38:05 +0000 Subject: [PATCH 06/29] Bump phonenumbers from 8.13.37 to 8.13.38 Bumps [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) from 8.13.37 to 8.13.38. - [Commits](https://github.com/daviddrysdale/python-phonenumbers/compare/v8.13.37...v8.13.38) --- updated-dependencies: - dependency-name: phonenumbers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 16 ++++++++++------ pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index a5760b965..70c58f198 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2098,9 +2098,13 @@ 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"}, @@ -2489,7 +2493,6 @@ 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"}, ] @@ -2823,13 +2826,13 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.13.37" +version = "8.13.38" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." optional = false python-versions = "*" files = [ - {file = "phonenumbers-8.13.37-py2.py3-none-any.whl", hash = "sha256:4ea00ef5012422c08c7955c21131e7ae5baa9a3ef52cf2d561e963f023006b80"}, - {file = "phonenumbers-8.13.37.tar.gz", hash = "sha256:bd315fed159aea0516f7c367231810fe8344d5bec26156b88fa18374c11d1cf2"}, + {file = "phonenumbers-8.13.38-py2.py3-none-any.whl", hash = "sha256:d22aa747fb591ef2a18afec13cab5a0e294ab20fce5a1560e4949e459e70eeef"}, + {file = "phonenumbers-8.13.38.tar.gz", hash = "sha256:2822c74ee9334e9d8ad792fc352cc8d21004307349b6b1bb61da12937fa2eaba"}, ] [[package]] @@ -3472,6 +3475,7 @@ 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"}, @@ -4748,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "6f2d1cd2be9beafc39efe140daf6fa60ac31a7f7c83f01b7a9f364ad317c382a" +content-hash = "99a70d0803c2f472a921022e34a228e5b5b25303bd53745dbeafe1fd559bb4ab" diff --git a/pyproject.toml b/pyproject.toml index 70dce0b6a..01f86c9e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ geojson = "^3.1.0" govuk-bank-holidays = "^0.14" numpy = "^1.26.4" ordered-set = "^4.1.0" -phonenumbers = "^8.13.36" +phonenumbers = "^8.13.38" python-json-logger = "^2.0.7" pytz = "^2024.1" regex = "^2024.5.15" From 074d0108c9ac8cb9e284818e2256898b5b7c447c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:01:07 +0000 Subject: [PATCH 07/29] Bump pytest from 8.2.1 to 8.2.2 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.2.1 to 8.2.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.2.1...8.2.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 70c58f198..676420e56 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3299,13 +3299,13 @@ files = [ [[package]] name = "pytest" -version = "8.2.1" +version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, - {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "99a70d0803c2f472a921022e34a228e5b5b25303bd53745dbeafe1fd559bb4ab" +content-hash = "8f48de7a8673ab06381db04f362b5069b3f9e2c1a25935bde691b1e74ffc0b76" diff --git a/pyproject.toml b/pyproject.toml index 01f86c9e0..7a4e43039 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ jinja2-cli = {version = "==0.8.2", extras = ["yaml"]} moto = "==5.0.9" pip-audit = "*" pre-commit = "^3.7.1" -pytest = "^8.2.1" +pytest = "^8.2.2" pytest-env = "^1.1.3" pytest-mock = "^3.14.0" pytest-cov = "^5.0.0" From cf9ccd4a458c7180eb8a0b333a53da2b2ba99b14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:38:52 +0000 Subject: [PATCH 08/29] Bump botocore from 1.34.119 to 1.34.120 Bumps [botocore](https://github.com/boto/botocore) from 1.34.119 to 1.34.120. - [Changelog](https://github.com/boto/botocore/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/botocore/compare/1.34.119...1.34.120) --- updated-dependencies: - dependency-name: botocore dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 16 ++++++++-------- pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index 676420e56..f9008838a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -204,17 +204,17 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "awscli" -version = "1.33.1" +version = "1.33.2" description = "Universal Command Line Environment for AWS." optional = false python-versions = ">=3.8" files = [ - {file = "awscli-1.33.1-py3-none-any.whl", hash = "sha256:d446a99e6d2a05a2456f4d3a12240b8d7c0b7cdc42485b4444bbfb3659039cde"}, - {file = "awscli-1.33.1.tar.gz", hash = "sha256:1a5b5d7e438c0f53ede9142b9e92f25ba955e587f559ddaccba3ceab2568fcb8"}, + {file = "awscli-1.33.2-py3-none-any.whl", hash = "sha256:b8246ef8085df75ba20537c5ac0a621a4dbae1f084dc536bdc49cf2736fd0d3c"}, + {file = "awscli-1.33.2.tar.gz", hash = "sha256:4f692fff0e75fc2505f9a7cb613ec09426c6ed52bcd5a99246f58426b16a1bb6"}, ] [package.dependencies] -botocore = "1.34.119" +botocore = "1.34.120" colorama = ">=0.2.5,<0.4.7" docutils = ">=0.10,<0.17" PyYAML = ">=3.10,<6.1" @@ -422,13 +422,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.119" +version = "1.34.120" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.119-py3-none-any.whl", hash = "sha256:4bdf7926a1290b2650d62899ceba65073dd2693e61c35f5cdeb3a286a0aaa27b"}, - {file = "botocore-1.34.119.tar.gz", hash = "sha256:b253f15b24b87b070e176af48e8ef146516090429d30a7d8b136a4c079b28008"}, + {file = "botocore-1.34.120-py3-none-any.whl", hash = "sha256:92bd739938078c7a0b110689a3eee21ecb3954d90653da013d9f98ef1165d6f7"}, + {file = "botocore-1.34.120.tar.gz", hash = "sha256:5cc0fca43cb2aad54917a394a001ac9ba774d21ad6a08828002d54b601776f78"}, ] [package.dependencies] @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "8f48de7a8673ab06381db04f362b5069b3f9e2c1a25935bde691b1e74ffc0b76" +content-hash = "93b100659a8d3a34589593cc61e30b792d5f14371904c91fc2435cdcf04e479e" diff --git a/pyproject.toml b/pyproject.toml index 7a4e43039..041574b33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ alembic = "==1.13.1" amqp = "==5.2.0" beautifulsoup4 = "==4.12.3" boto3 = "^1.34.119" -botocore = "^1.34.119" +botocore = "^1.34.120" cachetools = "==5.3.3" celery = {version = "==5.4.0", extras = ["redis"]} certifi = ">=2022.12.7" From aed0b7bc6834930d296500f8fa4493c6d2fc275c Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 6 Jun 2024 13:05:56 -0700 Subject: [PATCH 09/29] lets make the 7 day cache work properly --- app/aws/s3.py | 2 +- poetry.lock | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/aws/s3.py b/app/aws/s3.py index e71472ecd..9466e6cce 100644 --- a/app/aws/s3.py +++ b/app/aws/s3.py @@ -12,7 +12,7 @@ FILE_LOCATION_STRUCTURE = "service-{}-notify/{}.csv" # Temporarily extend cache to 7 days ttl = 60 * 60 * 24 * 7 -JOBS = ExpiringDict(max_len=1000, max_age_seconds=ttl) +JOBS = ExpiringDict(max_len=20000, max_age_seconds=ttl) JOBS_CACHE_HITS = "JOBS_CACHE_HITS" diff --git a/poetry.lock b/poetry.lock index f9008838a..85c1510cf 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"}, ] @@ -3475,7 +3472,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"}, From 6081ff48738214020e362fcdfae139c9d5443afb Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 6 Jun 2024 13:18:00 -0700 Subject: [PATCH 10/29] merge from main --- app/celery/tasks.py | 11 ++++++++--- app/notifications/process_notifications.py | 7 +++++-- tests/app/test_utils.py | 5 ++++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index c6e17cca1..06309efd8 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -25,7 +25,6 @@ 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, hilite, scrub, utc_now -from app.errors import TotalRequestsError from notifications_utils.recipients import RecipientCSV @@ -190,7 +189,11 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i # Return False when trial mode services try sending notifications # to non-team and non-simulated recipients. if not service_allowed_to_send_to(notification["to"], service, KeyType.NORMAL): - current_app.logger.info(hilite(scrub(f"service not allowed to send to {notification['to']}, aborting"))) + current_app.logger.info( + hilite( + scrub(f"service not allowed to send to {notification['to']}, aborting") + ) + ) current_app.logger.debug( "SMS {} failed as restricted service".format(notification_id) ) @@ -221,7 +224,9 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i ) # Kick off sns process in provider_tasks.py - current_app.logger.info(hilite(scrub(f"Going to deliver sms for recipient: {notification['to']}"))) + current_app.logger.info( + hilite(scrub(f"Going to deliver sms for recipient: {notification['to']}")) + ) provider_tasks.deliver_sms.apply_async( [str(saved_notification.id)], queue=QueueNames.SEND_SMS ) diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index 69de73922..7c97cd3ba 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -13,7 +13,6 @@ from app.enums import KeyType, NotificationStatus, NotificationType from app.errors import BadRequestError from app.models import Notification from app.utils import hilite, scrub, utc_now -from app.errors import BadRequestError from notifications_utils.recipients import ( format_email_address, get_international_phone_info, @@ -111,7 +110,11 @@ def persist_notification( formatted_recipient = validate_and_format_phone_number( recipient, international=True ) - current_app.logger.info(hilite(scrub(f"Persisting notification with recipient {formatted_recipient}"))) + current_app.logger.info( + hilite( + scrub(f"Persisting notification with recipient {formatted_recipient}") + ) + ) recipient_info = get_international_phone_info(formatted_recipient) notification.normalised_to = formatted_recipient notification.international = recipient_info.international diff --git a/tests/app/test_utils.py b/tests/app/test_utils.py index bbe37256a..20675aec5 100644 --- a/tests/app/test_utils.py +++ b/tests/app/test_utils.py @@ -99,7 +99,10 @@ def test_scrub(): result = scrub( "This is a message with 17775554324, and also 18884449323 and also 17775554324" ) - assert result == "This is a message with 1XXXXX54324, and also 1XXXXX49323 and also 1XXXXX54324" + assert ( + result + == "This is a message with 1XXXXX54324, and also 1XXXXX49323 and also 1XXXXX54324" + ) # This method is used for simulating bulk sends. We use localstack and run on a developer's machine to do the From 22e564155b523a0f68a83353a2156299f416765b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:54:07 +0000 Subject: [PATCH 11/29] Bump marshmallow from 3.21.2 to 3.21.3 Bumps [marshmallow](https://github.com/marshmallow-code/marshmallow) from 3.21.2 to 3.21.3. - [Changelog](https://github.com/marshmallow-code/marshmallow/blob/dev/CHANGELOG.rst) - [Commits](https://github.com/marshmallow-code/marshmallow/compare/3.21.2...3.21.3) --- updated-dependencies: - dependency-name: marshmallow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 16 ++++++++++------ pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 85c1510cf..05ad82d25 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2098,9 +2098,13 @@ 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"}, @@ -2302,13 +2306,13 @@ files = [ [[package]] name = "marshmallow" -version = "3.21.2" +version = "3.21.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.21.2-py3-none-any.whl", hash = "sha256:70b54a6282f4704d12c0a41599682c5c5450e843b9ec406308653b47c59648a1"}, - {file = "marshmallow-3.21.2.tar.gz", hash = "sha256:82408deadd8b33d56338d2182d455db632c6313aa2af61916672146bb32edc56"}, + {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, + {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, ] [package.dependencies] @@ -2489,7 +2493,6 @@ 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"}, ] @@ -3472,6 +3475,7 @@ 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"}, @@ -4748,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "93b100659a8d3a34589593cc61e30b792d5f14371904c91fc2435cdcf04e479e" +content-hash = "1548ec75e2eab5aeb900f31f1fc1e6e3815af3587e153854ade4a8223d64d05a" diff --git a/pyproject.toml b/pyproject.toml index 041574b33..14ef6e021 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ gunicorn = {version = "==22.0.0", extras = ["eventlet"]} iso8601 = "==2.1.0" jsonschema = {version = "==4.22.0", extras = ["format"]} lxml = "==5.2.2" -marshmallow = "==3.21.2" +marshmallow = "==3.21.3" marshmallow-sqlalchemy = "==1.0.0" newrelic = "*" notifications-python-client = "==9.1.0" From 563b8deba84ae901b0157a168339d6000ac627c0 Mon Sep 17 00:00:00 2001 From: John Skiles Skinner Date: Fri, 7 Jun 2024 17:27:24 -0700 Subject: [PATCH 12/29] Add a null_resource with prevent_destroy to Sandbox --- terraform/sandbox/main.tf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/terraform/sandbox/main.tf b/terraform/sandbox/main.tf index cc99b033b..5a210c402 100644 --- a/terraform/sandbox/main.tf +++ b/terraform/sandbox/main.tf @@ -6,6 +6,13 @@ locals { recursive_delete = true # deprecated, still used in shared modules } +resource "null_resource" "prevent_destroy" { + + lifecycle { + prevent_destroy = false + } +} + module "database" { source = "github.com/GSA-TTS/terraform-cloudgov//database?ref=v1.0.0" From 4631dd3d5e7e79c93d7aa340dafe6308c2fbd7fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jun 2024 02:50:50 +0000 Subject: [PATCH 13/29] Bump redis from 5.0.4 to 5.0.5 Bumps [redis](https://github.com/redis/redis-py) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/redis/redis-py/releases) - [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES) - [Commits](https://github.com/redis/redis-py/compare/v5.0.4...v5.0.5) --- updated-dependencies: - dependency-name: redis dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 05ad82d25..e04172aa2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3635,13 +3635,13 @@ full = ["numpy"] [[package]] name = "redis" -version = "5.0.4" +version = "5.0.5" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"}, - {file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"}, + {file = "redis-5.0.5-py3-none-any.whl", hash = "sha256:30b47d4ebb6b7a0b9b40c1275a19b87bb6f46b3bed82a89012cf56dea4024ada"}, + {file = "redis-5.0.5.tar.gz", hash = "sha256:3417688621acf6ee368dec4a04dd95881be24efd34c79f00d31f62bb528800ae"}, ] [package.extras] @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "1548ec75e2eab5aeb900f31f1fc1e6e3815af3587e153854ade4a8223d64d05a" +content-hash = "75bd5796cd9ccafc4b2ce676d84757af06e73298ddb25c50d13f95bb1eb9d64b" diff --git a/pyproject.toml b/pyproject.toml index 14ef6e021..a78b80e69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ urllib3 = "^2.2.1" webencodings = "^0.5.1" itsdangerous = "^2.2.0" jinja2 = "^3.1.4" -redis = "^5.0.4" +redis = "^5.0.5" requests = "^2.32.3" From 30b1934f123e627bd96ba9d388cd699f5e96eb8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jun 2024 03:15:03 +0000 Subject: [PATCH 14/29] Bump boto3 from 1.34.119 to 1.34.120 Bumps [boto3](https://github.com/boto/boto3) from 1.34.119 to 1.34.120. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.34.119...1.34.120) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index e04172aa2..01464a815 100644 --- a/poetry.lock +++ b/poetry.lock @@ -403,17 +403,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.119" +version = "1.34.120" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.119-py3-none-any.whl", hash = "sha256:8f9c43c54b3dfaa36c4a0d7b42c417227a515bc7a2e163e62802780000a5a3e2"}, - {file = "boto3-1.34.119.tar.gz", hash = "sha256:cea2365a25b2b83a97e77f24ac6f922ef62e20636b42f9f6ee9f97188f9c1c03"}, + {file = "boto3-1.34.120-py3-none-any.whl", hash = "sha256:3c42bc309246a761413f6e152f307f009e80e7c9fd03dd9e6c0dc8ab8b3a8fc1"}, + {file = "boto3-1.34.120.tar.gz", hash = "sha256:38893db8269d25b72cc6fbab97633bfc863eefde5456847169d06149a16aa6e0"}, ] [package.dependencies] -botocore = ">=1.34.119,<1.35.0" +botocore = ">=1.34.120,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "75bd5796cd9ccafc4b2ce676d84757af06e73298ddb25c50d13f95bb1eb9d64b" +content-hash = "8d3f15d6ffddd4f8ff33a90fb97da9f660c1665603b45e6463292e0e9f97c29c" diff --git a/pyproject.toml b/pyproject.toml index a78b80e69..125bbe4d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ python = "^3.12.2" alembic = "==1.13.1" amqp = "==5.2.0" beautifulsoup4 = "==4.12.3" -boto3 = "^1.34.119" +boto3 = "^1.34.120" botocore = "^1.34.120" cachetools = "==5.3.3" celery = {version = "==5.4.0", extras = ["redis"]} From 0176f89320c8f031f2011cb64770eb5902a9e5d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jun 2024 03:29:59 +0000 Subject: [PATCH 15/29] Bump faker from 25.5.0 to 25.8.0 Bumps [faker](https://github.com/joke2k/faker) from 25.5.0 to 25.8.0. - [Release notes](https://github.com/joke2k/faker/releases) - [Changelog](https://github.com/joke2k/faker/blob/master/CHANGELOG.md) - [Commits](https://github.com/joke2k/faker/compare/v25.5.0...v25.8.0) --- updated-dependencies: - dependency-name: faker dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 01464a815..2ee7144b2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1261,13 +1261,13 @@ tests = ["coverage", "coveralls", "dill", "mock", "nose"] [[package]] name = "faker" -version = "25.5.0" +version = "25.8.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" files = [ - {file = "Faker-25.5.0-py3-none-any.whl", hash = "sha256:edb85040a47ef1b30ccd8c4b6f07ee3cb4bd64aab1483be4efe75816ee2e2e36"}, - {file = "Faker-25.5.0.tar.gz", hash = "sha256:84d454fc9fef0b73428e00bdf45a36c04568c75f22727e990071580840cfbb84"}, + {file = "Faker-25.8.0-py3-none-any.whl", hash = "sha256:4c40b34a9c569018d4f9d6366d71a4da8a883d5ddf2b23197be5370f29b7e1b6"}, + {file = "Faker-25.8.0.tar.gz", hash = "sha256:bdec5f2fb057d244ebef6e0ed318fea4dcbdf32c3a1a010766fc45f5d68fc68d"}, ] [package.dependencies] @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "8d3f15d6ffddd4f8ff33a90fb97da9f660c1665603b45e6463292e0e9f97c29c" +content-hash = "ce7453a1b1abd5b3dbd343e42e043bb91e81c89869e6eac0722fd69b2381f3d8" diff --git a/pyproject.toml b/pyproject.toml index 125bbe4d9..080f89289 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ pyjwt = "==2.8.0" python-dotenv = "==1.0.1" sqlalchemy = "==2.0.30" werkzeug = "^3.0.3" -faker = "^25.5.0" +faker = "^25.8.0" async-timeout = "^4.0.3" bleach = "^6.1.0" geojson = "^3.1.0" From b65239a99c55529c3c49db25c52bebab9e5412a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 21:22:59 +0000 Subject: [PATCH 16/29] Bump botocore from 1.34.120 to 1.34.126 Bumps [botocore](https://github.com/boto/botocore) from 1.34.120 to 1.34.126. - [Changelog](https://github.com/boto/botocore/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/botocore/compare/1.34.120...1.34.126) --- updated-dependencies: - dependency-name: botocore dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 18 +++++++++--------- pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2ee7144b2..da6571fb0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -204,17 +204,17 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "awscli" -version = "1.33.2" +version = "1.33.8" description = "Universal Command Line Environment for AWS." optional = false python-versions = ">=3.8" files = [ - {file = "awscli-1.33.2-py3-none-any.whl", hash = "sha256:b8246ef8085df75ba20537c5ac0a621a4dbae1f084dc536bdc49cf2736fd0d3c"}, - {file = "awscli-1.33.2.tar.gz", hash = "sha256:4f692fff0e75fc2505f9a7cb613ec09426c6ed52bcd5a99246f58426b16a1bb6"}, + {file = "awscli-1.33.8-py3-none-any.whl", hash = "sha256:c7e77478ec7c7c59f570d2346eadc45957324833af739b92f1ba0e006e485f7d"}, + {file = "awscli-1.33.8.tar.gz", hash = "sha256:dfd51b250ffbd670d2d703b53ac953e3fcede23ade954fc6bd6fbbee3c181277"}, ] [package.dependencies] -botocore = "1.34.120" +botocore = "1.34.126" colorama = ">=0.2.5,<0.4.7" docutils = ">=0.10,<0.17" PyYAML = ">=3.10,<6.1" @@ -422,13 +422,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.120" +version = "1.34.126" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.120-py3-none-any.whl", hash = "sha256:92bd739938078c7a0b110689a3eee21ecb3954d90653da013d9f98ef1165d6f7"}, - {file = "botocore-1.34.120.tar.gz", hash = "sha256:5cc0fca43cb2aad54917a394a001ac9ba774d21ad6a08828002d54b601776f78"}, + {file = "botocore-1.34.126-py3-none-any.whl", hash = "sha256:7eff883c638fe30e0b036789df32d851e093d12544615a3b90062b42ac85bdbc"}, + {file = "botocore-1.34.126.tar.gz", hash = "sha256:7a8ccb6a7c02456757a984a3a44331b6f51c94cb8b9b287cd045122fd177a4b0"}, ] [package.dependencies] @@ -437,7 +437,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.20.9)"] +crt = ["awscrt (==0.20.11)"] [[package]] name = "build" @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "ce7453a1b1abd5b3dbd343e42e043bb91e81c89869e6eac0722fd69b2381f3d8" +content-hash = "6f824e8e5e271cfe1eae85f61fa990daed593f78edd744f4d3daefce29367e4d" diff --git a/pyproject.toml b/pyproject.toml index 080f89289..0d7573234 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ alembic = "==1.13.1" amqp = "==5.2.0" beautifulsoup4 = "==4.12.3" boto3 = "^1.34.120" -botocore = "^1.34.120" +botocore = "^1.34.126" cachetools = "==5.3.3" celery = {version = "==5.4.0", extras = ["redis"]} certifi = ">=2022.12.7" From b3a22cdddc74c0fe26daf56279e788a20aaaf157 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:07:18 +0000 Subject: [PATCH 17/29] Bump redis from 5.0.5 to 5.0.6 Bumps [redis](https://github.com/redis/redis-py) from 5.0.5 to 5.0.6. - [Release notes](https://github.com/redis/redis-py/releases) - [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES) - [Commits](https://github.com/redis/redis-py/compare/v5.0.5...v5.0.6) --- updated-dependencies: - dependency-name: redis dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index da6571fb0..0c7393675 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3635,13 +3635,13 @@ full = ["numpy"] [[package]] name = "redis" -version = "5.0.5" +version = "5.0.6" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.5-py3-none-any.whl", hash = "sha256:30b47d4ebb6b7a0b9b40c1275a19b87bb6f46b3bed82a89012cf56dea4024ada"}, - {file = "redis-5.0.5.tar.gz", hash = "sha256:3417688621acf6ee368dec4a04dd95881be24efd34c79f00d31f62bb528800ae"}, + {file = "redis-5.0.6-py3-none-any.whl", hash = "sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee"}, + {file = "redis-5.0.6.tar.gz", hash = "sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197"}, ] [package.extras] @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "6f824e8e5e271cfe1eae85f61fa990daed593f78edd744f4d3daefce29367e4d" +content-hash = "8ad47b2ca42462873ea960683134c0a1a0c3957bec4228ccb6e9c9a3f407cbab" diff --git a/pyproject.toml b/pyproject.toml index 0d7573234..a13a7b5c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ urllib3 = "^2.2.1" webencodings = "^0.5.1" itsdangerous = "^2.2.0" jinja2 = "^3.1.4" -redis = "^5.0.5" +redis = "^5.0.6" requests = "^2.32.3" From 4b945e7c64ef909d6981b77df761e23256211bb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:28:17 +0000 Subject: [PATCH 18/29] Bump packaging from 24.0 to 24.1 Bumps [packaging](https://github.com/pypa/packaging) from 24.0 to 24.1. - [Release notes](https://github.com/pypa/packaging/releases) - [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/packaging/compare/24.0...24.1) --- updated-dependencies: - dependency-name: packaging dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0c7393675..7988e2451 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2779,13 +2779,13 @@ test = ["pytest"] [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "8ad47b2ca42462873ea960683134c0a1a0c3957bec4228ccb6e9c9a3f407cbab" +content-hash = "9e9868a56ea34bb554338f337bf50d0d11f82241401987beaa3869514153fea2" diff --git a/pyproject.toml b/pyproject.toml index a13a7b5c7..b9a6248b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ marshmallow-sqlalchemy = "==1.0.0" newrelic = "*" notifications-python-client = "==9.1.0" oscrypto = "==1.3.0" -packaging = "==24.0" +packaging = "==24.1" poetry-dotenv-plugin = "==0.2.0" psycopg2-binary = "==2.9.9" pyjwt = "==2.8.0" From 86932059799b93cd07a92f9efedc70abfcc897e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:41:53 +0000 Subject: [PATCH 19/29] Bump bandit from 1.7.8 to 1.7.9 Bumps [bandit](https://github.com/PyCQA/bandit) from 1.7.8 to 1.7.9. - [Release notes](https://github.com/PyCQA/bandit/releases) - [Commits](https://github.com/PyCQA/bandit/compare/1.7.8...1.7.9) --- updated-dependencies: - dependency-name: bandit dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7988e2451..f18f3e92b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -223,13 +223,13 @@ s3transfer = ">=0.10.0,<0.11.0" [[package]] name = "bandit" -version = "1.7.8" +version = "1.7.9" description = "Security oriented static analyser for python code." optional = false python-versions = ">=3.8" files = [ - {file = "bandit-1.7.8-py3-none-any.whl", hash = "sha256:509f7af645bc0cd8fd4587abc1a038fc795636671ee8204d502b933aee44f381"}, - {file = "bandit-1.7.8.tar.gz", hash = "sha256:36de50f720856ab24a24dbaa5fee2c66050ed97c1477e0a1159deab1775eab6b"}, + {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, + {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, ] [package.dependencies] From 6a9f2d326fdd31a1df6cdcef63f780fab4938b84 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 14 Jun 2024 14:15:05 -0700 Subject: [PATCH 20/29] code review feedback --- tests/app/user/test_rest_verify.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/app/user/test_rest_verify.py b/tests/app/user/test_rest_verify.py index 40ede7ebc..d216df57e 100644 --- a/tests/app/user/test_rest_verify.py +++ b/tests/app/user/test_rest_verify.py @@ -500,10 +500,6 @@ 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"} mocker.patch("app.celery.scheduled_tasks.redis_store.raw_set") From 9a146bdd4d78d5d51bc560a98e076fef541b75ab Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 14 Jun 2024 14:19:08 -0700 Subject: [PATCH 21/29] flake8 --- tests/app/user/test_rest_verify.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/app/user/test_rest_verify.py b/tests/app/user/test_rest_verify.py index d216df57e..81d813039 100644 --- a/tests/app/user/test_rest_verify.py +++ b/tests/app/user/test_rest_verify.py @@ -500,7 +500,6 @@ 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" - mocker.patch("app.celery.scheduled_tasks.redis_store.raw_set") data = {"to": None, "next": "/services"} From 0e1aa2f28b62890b55d5f412cc76488e15b4f0e0 Mon Sep 17 00:00:00 2001 From: John Skiles Skinner Date: Fri, 14 Jun 2024 17:11:16 -0700 Subject: [PATCH 22/29] Proposal for how we create a service instance to upgrade --- terraform/sandbox/main.tf | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/terraform/sandbox/main.tf b/terraform/sandbox/main.tf index 5a210c402..b0b6381bd 100644 --- a/terraform/sandbox/main.tf +++ b/terraform/sandbox/main.tf @@ -22,7 +22,7 @@ module "database" { rds_plan_name = "micro-psql" } -module "redis" { +module "redis" { # default v6.2; delete after v7.0 resource is bound source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v1.0.0" cf_org_name = local.cf_org_name @@ -31,6 +31,20 @@ module "redis" { redis_plan_name = "redis-dev" } +module "redis-v70" { + source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v1.0.0" + + cf_org_name = local.cf_org_name + cf_space_name = local.cf_space_name + name = "${local.app_name}-redis-v70-${local.env}" + redis_plan_name = "redis-dev" + json_params = jsonencode( + { + "engineVersion" : "7.0", + } + ) +} + module "csv_upload_bucket" { source = "github.com/GSA-TTS/terraform-cloudgov//s3?ref=v1.0.0" From 59615880b05a7648e50adef6f771809fa1e43fa2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:42:04 +0000 Subject: [PATCH 23/29] Bump urllib3 from 2.2.1 to 2.2.2 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.1 to 2.2.2. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.2.1...2.2.2) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 16 ++++++++++------ pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index d35c3c070..b1ce1dc0c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2098,9 +2098,13 @@ 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"}, @@ -2489,7 +2493,6 @@ 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"}, ] @@ -3472,6 +3475,7 @@ 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"}, @@ -4352,13 +4356,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -4748,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "9e9868a56ea34bb554338f337bf50d0d11f82241401987beaa3869514153fea2" +content-hash = "d025e0bec9ca03362c6f5de2163ac3aa3153ff8a2614abf3f625d459847962ae" diff --git a/pyproject.toml b/pyproject.toml index b9a6248b7..2fd4d49ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ python-dateutil = "^2.9.0.post0" pyyaml = "^6.0.1" s3transfer = "^0.10.1" six = "^1.16.0" -urllib3 = "^2.2.1" +urllib3 = "^2.2.2" webencodings = "^0.5.1" itsdangerous = "^2.2.0" jinja2 = "^3.1.4" From e7c5e756493c3565f4c0b69e368f78b52c3c41ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 03:44:18 +0000 Subject: [PATCH 24/29] Bump botocore from 1.34.126 to 1.34.128 Bumps [botocore](https://github.com/boto/botocore) from 1.34.126 to 1.34.128. - [Changelog](https://github.com/boto/botocore/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/botocore/compare/1.34.126...1.34.128) --- updated-dependencies: - dependency-name: botocore dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 16 ++++++++-------- pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index b1ce1dc0c..caf6e369a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -204,17 +204,17 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "awscli" -version = "1.33.8" +version = "1.33.10" description = "Universal Command Line Environment for AWS." optional = false python-versions = ">=3.8" files = [ - {file = "awscli-1.33.8-py3-none-any.whl", hash = "sha256:c7e77478ec7c7c59f570d2346eadc45957324833af739b92f1ba0e006e485f7d"}, - {file = "awscli-1.33.8.tar.gz", hash = "sha256:dfd51b250ffbd670d2d703b53ac953e3fcede23ade954fc6bd6fbbee3c181277"}, + {file = "awscli-1.33.10-py3-none-any.whl", hash = "sha256:f3cdd988c4fd1cf25e8fe16fccc514147469ed102b43c128e29374d48337d7a0"}, + {file = "awscli-1.33.10.tar.gz", hash = "sha256:0a5b4b5555be2c7324bf9e748bc24c17c95ef1b070e8f324354ef8747ce984e9"}, ] [package.dependencies] -botocore = "1.34.126" +botocore = "1.34.128" colorama = ">=0.2.5,<0.4.7" docutils = ">=0.10,<0.17" PyYAML = ">=3.10,<6.1" @@ -422,13 +422,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.126" +version = "1.34.128" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.126-py3-none-any.whl", hash = "sha256:7eff883c638fe30e0b036789df32d851e093d12544615a3b90062b42ac85bdbc"}, - {file = "botocore-1.34.126.tar.gz", hash = "sha256:7a8ccb6a7c02456757a984a3a44331b6f51c94cb8b9b287cd045122fd177a4b0"}, + {file = "botocore-1.34.128-py3-none-any.whl", hash = "sha256:db67fda136c372ab3fa432580c819c89ba18d28a6152a4d2a7ea40d44082892e"}, + {file = "botocore-1.34.128.tar.gz", hash = "sha256:8d8e03f7c8c080ecafda72036eb3b482d649f8417c90b5dca33b7c2c47adb0c9"}, ] [package.dependencies] @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "d025e0bec9ca03362c6f5de2163ac3aa3153ff8a2614abf3f625d459847962ae" +content-hash = "f534a8960f26daf5902943d111e5bf387bf1f98fab56da4bb5e6c6a90a16063f" diff --git a/pyproject.toml b/pyproject.toml index 2fd4d49ac..3d7e24d49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ alembic = "==1.13.1" amqp = "==5.2.0" beautifulsoup4 = "==4.12.3" boto3 = "^1.34.120" -botocore = "^1.34.126" +botocore = "^1.34.128" cachetools = "==5.3.3" celery = {version = "==5.4.0", extras = ["redis"]} certifi = ">=2022.12.7" From a6df136f23e836b0399415352478bcf46446315d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 03:53:26 +0000 Subject: [PATCH 25/29] Bump boto3 from 1.34.120 to 1.34.128 Bumps [boto3](https://github.com/boto/boto3) from 1.34.120 to 1.34.128. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.34.120...1.34.128) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index caf6e369a..758f3d17b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -403,17 +403,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.120" +version = "1.34.128" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.120-py3-none-any.whl", hash = "sha256:3c42bc309246a761413f6e152f307f009e80e7c9fd03dd9e6c0dc8ab8b3a8fc1"}, - {file = "boto3-1.34.120.tar.gz", hash = "sha256:38893db8269d25b72cc6fbab97633bfc863eefde5456847169d06149a16aa6e0"}, + {file = "boto3-1.34.128-py3-none-any.whl", hash = "sha256:a048ff980a81cd652724a73bc496c519b336fabe19cc8bfc6c53b2ff6eb22c7b"}, + {file = "boto3-1.34.128.tar.gz", hash = "sha256:43a6e99f53a8d34b3b4dbe424dbcc6b894350dc41a85b0af7c7bc24a7ec2cead"}, ] [package.dependencies] -botocore = ">=1.34.120,<1.35.0" +botocore = ">=1.34.128,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "f534a8960f26daf5902943d111e5bf387bf1f98fab56da4bb5e6c6a90a16063f" +content-hash = "460acc2fefc9038bbbf2290afb1c657ea43018dd8abb626370044988d49744d6" diff --git a/pyproject.toml b/pyproject.toml index 3d7e24d49..f9cf4ff6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ python = "^3.12.2" alembic = "==1.13.1" amqp = "==5.2.0" beautifulsoup4 = "==4.12.3" -boto3 = "^1.34.120" +boto3 = "^1.34.128" botocore = "^1.34.128" cachetools = "==5.3.3" celery = {version = "==5.4.0", extras = ["redis"]} From 8515c9d9a63fe0d30da4e575c678489c7724eadc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 04:03:56 +0000 Subject: [PATCH 26/29] Bump newrelic from 9.10.0 to 9.11.0 Bumps [newrelic](https://github.com/newrelic/newrelic-python-agent) from 9.10.0 to 9.11.0. - [Release notes](https://github.com/newrelic/newrelic-python-agent/releases) - [Commits](https://github.com/newrelic/newrelic-python-agent/compare/v9.10.0...v9.11.0) --- updated-dependencies: - dependency-name: newrelic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 60 ++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/poetry.lock b/poetry.lock index 758f3d17b..5d95cdd54 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2608,40 +2608,40 @@ files = [ [[package]] name = "newrelic" -version = "9.10.0" +version = "9.11.0" description = "New Relic Python Agent" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "newrelic-9.10.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a4d4e5670082225ca7ef0ee986ef8e6588f4e530a05d43d66f9368459c0b1f18"}, - {file = "newrelic-9.10.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f4605bc4feb114235e242dfe260b75ec85d0894f5400aa7f30e75fbbc0423b3f"}, - {file = "newrelic-9.10.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d3be6c97d007ceb142f908f5ab2444807b44dc600a0b7f3254dc685b5b03fd10"}, - {file = "newrelic-9.10.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4e573d49c1543a488d6567906a9b2cb0c748cdbf80724c322b06874f8e47c789"}, - {file = "newrelic-9.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae0515f7ab19f1a5dd14e31506420d1b86014c5e1340c2a210833248bc765dae"}, - {file = "newrelic-9.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acf5cdcafd2971933ad2f9e836284957f4a3eababe88f063cf53b1b1f67f1a16"}, - {file = "newrelic-9.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5d18236bf4a80fca4eb1db03448ed72bf8e16b84b3a4ed5fcc29bb91c2d05d54"}, - {file = "newrelic-9.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:744c815f15ec06e441c11a6c57042d2eca8c41401c11de6f47b3e105d952b9bd"}, - {file = "newrelic-9.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:524ed5bfa09d330746b45e0087765da994ca34802cce032063041e404e58414c"}, - {file = "newrelic-9.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ad9cd5459b8c620ab7a876bd5d920c3ef2943948d1262a42289d4f8d16dadab"}, - {file = "newrelic-9.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4404c649b5e6165dcdd59091092c19b292a43cc96520d5ffd718b628fb866096"}, - {file = "newrelic-9.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2576bbec0b640d9b76454dcfd5b2f03078e0bb062a7ea3952a8db7b9972c352"}, - {file = "newrelic-9.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77537a020ce84033f39210e46cc43bb3927cec3fb4b34b5c4df802e96fddaedf"}, - {file = "newrelic-9.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2236f70b8c6aa79635f2175e7315d032f3a80dfd65ad9c9ed12a921f5df4c655"}, - {file = "newrelic-9.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b8201a33caf7632b2e55e3f9687584ad6956aaf5751485cdb2bad7c428a9b400"}, - {file = "newrelic-9.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6ed4bc2c9a44dfe59958eeecf1f327f0a0fb6324b5e609515bc511944d12db74"}, - {file = "newrelic-9.10.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cc3ddb26c0615ba4e18f87453bca57f0688a43d2fcdd50e2771a77515cfc3ba"}, - {file = "newrelic-9.10.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09912303e04bee6aa1fe1c671e87b4e8e55461081a96210895828798f5ba8c3f"}, - {file = "newrelic-9.10.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40368dca0d423efe40b210686d7018787d4365a24ee1deca136b3b7c9d850325"}, - {file = "newrelic-9.10.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56f4c309a07a2c66243b12d18056c32aa704735469741495642c31be4a1c77fa"}, - {file = "newrelic-9.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d68fc707d896dc7da8d6939bcc1f995bf9e463c2b911fc63250a10e1502a234"}, - {file = "newrelic-9.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cd462804a6ede617fb3b4b126e9083b3ee8b4ed1250f7cc12299ebacb785432"}, - {file = "newrelic-9.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ceef4fef2a5cffb69e9e1742bd18a35625ca62c3856c7016c22be68ec876753d"}, - {file = "newrelic-9.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1f11d9c17b50982fcc39de71f6592a61920ec5e5c29b9105edc9f8fb7f2480b9"}, - {file = "newrelic-9.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf6757d422954e61082715dbba4208cae17bf3720006bc337c3f87f19ede2876"}, - {file = "newrelic-9.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae84bacfdc60792bd04e681027cc5c58e6737a04c652e9be2eda84abe21f57f5"}, - {file = "newrelic-9.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:667722cf1f4ed9f6cd99f4fbe247fc2bdb941935528e14a93659ba2c651dc889"}, - {file = "newrelic-9.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0c18210648889416da3de61aa282248e012cb507ba9841511407f922fff9a52"}, - {file = "newrelic-9.10.0.tar.gz", hash = "sha256:02db25b0fd2fc835efe4a7f1c92dbc5bbb95125341aba07152041aa6a5666cda"}, + {file = "newrelic-9.11.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:87670d872c3abc36203e10f93d266c8f36ad2bd06fb54e790001a409f9e2f40f"}, + {file = "newrelic-9.11.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:11653fd14f55999c5058b4dde8c721833076c0bd3efe668296725a622e9e7de8"}, + {file = "newrelic-9.11.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:72dd3eb190c62bb54aa59029f0d6ac1420c2050b3aaf88d947fc7f62ec58d97f"}, + {file = "newrelic-9.11.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02eab15af4a08b870bcfdbc56390ecbb9dcacd144fe77f39a26d1be207bd30f0"}, + {file = "newrelic-9.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f477cdda9b998205084b822089b3ee4a8a2d9cd66b6f12487c9f9002566c5cb"}, + {file = "newrelic-9.11.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcec4173cd0f83420e6f61f92955065f1d460075af5e5bf88a5fea746e3cc180"}, + {file = "newrelic-9.11.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8664e3b9e6ee0f78806b0cf7c90656a1a86d13232c2e0be18a1b1eb452f3f5d1"}, + {file = "newrelic-9.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f1e473eb0505cb91ab9a4155321eabe13a2f6b93fb3c41d6f10e5486276be60"}, + {file = "newrelic-9.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f95eb366ff714bce32476d256551b853247a72398ec46a89148ef5108509aa8"}, + {file = "newrelic-9.11.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553674a66ef2c2206852b415b74e3c2fb7ed2b92e9800b68394d577f6aa1133e"}, + {file = "newrelic-9.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:21e7b52d5b214bba3534ced166e6ec991117772815020bec38b0571fdcecbaf4"}, + {file = "newrelic-9.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10cb7f7a78c49580602b90f367f3378264e495f2f3706734f88ced7e7ca9b033"}, + {file = "newrelic-9.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34b25d1beaf19825409f3d915a5bafa87b7b9230415821422be1e78e988750b7"}, + {file = "newrelic-9.11.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02139458aefba86a4572cb8214f91a942103d24d5502395f64d6d7a4ad3f25"}, + {file = "newrelic-9.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3283885bcf31d9cbf8facb0004508a4eaa652a62471e0b724d26f9738a291979"}, + {file = "newrelic-9.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0d43a0891bf71333f6a6253cf87dea2c9009e22699a2acfd93608125a33b1936"}, + {file = "newrelic-9.11.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7903ba71ce5a4b2840f6d3c63ecd0fb3a018d2aceb915b48133c13c4a60185f"}, + {file = "newrelic-9.11.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d88fa17a515fb002eb14570800e4bfa69ac87ac27e6e2a96bc2bc9b60c80057a"}, + {file = "newrelic-9.11.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ceac1d8f13da38fa1b41c8202a91d3b4345e06adb655deaae0df08911fda56f"}, + {file = "newrelic-9.11.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ffc0d8d490de0f12df70db637481aaadb8a43fb6d71ba8866dc14242aa5edad4"}, + {file = "newrelic-9.11.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f6e1bb0df8ff2b54195baac41fddc0e15ea1bdf1deb6af49153487696355181"}, + {file = "newrelic-9.11.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5d2d0814e1aa9de5bd55797ff8c426d98200ba46ca14dbca15557d0f17cfb4e"}, + {file = "newrelic-9.11.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b33539345c7cf349b65a176a30ab38e2998b071512a7450f5c5b89ac6c097006"}, + {file = "newrelic-9.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c073f4c26539d6d74fbf4bac7f5046cac578975fb2cf77b156f802f1b39835e"}, + {file = "newrelic-9.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76eb4cc599645a38a459b0002696d9c84844fecb02cf07bc18a4a91f737e438e"}, + {file = "newrelic-9.11.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d08587e694f5c517e55fb7119f924c64569d2e7ec4968ef761fc1f7bd1f40c"}, + {file = "newrelic-9.11.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bc5c1b8a51946f64c34fc5fa29ce0221c4927a65c7f4435b3b8adeb29b9812d2"}, + {file = "newrelic-9.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2010ed2793294a7e3c1057ec301d48997ed05dcef114d4c25120ac771f66bac1"}, + {file = "newrelic-9.11.0.tar.gz", hash = "sha256:94369792d61ccf21469c35cf66886c32350a180d8e782c0d28ec66411db29474"}, ] [package.extras] From a75bc67d23dd7ada44ad38593df080222b466a30 Mon Sep 17 00:00:00 2001 From: John Skiles Skinner Date: Tue, 18 Jun 2024 10:14:18 -0700 Subject: [PATCH 27/29] Add Redis v7.0 module, terraform-cloudgov v1.0.0 --- terraform/demo/main.tf | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/terraform/demo/main.tf b/terraform/demo/main.tf index 615f92670..0a0c608cf 100644 --- a/terraform/demo/main.tf +++ b/terraform/demo/main.tf @@ -26,6 +26,20 @@ module "redis" { redis_plan_name = "redis-dev" } +module "redis-v70" { + source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v1.0.0" + + cf_org_name = local.cf_org_name + cf_space_name = local.cf_space_name + name = "${local.app_name}-redis-v70-${local.env}" + redis_plan_name = "redis-dev" + json_params = jsonencode( + { + "engineVersion" : "7.0", + } + ) +} + module "csv_upload_bucket" { source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1" From e52f44a0d96399b1a830c4f7d44fed66518d8490 Mon Sep 17 00:00:00 2001 From: John Skiles Skinner Date: Tue, 18 Jun 2024 10:16:16 -0700 Subject: [PATCH 28/29] Comment explaining resource to delete --- terraform/demo/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/demo/main.tf b/terraform/demo/main.tf index 0a0c608cf..e252ddf6d 100644 --- a/terraform/demo/main.tf +++ b/terraform/demo/main.tf @@ -16,7 +16,7 @@ module "database" { rds_plan_name = "micro-psql" } -module "redis" { +module "redis" { # default v6.2; delete after v7.0 resource is bound source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1" cf_org_name = local.cf_org_name From 96fc71a88d66bdc0df3ef6a9428e36efc5362891 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 21:30:55 +0000 Subject: [PATCH 29/29] Bump phonenumbers from 8.13.38 to 8.13.39 Bumps [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) from 8.13.38 to 8.13.39. - [Commits](https://github.com/daviddrysdale/python-phonenumbers/compare/v8.13.38...v8.13.39) --- updated-dependencies: - dependency-name: phonenumbers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5d95cdd54..1966be25a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2826,13 +2826,13 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.13.38" +version = "8.13.39" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." optional = false python-versions = "*" files = [ - {file = "phonenumbers-8.13.38-py2.py3-none-any.whl", hash = "sha256:d22aa747fb591ef2a18afec13cab5a0e294ab20fce5a1560e4949e459e70eeef"}, - {file = "phonenumbers-8.13.38.tar.gz", hash = "sha256:2822c74ee9334e9d8ad792fc352cc8d21004307349b6b1bb61da12937fa2eaba"}, + {file = "phonenumbers-8.13.39-py2.py3-none-any.whl", hash = "sha256:3ad2d086fa71e7eef409001b9195ac54bebb0c6e3e752209b558ca192c9229a0"}, + {file = "phonenumbers-8.13.39.tar.gz", hash = "sha256:db7ca4970d206b2056231105300753b1a5b229f43416f8c2b3010e63fbb68d77"}, ] [[package]] @@ -4752,4 +4752,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "460acc2fefc9038bbbf2290afb1c657ea43018dd8abb626370044988d49744d6" +content-hash = "122fe6459adad8b8105d12739e4db159ae39fc32e969d02c7c24436ed3fb85b1" diff --git a/pyproject.toml b/pyproject.toml index f9cf4ff6e..ff5d6a959 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ geojson = "^3.1.0" govuk-bank-holidays = "^0.14" numpy = "^1.26.4" ordered-set = "^4.1.0" -phonenumbers = "^8.13.38" +phonenumbers = "^8.13.39" python-json-logger = "^2.0.7" pytz = "^2024.1" regex = "^2024.5.15"