mirror of
https://github.com/GSA/notifications-api.git
synced 2026-09-10 18:23:01 -04:00
clean flake8 except provider code
This commit is contained in:
@@ -18,7 +18,10 @@ from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
from app.serialised_models import SerialisedService
|
||||
|
||||
GENERAL_TOKEN_ERROR_MESSAGE = 'Invalid token: make sure your API token matches the example at https://docs.notifications.service.gov.uk/rest-api.html#authorisation-header' # nosec B105
|
||||
GENERAL_TOKEN_ERROR_MESSAGE = '''
|
||||
Invalid token: make sure your API token matches the example
|
||||
at https://docs.notifications.service.gov.uk/rest-api.html#authorisation-header
|
||||
''' # nosec B105
|
||||
|
||||
AUTH_DB_CONNECTION_DURATION_SECONDS = Histogram(
|
||||
'auth_db_connection_duration_seconds',
|
||||
|
||||
+10
-3
@@ -10,18 +10,25 @@ default_access_key = os.environ.get('AWS_ACCESS_KEY_ID')
|
||||
default_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
|
||||
default_region = os.environ.get('AWS_REGION')
|
||||
|
||||
def get_s3_file(bucket_name, file_location, access_key=default_access_key, secret_key=default_secret_key, region=default_region):
|
||||
|
||||
def get_s3_file(
|
||||
bucket_name, file_location, access_key=default_access_key, secret_key=default_secret_key, region=default_region
|
||||
):
|
||||
s3_file = get_s3_object(bucket_name, file_location, access_key, secret_key, region)
|
||||
return s3_file.get()['Body'].read().decode('utf-8')
|
||||
|
||||
|
||||
def get_s3_object(bucket_name, file_location, access_key=default_access_key, secret_key=default_secret_key, region=default_region):
|
||||
def get_s3_object(
|
||||
bucket_name, file_location, access_key=default_access_key, secret_key=default_secret_key, region=default_region
|
||||
):
|
||||
session = Session(aws_access_key_id=access_key, aws_secret_access_key=secret_key, region_name=region)
|
||||
s3 = session.resource('s3')
|
||||
return s3.Object(bucket_name, file_location)
|
||||
|
||||
|
||||
def file_exists(bucket_name, file_location, access_key=default_access_key, secret_key=default_secret_key, region=default_region):
|
||||
def file_exists(
|
||||
bucket_name, file_location, access_key=default_access_key, secret_key=default_secret_key, region=default_region
|
||||
):
|
||||
try:
|
||||
# try and access metadata of object
|
||||
get_s3_object(bucket_name, file_location, access_key, secret_key, region).metadata
|
||||
|
||||
@@ -29,7 +29,10 @@ def process_ses_results(self, response):
|
||||
ses_message = json.loads(response["Message"])
|
||||
notification_type = ses_message["notificationType"]
|
||||
# TODO remove after smoke testing on prod is implemented
|
||||
current_app.logger.info(f"Attempting to process SES delivery status message from SNS with type: {notification_type} and body: {ses_message}")
|
||||
current_app.logger.info(
|
||||
f"Attempting to process SES delivery status message \
|
||||
from SNS with type: {notification_type} and body: {ses_message}"
|
||||
)
|
||||
bounce_message = None
|
||||
|
||||
if notification_type == 'Bounce':
|
||||
@@ -49,13 +52,16 @@ def process_ses_results(self, response):
|
||||
message_time = iso8601.parse_date(ses_message["mail"]["timestamp"]).replace(tzinfo=None)
|
||||
if datetime.utcnow() - message_time < timedelta(minutes=5):
|
||||
current_app.logger.info(
|
||||
f"notification not found for reference: {reference} (while attempting update to {notification_status}). "
|
||||
f"Callback may have arrived before notification was persisted to the DB. Adding task to retry queue"
|
||||
f"notification not found for reference: {reference} \
|
||||
(while attempting update to {notification_status}). "
|
||||
f"Callback may have arrived before notification was \
|
||||
persisted to the DB. Adding task to retry queue"
|
||||
)
|
||||
self.retry(queue=QueueNames.RETRY)
|
||||
else:
|
||||
current_app.logger.warning(
|
||||
"notification not found for reference: {} (while attempting update to {})".format(reference, notification_status)
|
||||
"notification not found for reference: {} (while \
|
||||
attempting update to {})".format(reference, notification_status)
|
||||
)
|
||||
return
|
||||
|
||||
@@ -102,6 +108,7 @@ def process_ses_results(self, response):
|
||||
current_app.logger.exception("Error processing SES results: {}".format(type(e)))
|
||||
self.retry(queue=QueueNames.RETRY)
|
||||
|
||||
|
||||
def determine_notification_bounce_type(ses_message):
|
||||
notification_type = ses_message["notificationType"]
|
||||
if notification_type in ["Delivery", "Complaint"]:
|
||||
@@ -116,14 +123,16 @@ def determine_notification_bounce_type(ses_message):
|
||||
return "Permanent"
|
||||
return "Temporary"
|
||||
|
||||
|
||||
def determine_notification_type(ses_message):
|
||||
notification_type = ses_message["notificationType"]
|
||||
if notification_type not in ["Bounce","Complaint","Delivery"]:
|
||||
if notification_type not in ["Bounce", "Complaint", "Delivery"]:
|
||||
raise KeyError(f"Unhandled sns notification type {notification_type}")
|
||||
if notification_type == 'Bounce':
|
||||
return determine_notification_bounce_type(ses_message)
|
||||
return notification_type
|
||||
|
||||
|
||||
def _determine_provider_response(ses_message):
|
||||
if ses_message["notificationType"] != "Bounce":
|
||||
return None
|
||||
@@ -175,7 +184,9 @@ def get_aws_responses(ses_message):
|
||||
|
||||
def handle_complaint(ses_message):
|
||||
recipient_email = remove_emails_from_complaint(ses_message)[0]
|
||||
current_app.logger.info("Complaint from SES: \n{}".format(json.dumps(ses_message).replace("{", "(").replace("}", ")")))
|
||||
current_app.logger.info(
|
||||
"Complaint from SES: \n{}".format(json.dumps(ses_message).replace("{", "(").replace("}", ")"))
|
||||
)
|
||||
try:
|
||||
reference = ses_message["mail"]["messageId"]
|
||||
except KeyError as e:
|
||||
@@ -219,7 +230,9 @@ def check_and_queue_callback_task(notification):
|
||||
service_callback_api = get_service_delivery_status_callback_api_for_service(service_id=notification.service_id)
|
||||
if service_callback_api:
|
||||
notification_data = create_delivery_status_callback_data(notification, service_callback_api)
|
||||
send_delivery_status_to_service.apply_async([str(notification.id), notification_data], queue=QueueNames.CALLBACKS)
|
||||
send_delivery_status_to_service.apply_async(
|
||||
[str(notification.id), notification_data], queue=QueueNames.CALLBACKS
|
||||
)
|
||||
|
||||
|
||||
def _check_and_queue_complaint_callback_task(complaint, notification, recipient):
|
||||
@@ -228,4 +241,3 @@ def _check_and_queue_complaint_callback_task(complaint, notification, recipient)
|
||||
if service_callback_api:
|
||||
complaint_data = create_complaint_callback_data(complaint, notification, service_callback_api, recipient)
|
||||
send_complaint_to_service.apply_async([complaint_data], queue=QueueNames.CALLBACKS)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from flask import current_app
|
||||
from notifications_utils.template import SMSMessageTemplate
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ def create_fake_letter_response_file(self, reference):
|
||||
dvla_response_data = '{}|Sent|0|Sorted'.format(reference)
|
||||
|
||||
# try and find a filename that hasn't been taken yet - from a random time within the last 30 seconds
|
||||
for i in sorted(range(30), key=lambda _: random.random()): # nosec B311 - not security related
|
||||
for i in sorted(range(30), key=lambda _: random.random()): # nosec B311 - not security related
|
||||
upload_file_name = 'NOTIFY-{}-RSP.TXT'.format((now - timedelta(seconds=i)).strftime('%Y%m%d%H%M%S'))
|
||||
if not file_exists(current_app.config['DVLA_RESPONSE_BUCKET_NAME'], upload_file_name):
|
||||
break
|
||||
|
||||
@@ -114,7 +114,7 @@ def create_delivery_status_callback_data(notification, service_callback_api):
|
||||
"notification_client_reference": notification.client_reference,
|
||||
"notification_to": notification.to,
|
||||
"notification_status": notification.status,
|
||||
"notification_provider_response": notification.provider_response, # TODO do we have a test for provider_response
|
||||
"notification_provider_response": notification.provider_response, # TODO do we test for provider_response?
|
||||
"notification_created_at": notification.created_at.strftime(DATETIME_FORMAT),
|
||||
"notification_updated_at":
|
||||
notification.updated_at.strftime(DATETIME_FORMAT) if notification.updated_at else None,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
from celery import current_app
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
'''
|
||||
Base Exceptions for sending notifications that fail
|
||||
@@ -38,7 +35,7 @@ class NotificationProviderClients(object):
|
||||
return self.email_clients.get(name)
|
||||
|
||||
def get_client_by_name_and_type(self, name, notification_type):
|
||||
assert notification_type in ['email', 'sms'] # nosec B101
|
||||
assert notification_type in ['email', 'sms'] # nosec B101
|
||||
|
||||
if notification_type == 'email':
|
||||
return self.get_email_client(name)
|
||||
|
||||
@@ -8,16 +8,20 @@ def find_by_service_name(services, service_name):
|
||||
return services[i]
|
||||
return None
|
||||
|
||||
|
||||
def extract_cloudfoundry_config():
|
||||
vcap_services = json.loads(os.environ['VCAP_SERVICES'])
|
||||
|
||||
# Postgres config
|
||||
os.environ['SQLALCHEMY_DATABASE_URI'] = vcap_services['aws-rds'][0]['credentials']['uri'].replace('postgres','postgresql')
|
||||
os.environ['SQLALCHEMY_DATABASE_URI'] = \
|
||||
vcap_services['aws-rds'][0]['credentials']['uri'].replace('postgres', 'postgresql')
|
||||
# Redis config
|
||||
os.environ['REDIS_URL'] = vcap_services['aws-elasticache-redis'][0]['credentials']['uri'].replace('redis://','rediss://')
|
||||
os.environ['REDIS_URL'] = \
|
||||
vcap_services['aws-elasticache-redis'][0]['credentials']['uri'].replace('redis://', 'rediss://')
|
||||
|
||||
# CSV Upload Bucket Name
|
||||
bucket_service = find_by_service_name(vcap_services['s3'], f"notifications-api-csv-upload-bucket-{os.environ['DEPLOY_ENV']}")
|
||||
bucket_service = \
|
||||
find_by_service_name(vcap_services['s3'], f"notifications-api-csv-upload-bucket-{os.environ['DEPLOY_ENV']}")
|
||||
if bucket_service:
|
||||
os.environ['CSV_UPLOAD_BUCKET_NAME'] = bucket_service['credentials']['bucket']
|
||||
os.environ['CSV_UPLOAD_ACCESS_KEY'] = bucket_service['credentials']['access_key_id']
|
||||
@@ -25,7 +29,8 @@ def extract_cloudfoundry_config():
|
||||
os.environ['CSV_UPLOAD_REGION'] = bucket_service['credentials']['region']
|
||||
|
||||
# Contact List Bucket Name
|
||||
bucket_service = find_by_service_name(vcap_services['s3'], f"notifications-api-contact-list-bucket-{os.environ['DEPLOY_ENV']}")
|
||||
bucket_service = \
|
||||
find_by_service_name(vcap_services['s3'], f"notifications-api-contact-list-bucket-{os.environ['DEPLOY_ENV']}")
|
||||
if bucket_service:
|
||||
os.environ['CONTACT_LIST_BUCKET_NAME'] = bucket_service['credentials']['bucket']
|
||||
os.environ['CONTACT_LIST_ACCESS_KEY'] = bucket_service['credentials']['access_key_id']
|
||||
|
||||
@@ -142,86 +142,6 @@ def purge_functional_test_data(user_email_prefix):
|
||||
delete_model_user(usr)
|
||||
|
||||
|
||||
@notify_command()
|
||||
def backfill_notification_statuses():
|
||||
"""
|
||||
DEPRECATED. Populates notification_status.
|
||||
|
||||
This will be used to populate the new `Notification._status_fkey` with the old
|
||||
`Notification._status_enum`
|
||||
"""
|
||||
LIMIT = 250000
|
||||
subq = "SELECT id FROM notification_history WHERE notification_status is NULL LIMIT {}".format(LIMIT) # nosec B608 no user-controlled input
|
||||
update = "UPDATE notification_history SET notification_status = status WHERE id in ({})".format(subq) # nosec B608 no user-controlled input
|
||||
result = db.session.execute(subq).fetchall()
|
||||
|
||||
while len(result) > 0:
|
||||
db.session.execute(update)
|
||||
print('commit {} updates at {}'.format(LIMIT, datetime.utcnow()))
|
||||
db.session.commit()
|
||||
result = db.session.execute(subq).fetchall()
|
||||
|
||||
|
||||
@notify_command()
|
||||
def update_notification_international_flag():
|
||||
"""
|
||||
DEPRECATED. Set notifications.international=false.
|
||||
"""
|
||||
# 250,000 rows takes 30 seconds to update.
|
||||
subq = "select id from notifications where international is null limit 250000"
|
||||
update = "update notifications set international = False where id in ({})".format(subq) # nosec B608 no user-controlled input
|
||||
result = db.session.execute(subq).fetchall()
|
||||
|
||||
while len(result) > 0:
|
||||
db.session.execute(update)
|
||||
print('commit 250000 updates at {}'.format(datetime.utcnow()))
|
||||
db.session.commit()
|
||||
result = db.session.execute(subq).fetchall()
|
||||
|
||||
# Now update notification_history
|
||||
subq_history = "select id from notification_history where international is null limit 250000"
|
||||
update_history = "update notification_history set international = False where id in ({})".format(subq_history) # nosec B608 no user-controlled input
|
||||
result_history = db.session.execute(subq_history).fetchall()
|
||||
while len(result_history) > 0:
|
||||
db.session.execute(update_history)
|
||||
print('commit 250000 updates at {}'.format(datetime.utcnow()))
|
||||
db.session.commit()
|
||||
result_history = db.session.execute(subq_history).fetchall()
|
||||
|
||||
|
||||
@notify_command()
|
||||
def fix_notification_statuses_not_in_sync():
|
||||
"""
|
||||
DEPRECATED.
|
||||
This will be used to correct an issue where Notification._status_enum and NotificationHistory._status_fkey
|
||||
became out of sync. See 979e90a.
|
||||
|
||||
Notification._status_enum is the source of truth so NotificationHistory._status_fkey will be updated with
|
||||
these values.
|
||||
"""
|
||||
MAX = 10000
|
||||
|
||||
subq = "SELECT id FROM notifications WHERE cast (status as text) != notification_status LIMIT {}".format(MAX) # nosec B608 no user-controlled input
|
||||
update = "UPDATE notifications SET notification_status = status WHERE id in ({})".format(subq) # nosec B608 no user-controlled input
|
||||
result = db.session.execute(subq).fetchall()
|
||||
|
||||
while len(result) > 0:
|
||||
db.session.execute(update)
|
||||
print('Committed {} updates at {}'.format(len(result), datetime.utcnow()))
|
||||
db.session.commit()
|
||||
result = db.session.execute(subq).fetchall()
|
||||
|
||||
subq_hist = "SELECT id FROM notification_history WHERE cast (status as text) != notification_status LIMIT {}".format(MAX) # nosec B608
|
||||
update = "UPDATE notification_history SET notification_status = status WHERE id in ({})".format(subq_hist) # nosec B608 no user-controlled input
|
||||
result = db.session.execute(subq_hist).fetchall()
|
||||
|
||||
while len(result) > 0:
|
||||
db.session.execute(update)
|
||||
print('Committed {} updates at {}'.format(len(result), datetime.utcnow()))
|
||||
db.session.commit()
|
||||
result = db.session.execute(subq_hist).fetchall()
|
||||
|
||||
|
||||
@notify_command(name='insert-inbound-numbers')
|
||||
@click.option('-f', '--file_name', required=True,
|
||||
help="""Full path of the file to upload, file is a contains inbound numbers,
|
||||
|
||||
+21
-9
@@ -89,11 +89,11 @@ class Config(object):
|
||||
|
||||
# secrets that internal apps, such as the admin app or document download, must use to authenticate with the API
|
||||
ADMIN_CLIENT_ID = 'notify-admin'
|
||||
GOVUK_ALERTS_CLIENT_ID = 'govuk-alerts' # TODO: can remove?
|
||||
GOVUK_ALERTS_CLIENT_ID = 'govuk-alerts' # TODO: can remove?
|
||||
|
||||
INTERNAL_CLIENT_API_KEYS = json.loads(
|
||||
os.environ.get('INTERNAL_CLIENT_API_KEYS', '{"notify-admin":["dev-notify-secret-key"]}')
|
||||
) # TODO: handled by varsfile?
|
||||
) # TODO: handled by varsfile?
|
||||
|
||||
# encyption secret/salt
|
||||
ADMIN_CLIENT_SECRET = os.environ.get('ADMIN_CLIENT_SECRET')
|
||||
@@ -165,7 +165,7 @@ class Config(object):
|
||||
MAX_VERIFY_CODE_COUNT = 5
|
||||
MAX_FAILED_LOGIN_COUNT = 10
|
||||
|
||||
SES_STUB_URL = None # TODO: set to a URL in env and remove this to use a stubbed SES service
|
||||
SES_STUB_URL = None # TODO: set to a URL in env and remove this to use a stubbed SES service
|
||||
|
||||
# be careful increasing this size without being sure that we won't see slowness in pysftp
|
||||
MAX_LETTER_PDF_ZIP_FILESIZE = 40 * 1024 * 1024 # 40mb
|
||||
@@ -186,7 +186,7 @@ class Config(object):
|
||||
SMS_CODE_TEMPLATE_ID = '36fb0730-6259-4da1-8a80-c8de22ad4246'
|
||||
EMAIL_2FA_TEMPLATE_ID = '299726d2-dba6-42b8-8209-30e1d66ea164'
|
||||
NEW_USER_EMAIL_VERIFICATION_TEMPLATE_ID = 'ece42649-22a8-4d06-b87f-d52d5d3f0a27'
|
||||
PASSWORD_RESET_TEMPLATE_ID = '474e9242-823b-4f99-813d-ed392e7f1201' # nosec B105 - this is not a password
|
||||
PASSWORD_RESET_TEMPLATE_ID = '474e9242-823b-4f99-813d-ed392e7f1201' # nosec B105 - this is not a password
|
||||
ALREADY_REGISTERED_EMAIL_TEMPLATE_ID = '0880fbb1-a0c6-46f0-9a8e-36c986381ceb'
|
||||
CHANGE_EMAIL_CONFIRMATION_TEMPLATE_ID = 'eb4d9930-87ab-4aef-9bce-786762687884'
|
||||
SERVICE_NOW_LIVE_TEMPLATE_ID = '618185c6-3636-49cd-b7d2-6f6f5eb3bdde'
|
||||
@@ -437,7 +437,7 @@ class Development(Config):
|
||||
# Config.GOVUK_ALERTS_CLIENT_ID: ['govuk-alerts-secret-key']
|
||||
# }
|
||||
|
||||
SECRET_KEY = 'dev-notify-secret-key' # nosec B105 - this is only used in development
|
||||
SECRET_KEY = 'dev-notify-secret-key' # nosec B105 - this is only used in development
|
||||
DANGEROUS_SALT = 'dev-notify-salt'
|
||||
|
||||
MMG_INBOUND_SMS_AUTH = ['testkey']
|
||||
@@ -448,7 +448,10 @@ class Development(Config):
|
||||
|
||||
NOTIFY_EMAIL_DOMAIN = os.getenv('NOTIFY_EMAIL_DOMAIN', 'notify.sandbox.10x.gsa.gov')
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://postgres:chummy@db:5432/notification_api')
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get(
|
||||
'SQLALCHEMY_DATABASE_URI',
|
||||
'postgresql://postgres:chummy@db:5432/notification_api'
|
||||
)
|
||||
|
||||
ANTIVIRUS_ENABLED = os.environ.get('ANTIVIRUS_ENABLED') == '1'
|
||||
|
||||
@@ -486,7 +489,10 @@ class Test(Development):
|
||||
# LETTER_SANITISE_BUCKET_NAME = 'test-letters-sanitise'
|
||||
|
||||
# this is overriden in CI
|
||||
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_TEST_URI', 'postgresql://postgres:chummy@db:5432/test_notification_api')
|
||||
SQLALCHEMY_DATABASE_URI = os.getenv(
|
||||
'SQLALCHEMY_DATABASE_TEST_URI',
|
||||
'postgresql://postgres:chummy@db:5432/test_notification_api'
|
||||
)
|
||||
|
||||
CELERY = {
|
||||
**Config.CELERY,
|
||||
@@ -546,11 +552,17 @@ class Staging(Config):
|
||||
class Live(Config):
|
||||
NOTIFY_ENVIRONMENT = 'live'
|
||||
# buckets
|
||||
CSV_UPLOAD_BUCKET_NAME = os.environ.get('CSV_UPLOAD_BUCKET_NAME', 'notifications-prototype-csv-upload') # created in gsa sandbox
|
||||
CSV_UPLOAD_BUCKET_NAME = os.environ.get(
|
||||
'CSV_UPLOAD_BUCKET_NAME',
|
||||
'notifications-prototype-csv-upload'
|
||||
) # created in gsa sandbox
|
||||
CSV_UPLOAD_ACCESS_KEY = os.environ.get('CSV_UPLOAD_ACCESS_KEY')
|
||||
CSV_UPLOAD_SECRET_KEY = os.environ.get('CSV_UPLOAD_SECRET_KEY')
|
||||
CSV_UPLOAD_REGION = os.environ.get('CSV_UPLOAD_REGION')
|
||||
CONTACT_LIST_BUCKET_NAME = os.environ.get('CONTACT_LIST_BUCKET_NAME', 'notifications-prototype-contact-list-upload') # created in gsa sandbox
|
||||
CONTACT_LIST_BUCKET_NAME = os.environ.get(
|
||||
'CONTACT_LIST_BUCKET_NAME',
|
||||
'notifications-prototype-contact-list-upload'
|
||||
) # created in gsa sandbox
|
||||
CONTACT_LIST_ACCESS_KEY = os.environ.get('CONTACT_LIST_ACCESS_KEY')
|
||||
CONTACT_LIST_SECRET_KEY = os.environ.get('CONTACT_LIST_SECRET_KEY')
|
||||
CONTACT_LIST_REGION = os.environ.get('CONTACT_LIST_REGION')
|
||||
|
||||
@@ -87,6 +87,7 @@ def country_records_delivery(phone_prefix):
|
||||
dlr = INTERNATIONAL_BILLING_RATES[phone_prefix]['attributes']['dlr']
|
||||
return dlr and dlr.lower() == 'yes'
|
||||
|
||||
|
||||
def _decide_permanent_temporary_failure(current_status, status):
|
||||
# If we go from pending to delivered we need to set failure type as temporary-failure
|
||||
if current_status == NOTIFICATION_PENDING and status == NOTIFICATION_PERMANENT_FAILURE:
|
||||
@@ -102,6 +103,7 @@ def _update_notification_status(notification, status, provider_response=None):
|
||||
dao_update_notification(notification)
|
||||
return notification
|
||||
|
||||
|
||||
@autocommit
|
||||
def update_notification_status_by_id(notification_id, status, sent_by=None, detailed_status_code=None):
|
||||
notification = Notification.query.with_for_update().filter(Notification.id == notification_id).first()
|
||||
|
||||
@@ -175,10 +175,12 @@ provider_cache = TTLCache(maxsize=8, ttl=10)
|
||||
|
||||
@cached(cache=provider_cache)
|
||||
def provider_to_use(notification_type, international=True):
|
||||
international = False # TODO: remove or resolve the functionality of this flag
|
||||
international = False # TODO: remove or resolve the functionality of this flag
|
||||
# TODO rip firetext and mmg out of early migrations and clean up the expression below
|
||||
active_providers = [
|
||||
p for p in get_provider_details_by_notification_type(notification_type, international) if p.active and p.identifier not in ['firetext','mmg']
|
||||
p for p in get_provider_details_by_notification_type(
|
||||
notification_type, international
|
||||
) if p.active and p.identifier not in ['firetext', 'mmg']
|
||||
]
|
||||
|
||||
if not active_providers:
|
||||
@@ -191,7 +193,7 @@ def provider_to_use(notification_type, international=True):
|
||||
chosen_provider = active_providers[0]
|
||||
else:
|
||||
weights = [p.priority for p in active_providers]
|
||||
chosen_provider = random.choices(active_providers, weights=weights)[0] # nosec B311 - this is not security/cryptography related
|
||||
chosen_provider = random.choices(active_providers, weights=weights)[0] # nosec B311 - not sec/crypto related
|
||||
|
||||
return notification_provider_clients.get_client_by_name_and_type(chosen_provider.identifier, notification_type)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from notifications_utils.recipients import try_validate_and_format_phone_number
|
||||
# from notifications_utils.recipients import try_validate_and_format_phone_number
|
||||
|
||||
from app.dao.inbound_sms_dao import (
|
||||
dao_count_inbound_sms_for_service,
|
||||
|
||||
+5
-3
@@ -122,7 +122,9 @@ class User(db.Model):
|
||||
state = db.Column(db.String, nullable=False, default='pending')
|
||||
platform_admin = db.Column(db.Boolean, nullable=False, default=False)
|
||||
current_session_id = db.Column(UUID(as_uuid=True), nullable=True)
|
||||
auth_type = db.Column(db.String, db.ForeignKey('auth_type.name'), index=True, nullable=False, default=EMAIL_AUTH_TYPE)
|
||||
auth_type = db.Column(
|
||||
db.String, db.ForeignKey('auth_type.name'), index=True, nullable=False, default=EMAIL_AUTH_TYPE
|
||||
)
|
||||
email_access_validated_at = db.Column(
|
||||
db.DateTime, index=False, unique=False, nullable=False, default=datetime.datetime.utcnow
|
||||
)
|
||||
@@ -608,7 +610,7 @@ class AnnualBilling(db.Model):
|
||||
"name": self.service.name
|
||||
}
|
||||
|
||||
return{
|
||||
return {
|
||||
"id": str(self.id),
|
||||
'free_sms_fragment_limit': self.free_sms_fragment_limit,
|
||||
'service_id': self.service_id,
|
||||
@@ -1645,7 +1647,7 @@ class Notification(db.Model):
|
||||
"""
|
||||
# this should only ever be called for letter notifications - it makes no sense otherwise and I'd rather not
|
||||
# get the two code flows mixed up at all
|
||||
assert self.notification_type == LETTER_TYPE # nosec B101 - current calling code already validates the correct type
|
||||
assert self.notification_type == LETTER_TYPE # nosec B101 - current calling code validates correct type
|
||||
|
||||
if self.status in [NOTIFICATION_CREATED, NOTIFICATION_SENDING]:
|
||||
return NOTIFICATION_STATUS_LETTER_ACCEPTED
|
||||
|
||||
@@ -10,6 +10,7 @@ from app.notifications.sns_handlers import sns_notification_handler
|
||||
ses_callback_blueprint = Blueprint('notifications_ses_callback', __name__)
|
||||
DEFAULT_MAX_AGE = timedelta(days=10000)
|
||||
|
||||
|
||||
# 400 counts as a permanent failure so SNS will not retry.
|
||||
# 500 counts as a failed delivery attempt so SNS will retry.
|
||||
# See https://docs.aws.amazon.com/sns/latest/dg/DeliveryPolicies.html#DeliveryPolicies
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from flask import Blueprint, json, jsonify, request
|
||||
from flask import Blueprint # , json, jsonify, request
|
||||
|
||||
# from app.celery.process_sms_client_response_tasks import (
|
||||
# process_sms_client_response,
|
||||
# )
|
||||
from app.config import QueueNames
|
||||
from app.errors import InvalidRequest, register_errors
|
||||
# from app.config import QueueNames
|
||||
from app.errors import register_errors
|
||||
|
||||
sms_callback_blueprint = Blueprint("sms_callback", __name__, url_prefix="/notifications/sms")
|
||||
register_errors(sms_callback_blueprint)
|
||||
|
||||
@@ -24,6 +24,7 @@ INBOUND_SMS_COUNTER = Counter(
|
||||
['provider']
|
||||
)
|
||||
|
||||
|
||||
@receive_notifications_blueprint.route('/notifications/sms/receive/sns', methods=['POST'])
|
||||
def receive_sns_sms():
|
||||
"""
|
||||
@@ -53,13 +54,16 @@ def receive_sns_sms():
|
||||
# TODO wrap this up
|
||||
if "inboundMessageId" in message:
|
||||
# TODO use standard formatting we use for all US numbers
|
||||
inbound_number = message['destinationNumber'].replace('+','')
|
||||
inbound_number = message['destinationNumber'].replace('+', '')
|
||||
|
||||
service = fetch_potential_service(inbound_number, 'sns')
|
||||
if not service:
|
||||
# since this is an issue with our service <-> number mapping, or no inbound_sms service permission
|
||||
# we should still tell SNS that we received it successfully
|
||||
current_app.logger.warning(f"Mapping between service and inbound number: {inbound_number} is broken, or service does not have permission to receive inbound sms")
|
||||
current_app.logger.warning(
|
||||
f"Mapping between service and inbound number: {inbound_number} is broken, \
|
||||
or service does not have permission to receive inbound sms"
|
||||
)
|
||||
return jsonify(
|
||||
result="success", message="SMS-SNS callback succeeded"
|
||||
), 200
|
||||
@@ -79,7 +83,6 @@ def receive_sns_sms():
|
||||
date_received=date_received,
|
||||
provider_name=provider_name)
|
||||
|
||||
# TODO ensure inbound sms callback endpoints are accessible and functioning for notify api users, then uncomment the task below
|
||||
tasks.send_inbound_sms_to_service.apply_async([str(inbound.id), str(service.id)], queue=QueueNames.NOTIFY)
|
||||
|
||||
current_app.logger.debug(
|
||||
|
||||
@@ -19,6 +19,7 @@ _cert_url_re = re.compile(
|
||||
r'sns\.([a-z]{1,3}-[a-z]+-[0-9]{1,2})\.amazonaws\.com',
|
||||
)
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
"""
|
||||
ValidationError. Raised when a message fails integrity checks.
|
||||
@@ -56,7 +57,7 @@ def get_string_to_sign(sns_payload):
|
||||
for field in fields:
|
||||
field_value = sns_payload.get(field)
|
||||
if not isinstance(field_value, str):
|
||||
if field == 'Subject' and field_value == None:
|
||||
if field == 'Subject' and field_value is None:
|
||||
continue
|
||||
raise ValidationError(f"In {field}, found non-string value: {field_value}")
|
||||
string_to_sign += field + '\n' + field_value + '\n'
|
||||
@@ -83,7 +84,8 @@ def validate_sns_cert(sns_payload):
|
||||
string_to_sign = get_string_to_sign(sns_payload)
|
||||
|
||||
# Key signing cert url via Lambda and via webhook are slightly different
|
||||
signing_cert_url = sns_payload.get('SigningCertUrl') if 'SigningCertUrl' in sns_payload else sns_payload.get('SigningCertURL')
|
||||
signing_cert_url = sns_payload.get('SigningCertUrl') if 'SigningCertUrl' in \
|
||||
sns_payload else sns_payload.get('SigningCertURL')
|
||||
if not isinstance(signing_cert_url, str):
|
||||
raise ValidationError("Signing cert url must be a string")
|
||||
cert_scheme, cert_netloc, *_ = urlparse(signing_cert_url)
|
||||
|
||||
@@ -45,7 +45,9 @@ def sns_notification_handler(data, headers):
|
||||
try:
|
||||
validate_sns_cert(message)
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"SES-SNS callback failed: validation failed with error: Signature validation failed with error {e}")
|
||||
current_app.logger.error(
|
||||
f"SES-SNS callback failed: validation failed with error: Signature validation failed with error {e}"
|
||||
)
|
||||
raise InvalidRequest("SES-SNS callback failed: validation failed", 400)
|
||||
|
||||
if message.get('Type') == 'SubscriptionConfirmation':
|
||||
@@ -55,8 +57,14 @@ def sns_notification_handler(data, headers):
|
||||
try:
|
||||
response.raise_for_status()
|
||||
except Exception as e:
|
||||
current_app.logger.warning(f"Attempt to raise_for_status()SubscriptionConfirmation Type message files for response: {response.text} with error {e}")
|
||||
raise InvalidRequest("SES-SNS callback failed: attempt to raise_for_status()SubscriptionConfirmation Type message failed", 400)
|
||||
current_app.logger.warning(
|
||||
f"Attempt to raise_for_status()SubscriptionConfirmation Type \
|
||||
message files for response: {response.text} with error {e}"
|
||||
)
|
||||
raise InvalidRequest(
|
||||
"SES-SNS callback failed: attempt to raise_for_status()SubscriptionConfirmation \
|
||||
Type message failed", 400
|
||||
)
|
||||
current_app.logger.info("SES-SNS auto-confirm subscription callback succeeded")
|
||||
return message
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ def test_notifications_ses_200_autoconfirms_subscription(client, mocker):
|
||||
def test_notifications_ses_200_call_process_task(client, mocker):
|
||||
process_mock = mocker.patch("app.notifications.notifications_ses_callback.process_ses_results.apply_async")
|
||||
mocker.patch("app.notifications.sns_handlers.validate_sns_cert", return_value=True)
|
||||
data = {"Type": "Notification", "foo": "bar", "Message": {"mail": "baz"} }
|
||||
data = {"Type": "Notification", "foo": "bar", "Message": {"mail": "baz"}}
|
||||
mocker.patch("app.notifications.sns_handlers.sns_notification_handler", return_value=data)
|
||||
json_data = json.dumps(data)
|
||||
response = client.post(
|
||||
@@ -156,7 +156,10 @@ def test_ses_callback_should_update_notification_status(
|
||||
status='sending',
|
||||
sent_at=datetime.utcnow()
|
||||
)
|
||||
callback_api = create_service_callback_api(service=sample_email_template.service, url="https://original_url.com")
|
||||
callback_api = create_service_callback_api(
|
||||
service=sample_email_template.service,
|
||||
url="https://original_url.com"
|
||||
)
|
||||
assert get_notification_by_id(notification.id).status == 'sending'
|
||||
assert process_ses_results(ses_notification_callback(reference='ref'))
|
||||
assert get_notification_by_id(notification.id).status == 'delivered'
|
||||
@@ -186,13 +189,19 @@ def test_ses_callback_should_retry_if_notification_is_new(mocker):
|
||||
assert process_ses_results(ses_notification_callback(reference='ref')) is None
|
||||
assert mock_logger.call_count == 0
|
||||
assert mock_retry.call_count == 1
|
||||
|
||||
|
||||
def test_ses_callback_should_log_if_notification_is_missing(client, _notify_db, mocker):
|
||||
mock_retry = mocker.patch('app.celery.process_ses_receipts_tasks.process_ses_results.retry')
|
||||
mock_logger = mocker.patch('app.celery.process_ses_receipts_tasks.current_app.logger.warning')
|
||||
with freeze_time('2017-11-17T12:34:03.646Z'):
|
||||
assert process_ses_results(ses_notification_callback(reference='ref')) is None
|
||||
assert mock_retry.call_count == 0
|
||||
mock_logger.assert_called_once_with('notification not found for reference: ref (while attempting update to delivered)')
|
||||
mock_logger.assert_called_once_with(
|
||||
'notification not found for reference: ref (while attempting update to delivered)'
|
||||
)
|
||||
|
||||
|
||||
def test_ses_callback_should_not_retry_if_notification_is_old(mocker):
|
||||
mock_retry = mocker.patch('app.celery.process_ses_receipts_tasks.process_ses_results.retry')
|
||||
mock_logger = mocker.patch('app.celery.process_ses_receipts_tasks.current_app.logger.error')
|
||||
@@ -200,6 +209,8 @@ def test_ses_callback_should_not_retry_if_notification_is_old(mocker):
|
||||
assert process_ses_results(ses_notification_callback(reference='ref')) is None
|
||||
assert mock_logger.call_count == 0
|
||||
assert mock_retry.call_count == 0
|
||||
|
||||
|
||||
def test_ses_callback_does_not_call_send_delivery_status_if_no_db_entry(
|
||||
client,
|
||||
_notify_db,
|
||||
@@ -222,6 +233,8 @@ def test_ses_callback_does_not_call_send_delivery_status_if_no_db_entry(
|
||||
assert process_ses_results(ses_notification_callback(reference='ref'))
|
||||
assert get_notification_by_id(notification.id).status == 'delivered'
|
||||
send_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_ses_callback_should_update_multiple_notification_status_sent(
|
||||
client,
|
||||
_notify_db,
|
||||
@@ -257,6 +270,8 @@ def test_ses_callback_should_update_multiple_notification_status_sent(
|
||||
assert process_ses_results(ses_notification_callback(reference='ref2'))
|
||||
assert process_ses_results(ses_notification_callback(reference='ref3'))
|
||||
assert send_mock.called
|
||||
|
||||
|
||||
def test_ses_callback_should_set_status_to_temporary_failure(client,
|
||||
_notify_db,
|
||||
notify_db_session,
|
||||
@@ -278,6 +293,8 @@ def test_ses_callback_should_set_status_to_temporary_failure(client,
|
||||
assert process_ses_results(ses_soft_bounce_callback(reference='ref'))
|
||||
assert get_notification_by_id(notification.id).status == 'temporary-failure'
|
||||
assert send_mock.called
|
||||
|
||||
|
||||
def test_ses_callback_should_set_status_to_permanent_failure(client,
|
||||
_notify_db,
|
||||
notify_db_session,
|
||||
@@ -299,6 +316,8 @@ def test_ses_callback_should_set_status_to_permanent_failure(client,
|
||||
assert process_ses_results(ses_hard_bounce_callback(reference='ref'))
|
||||
assert get_notification_by_id(notification.id).status == 'permanent-failure'
|
||||
assert send_mock.called
|
||||
|
||||
|
||||
def test_ses_callback_should_send_on_complaint_to_user_callback_api(sample_email_template, mocker):
|
||||
send_mock = mocker.patch(
|
||||
'app.celery.service_callback_tasks.send_complaint_to_service.apply_async'
|
||||
@@ -321,4 +340,3 @@ def test_ses_callback_should_send_on_complaint_to_user_callback_api(sample_email
|
||||
'service_callback_api_url': 'https://original_url.com',
|
||||
'to': 'recipient1@example.com'
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import pytest
|
||||
from freezegun import freeze_time
|
||||
|
||||
from app import statsd_client
|
||||
# from app.celery.process_sms_client_response_tasks import (
|
||||
# process_sms_client_response,
|
||||
# )
|
||||
from app.celery.process_sms_client_response_tasks import (
|
||||
process_sms_client_response,
|
||||
)
|
||||
from app.clients import ClientException
|
||||
from app.models import NOTIFICATION_TECHNICAL_FAILURE
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ def test_should_add_to_retry_queue_if_notification_not_found_in_deliver_email_ta
|
||||
app.delivery.send_to_providers.send_email_to_provider.assert_not_called()
|
||||
app.celery.provider_tasks.deliver_email.retry.assert_called_with(queue="retry-tasks")
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Failing for unknown reason")
|
||||
@pytest.mark.parametrize(
|
||||
'exception_class', [
|
||||
|
||||
@@ -290,6 +290,7 @@ def test_create_nightly_billing_for_day_different_sent_by(
|
||||
assert record.billable_units == 1
|
||||
assert record.rate_multiplier == 1.0
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Remove mail")
|
||||
def test_create_nightly_billing_for_day_different_letter_postage(
|
||||
notify_db_session,
|
||||
|
||||
@@ -15,6 +15,7 @@ def fake_client(notify_api):
|
||||
fake_client.init_app(notify_api, statsd_client)
|
||||
return fake_client
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: New SMS client")
|
||||
def test_send_sms(fake_client, mocker):
|
||||
mock_send = mocker.patch.object(fake_client, 'try_send_sms')
|
||||
@@ -31,6 +32,7 @@ def test_send_sms(fake_client, mocker):
|
||||
'to', 'content', 'reference', False, 'testing'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: New SMS client")
|
||||
def test_send_sms_error(fake_client, mocker):
|
||||
mocker.patch.object(
|
||||
|
||||
@@ -144,6 +144,7 @@ def test_adjust_provider_priority_sets_priority(
|
||||
assert mmg_provider.created_by.id == notify_user.id
|
||||
assert mmg_provider.priority == 50
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
||||
@freeze_time('2016-01-01 00:30')
|
||||
def test_adjust_provider_priority_adds_history(
|
||||
@@ -172,6 +173,7 @@ def test_adjust_provider_priority_adds_history(
|
||||
assert updated_provider_history_rows[0].version - old_provider_history_rows[0].version == 1
|
||||
assert updated_provider_history_rows[0].priority == 50
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
||||
@freeze_time('2016-01-01 01:00')
|
||||
def test_get_sms_providers_for_update_returns_providers(restore_provider_details):
|
||||
|
||||
@@ -43,6 +43,7 @@ def setup_function(_function):
|
||||
# state of the cache is not shared between tests.
|
||||
send_to_providers.provider_cache.clear()
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Update with new providers")
|
||||
def test_provider_to_use_should_return_random_provider(mocker, notify_db_session):
|
||||
mmg = get_provider_details_by_identifier('mmg')
|
||||
@@ -72,6 +73,7 @@ def test_provider_to_use_should_cache_repeated_calls(mocker, notify_db_session):
|
||||
assert all(result == results[0] for result in results)
|
||||
assert len(mock_choices.call_args_list) == 1
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Update with new providers")
|
||||
@pytest.mark.parametrize('international_provider_priority', (
|
||||
# Since there’s only one international provider it should always
|
||||
@@ -592,6 +594,7 @@ def test_should_not_update_notification_if_research_mode_on_exception(
|
||||
assert persisted_notification.billable_units == 0
|
||||
assert update_mock.called
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Update with new providers")
|
||||
@pytest.mark.parametrize("starting_status, expected_status", [
|
||||
("delivered", "delivered"),
|
||||
|
||||
@@ -31,8 +31,8 @@ from tests.app.db import create_notification
|
||||
|
||||
FROZEN_DATE_TIME = "2018-03-14 17:00:00"
|
||||
|
||||
pytest.skip(reason="Skipping letter-related functionality for now", allow_module_level=True)
|
||||
|
||||
@pytest.skip(reason="Skipping letter-related functionality for now", allow_module_level=True)
|
||||
@pytest.fixture(name='sample_precompiled_letter_notification')
|
||||
def _sample_precompiled_letter_notification(sample_letter_notification):
|
||||
sample_letter_notification.template.hidden = True
|
||||
|
||||
@@ -17,6 +17,7 @@ def mmg_post(client, data):
|
||||
data=data,
|
||||
headers=[('Content-Type', 'application/json')])
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
||||
def test_firetext_callback_should_not_need_auth(client, mocker):
|
||||
mocker.patch('app.notifications.notifications_sms_callback.process_sms_client_response')
|
||||
@@ -36,6 +37,7 @@ def test_firetext_callback_should_return_400_if_empty_reference(client, mocker):
|
||||
assert json_resp['result'] == 'error'
|
||||
assert json_resp['message'] == ['Firetext callback failed: reference missing']
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
||||
def test_firetext_callback_should_return_400_if_no_reference(client, mocker):
|
||||
data = 'mobile=441234123123&status=0&time=2016-03-10 14:17:00'
|
||||
@@ -45,6 +47,7 @@ def test_firetext_callback_should_return_400_if_no_reference(client, mocker):
|
||||
assert json_resp['result'] == 'error'
|
||||
assert json_resp['message'] == ['Firetext callback failed: reference missing']
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
||||
def test_firetext_callback_should_return_400_if_no_status(client, mocker):
|
||||
data = 'mobile=441234123123&time=2016-03-10 14:17:00&reference=notification_id'
|
||||
@@ -54,6 +57,7 @@ def test_firetext_callback_should_return_400_if_no_status(client, mocker):
|
||||
assert json_resp['result'] == 'error'
|
||||
assert json_resp['message'] == ['Firetext callback failed: status missing']
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
||||
def test_firetext_callback_should_return_200_and_call_task_with_valid_data(client, mocker):
|
||||
mock_celery = mocker.patch(
|
||||
@@ -70,6 +74,7 @@ def test_firetext_callback_should_return_200_and_call_task_with_valid_data(clien
|
||||
queue='sms-callbacks',
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
||||
def test_firetext_callback_including_a_code_should_return_200_and_call_task_with_valid_data(client, mocker):
|
||||
mock_celery = mocker.patch(
|
||||
@@ -86,6 +91,7 @@ def test_firetext_callback_including_a_code_should_return_200_and_call_task_with
|
||||
queue='sms-callbacks',
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
||||
def test_mmg_callback_should_not_need_auth(client, mocker, sample_notification):
|
||||
mocker.patch('app.notifications.notifications_sms_callback.process_sms_client_response')
|
||||
@@ -98,6 +104,7 @@ def test_mmg_callback_should_not_need_auth(client, mocker, sample_notification):
|
||||
response = mmg_post(client, data)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
||||
def test_process_mmg_response_returns_400_for_malformed_data(client):
|
||||
data = json.dumps({"reference": "mmg_reference",
|
||||
@@ -114,6 +121,7 @@ def test_process_mmg_response_returns_400_for_malformed_data(client):
|
||||
assert "{} callback failed: {} missing".format('MMG', 'status') in json_data['message']
|
||||
assert "{} callback failed: {} missing".format('MMG', 'CID') in json_data['message']
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
||||
def test_mmg_callback_should_return_200_and_call_task_with_valid_data(client, mocker):
|
||||
mock_celery = mocker.patch(
|
||||
|
||||
@@ -238,6 +238,7 @@ def test_should_cache_template_lookups_in_memory(mocker, client, sample_template
|
||||
]
|
||||
assert Notification.query.count() == 5
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: cloud.gov redis fails, local docker works, mock redis fails")
|
||||
def test_should_cache_template_and_service_in_redis(mocker, client, sample_template):
|
||||
|
||||
@@ -288,6 +289,7 @@ def test_should_cache_template_and_service_in_redis(mocker, client, sample_templ
|
||||
assert json.loads(templates_call[0][1]) == {'data': template_dict}
|
||||
assert templates_call[1]['ex'] == 604_800
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Needs updating for TTS: cloud.gov redis fails, local docker works, mock redis fails")
|
||||
def test_should_return_template_if_found_in_redis(mocker, client, sample_template):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user