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
|
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 = Histogram(
|
||||||
'auth_db_connection_duration_seconds',
|
'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_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
|
||||||
default_region = os.environ.get('AWS_REGION')
|
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)
|
s3_file = get_s3_object(bucket_name, file_location, access_key, secret_key, region)
|
||||||
return s3_file.get()['Body'].read().decode('utf-8')
|
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)
|
session = Session(aws_access_key_id=access_key, aws_secret_access_key=secret_key, region_name=region)
|
||||||
s3 = session.resource('s3')
|
s3 = session.resource('s3')
|
||||||
return s3.Object(bucket_name, file_location)
|
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:
|
||||||
# try and access metadata of object
|
# try and access metadata of object
|
||||||
get_s3_object(bucket_name, file_location, access_key, secret_key, region).metadata
|
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"])
|
ses_message = json.loads(response["Message"])
|
||||||
notification_type = ses_message["notificationType"]
|
notification_type = ses_message["notificationType"]
|
||||||
# TODO remove after smoke testing on prod is implemented
|
# 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
|
bounce_message = None
|
||||||
|
|
||||||
if notification_type == 'Bounce':
|
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)
|
message_time = iso8601.parse_date(ses_message["mail"]["timestamp"]).replace(tzinfo=None)
|
||||||
if datetime.utcnow() - message_time < timedelta(minutes=5):
|
if datetime.utcnow() - message_time < timedelta(minutes=5):
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
f"notification not found for reference: {reference} (while attempting update to {notification_status}). "
|
f"notification not found for reference: {reference} \
|
||||||
f"Callback may have arrived before notification was persisted to the DB. Adding task to retry queue"
|
(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)
|
self.retry(queue=QueueNames.RETRY)
|
||||||
else:
|
else:
|
||||||
current_app.logger.warning(
|
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
|
return
|
||||||
|
|
||||||
@@ -102,6 +108,7 @@ def process_ses_results(self, response):
|
|||||||
current_app.logger.exception("Error processing SES results: {}".format(type(e)))
|
current_app.logger.exception("Error processing SES results: {}".format(type(e)))
|
||||||
self.retry(queue=QueueNames.RETRY)
|
self.retry(queue=QueueNames.RETRY)
|
||||||
|
|
||||||
|
|
||||||
def determine_notification_bounce_type(ses_message):
|
def determine_notification_bounce_type(ses_message):
|
||||||
notification_type = ses_message["notificationType"]
|
notification_type = ses_message["notificationType"]
|
||||||
if notification_type in ["Delivery", "Complaint"]:
|
if notification_type in ["Delivery", "Complaint"]:
|
||||||
@@ -116,14 +123,16 @@ def determine_notification_bounce_type(ses_message):
|
|||||||
return "Permanent"
|
return "Permanent"
|
||||||
return "Temporary"
|
return "Temporary"
|
||||||
|
|
||||||
|
|
||||||
def determine_notification_type(ses_message):
|
def determine_notification_type(ses_message):
|
||||||
notification_type = ses_message["notificationType"]
|
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}")
|
raise KeyError(f"Unhandled sns notification type {notification_type}")
|
||||||
if notification_type == 'Bounce':
|
if notification_type == 'Bounce':
|
||||||
return determine_notification_bounce_type(ses_message)
|
return determine_notification_bounce_type(ses_message)
|
||||||
return notification_type
|
return notification_type
|
||||||
|
|
||||||
|
|
||||||
def _determine_provider_response(ses_message):
|
def _determine_provider_response(ses_message):
|
||||||
if ses_message["notificationType"] != "Bounce":
|
if ses_message["notificationType"] != "Bounce":
|
||||||
return None
|
return None
|
||||||
@@ -175,7 +184,9 @@ def get_aws_responses(ses_message):
|
|||||||
|
|
||||||
def handle_complaint(ses_message):
|
def handle_complaint(ses_message):
|
||||||
recipient_email = remove_emails_from_complaint(ses_message)[0]
|
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:
|
try:
|
||||||
reference = ses_message["mail"]["messageId"]
|
reference = ses_message["mail"]["messageId"]
|
||||||
except KeyError as e:
|
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)
|
service_callback_api = get_service_delivery_status_callback_api_for_service(service_id=notification.service_id)
|
||||||
if service_callback_api:
|
if service_callback_api:
|
||||||
notification_data = create_delivery_status_callback_data(notification, 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):
|
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:
|
if service_callback_api:
|
||||||
complaint_data = create_complaint_callback_data(complaint, notification, service_callback_api, recipient)
|
complaint_data = create_complaint_callback_data(complaint, notification, service_callback_api, recipient)
|
||||||
send_complaint_to_service.apply_async([complaint_data], queue=QueueNames.CALLBACKS)
|
send_complaint_to_service.apply_async([complaint_data], queue=QueueNames.CALLBACKS)
|
||||||
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import pytest
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from notifications_utils.template import SMSMessageTemplate
|
from notifications_utils.template import SMSMessageTemplate
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ def create_delivery_status_callback_data(notification, service_callback_api):
|
|||||||
"notification_client_reference": notification.client_reference,
|
"notification_client_reference": notification.client_reference,
|
||||||
"notification_to": notification.to,
|
"notification_to": notification.to,
|
||||||
"notification_status": notification.status,
|
"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_created_at": notification.created_at.strftime(DATETIME_FORMAT),
|
||||||
"notification_updated_at":
|
"notification_updated_at":
|
||||||
notification.updated_at.strftime(DATETIME_FORMAT) if notification.updated_at else None,
|
notification.updated_at.strftime(DATETIME_FORMAT) if notification.updated_at else None,
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
from celery import current_app
|
|
||||||
|
|
||||||
|
|
||||||
class ClientException(Exception):
|
class ClientException(Exception):
|
||||||
'''
|
'''
|
||||||
Base Exceptions for sending notifications that fail
|
Base Exceptions for sending notifications that fail
|
||||||
|
|||||||
@@ -8,16 +8,20 @@ def find_by_service_name(services, service_name):
|
|||||||
return services[i]
|
return services[i]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def extract_cloudfoundry_config():
|
def extract_cloudfoundry_config():
|
||||||
vcap_services = json.loads(os.environ['VCAP_SERVICES'])
|
vcap_services = json.loads(os.environ['VCAP_SERVICES'])
|
||||||
|
|
||||||
# Postgres config
|
# 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
|
# 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
|
# 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:
|
if bucket_service:
|
||||||
os.environ['CSV_UPLOAD_BUCKET_NAME'] = bucket_service['credentials']['bucket']
|
os.environ['CSV_UPLOAD_BUCKET_NAME'] = bucket_service['credentials']['bucket']
|
||||||
os.environ['CSV_UPLOAD_ACCESS_KEY'] = bucket_service['credentials']['access_key_id']
|
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']
|
os.environ['CSV_UPLOAD_REGION'] = bucket_service['credentials']['region']
|
||||||
|
|
||||||
# Contact List Bucket Name
|
# 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:
|
if bucket_service:
|
||||||
os.environ['CONTACT_LIST_BUCKET_NAME'] = bucket_service['credentials']['bucket']
|
os.environ['CONTACT_LIST_BUCKET_NAME'] = bucket_service['credentials']['bucket']
|
||||||
os.environ['CONTACT_LIST_ACCESS_KEY'] = bucket_service['credentials']['access_key_id']
|
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)
|
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')
|
@notify_command(name='insert-inbound-numbers')
|
||||||
@click.option('-f', '--file_name', required=True,
|
@click.option('-f', '--file_name', required=True,
|
||||||
help="""Full path of the file to upload, file is a contains inbound numbers,
|
help="""Full path of the file to upload, file is a contains inbound numbers,
|
||||||
|
|||||||
+16
-4
@@ -448,7 +448,10 @@ class Development(Config):
|
|||||||
|
|
||||||
NOTIFY_EMAIL_DOMAIN = os.getenv('NOTIFY_EMAIL_DOMAIN', 'notify.sandbox.10x.gsa.gov')
|
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'
|
ANTIVIRUS_ENABLED = os.environ.get('ANTIVIRUS_ENABLED') == '1'
|
||||||
|
|
||||||
@@ -486,7 +489,10 @@ class Test(Development):
|
|||||||
# LETTER_SANITISE_BUCKET_NAME = 'test-letters-sanitise'
|
# LETTER_SANITISE_BUCKET_NAME = 'test-letters-sanitise'
|
||||||
|
|
||||||
# this is overriden in CI
|
# 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 = {
|
CELERY = {
|
||||||
**Config.CELERY,
|
**Config.CELERY,
|
||||||
@@ -546,11 +552,17 @@ class Staging(Config):
|
|||||||
class Live(Config):
|
class Live(Config):
|
||||||
NOTIFY_ENVIRONMENT = 'live'
|
NOTIFY_ENVIRONMENT = 'live'
|
||||||
# buckets
|
# 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_ACCESS_KEY = os.environ.get('CSV_UPLOAD_ACCESS_KEY')
|
||||||
CSV_UPLOAD_SECRET_KEY = os.environ.get('CSV_UPLOAD_SECRET_KEY')
|
CSV_UPLOAD_SECRET_KEY = os.environ.get('CSV_UPLOAD_SECRET_KEY')
|
||||||
CSV_UPLOAD_REGION = os.environ.get('CSV_UPLOAD_REGION')
|
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_ACCESS_KEY = os.environ.get('CONTACT_LIST_ACCESS_KEY')
|
||||||
CONTACT_LIST_SECRET_KEY = os.environ.get('CONTACT_LIST_SECRET_KEY')
|
CONTACT_LIST_SECRET_KEY = os.environ.get('CONTACT_LIST_SECRET_KEY')
|
||||||
CONTACT_LIST_REGION = os.environ.get('CONTACT_LIST_REGION')
|
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']
|
dlr = INTERNATIONAL_BILLING_RATES[phone_prefix]['attributes']['dlr']
|
||||||
return dlr and dlr.lower() == 'yes'
|
return dlr and dlr.lower() == 'yes'
|
||||||
|
|
||||||
|
|
||||||
def _decide_permanent_temporary_failure(current_status, status):
|
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 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:
|
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)
|
dao_update_notification(notification)
|
||||||
return notification
|
return notification
|
||||||
|
|
||||||
|
|
||||||
@autocommit
|
@autocommit
|
||||||
def update_notification_status_by_id(notification_id, status, sent_by=None, detailed_status_code=None):
|
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()
|
notification = Notification.query.with_for_update().filter(Notification.id == notification_id).first()
|
||||||
|
|||||||
@@ -178,7 +178,9 @@ 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
|
# TODO rip firetext and mmg out of early migrations and clean up the expression below
|
||||||
active_providers = [
|
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:
|
if not active_providers:
|
||||||
@@ -191,7 +193,7 @@ def provider_to_use(notification_type, international=True):
|
|||||||
chosen_provider = active_providers[0]
|
chosen_provider = active_providers[0]
|
||||||
else:
|
else:
|
||||||
weights = [p.priority for p in active_providers]
|
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)
|
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 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 (
|
from app.dao.inbound_sms_dao import (
|
||||||
dao_count_inbound_sms_for_service,
|
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')
|
state = db.Column(db.String, nullable=False, default='pending')
|
||||||
platform_admin = db.Column(db.Boolean, nullable=False, default=False)
|
platform_admin = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
current_session_id = db.Column(UUID(as_uuid=True), nullable=True)
|
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(
|
email_access_validated_at = db.Column(
|
||||||
db.DateTime, index=False, unique=False, nullable=False, default=datetime.datetime.utcnow
|
db.DateTime, index=False, unique=False, nullable=False, default=datetime.datetime.utcnow
|
||||||
)
|
)
|
||||||
@@ -608,7 +610,7 @@ class AnnualBilling(db.Model):
|
|||||||
"name": self.service.name
|
"name": self.service.name
|
||||||
}
|
}
|
||||||
|
|
||||||
return{
|
return {
|
||||||
"id": str(self.id),
|
"id": str(self.id),
|
||||||
'free_sms_fragment_limit': self.free_sms_fragment_limit,
|
'free_sms_fragment_limit': self.free_sms_fragment_limit,
|
||||||
'service_id': self.service_id,
|
'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
|
# 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
|
# 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]:
|
if self.status in [NOTIFICATION_CREATED, NOTIFICATION_SENDING]:
|
||||||
return NOTIFICATION_STATUS_LETTER_ACCEPTED
|
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__)
|
ses_callback_blueprint = Blueprint('notifications_ses_callback', __name__)
|
||||||
DEFAULT_MAX_AGE = timedelta(days=10000)
|
DEFAULT_MAX_AGE = timedelta(days=10000)
|
||||||
|
|
||||||
|
|
||||||
# 400 counts as a permanent failure so SNS will not retry.
|
# 400 counts as a permanent failure so SNS will not retry.
|
||||||
# 500 counts as a failed delivery attempt so SNS will retry.
|
# 500 counts as a failed delivery attempt so SNS will retry.
|
||||||
# See https://docs.aws.amazon.com/sns/latest/dg/DeliveryPolicies.html#DeliveryPolicies
|
# 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 (
|
# from app.celery.process_sms_client_response_tasks import (
|
||||||
# process_sms_client_response,
|
# process_sms_client_response,
|
||||||
# )
|
# )
|
||||||
from app.config import QueueNames
|
# from app.config import QueueNames
|
||||||
from app.errors import InvalidRequest, register_errors
|
from app.errors import register_errors
|
||||||
|
|
||||||
sms_callback_blueprint = Blueprint("sms_callback", __name__, url_prefix="/notifications/sms")
|
sms_callback_blueprint = Blueprint("sms_callback", __name__, url_prefix="/notifications/sms")
|
||||||
register_errors(sms_callback_blueprint)
|
register_errors(sms_callback_blueprint)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ INBOUND_SMS_COUNTER = Counter(
|
|||||||
['provider']
|
['provider']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receive_notifications_blueprint.route('/notifications/sms/receive/sns', methods=['POST'])
|
@receive_notifications_blueprint.route('/notifications/sms/receive/sns', methods=['POST'])
|
||||||
def receive_sns_sms():
|
def receive_sns_sms():
|
||||||
"""
|
"""
|
||||||
@@ -53,13 +54,16 @@ def receive_sns_sms():
|
|||||||
# TODO wrap this up
|
# TODO wrap this up
|
||||||
if "inboundMessageId" in message:
|
if "inboundMessageId" in message:
|
||||||
# TODO use standard formatting we use for all US numbers
|
# 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')
|
service = fetch_potential_service(inbound_number, 'sns')
|
||||||
if not service:
|
if not service:
|
||||||
# since this is an issue with our service <-> number mapping, or no inbound_sms service permission
|
# 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
|
# 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(
|
return jsonify(
|
||||||
result="success", message="SMS-SNS callback succeeded"
|
result="success", message="SMS-SNS callback succeeded"
|
||||||
), 200
|
), 200
|
||||||
@@ -79,7 +83,6 @@ def receive_sns_sms():
|
|||||||
date_received=date_received,
|
date_received=date_received,
|
||||||
provider_name=provider_name)
|
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)
|
tasks.send_inbound_sms_to_service.apply_async([str(inbound.id), str(service.id)], queue=QueueNames.NOTIFY)
|
||||||
|
|
||||||
current_app.logger.debug(
|
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',
|
r'sns\.([a-z]{1,3}-[a-z]+-[0-9]{1,2})\.amazonaws\.com',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(Exception):
|
class ValidationError(Exception):
|
||||||
"""
|
"""
|
||||||
ValidationError. Raised when a message fails integrity checks.
|
ValidationError. Raised when a message fails integrity checks.
|
||||||
@@ -56,7 +57,7 @@ def get_string_to_sign(sns_payload):
|
|||||||
for field in fields:
|
for field in fields:
|
||||||
field_value = sns_payload.get(field)
|
field_value = sns_payload.get(field)
|
||||||
if not isinstance(field_value, str):
|
if not isinstance(field_value, str):
|
||||||
if field == 'Subject' and field_value == None:
|
if field == 'Subject' and field_value is None:
|
||||||
continue
|
continue
|
||||||
raise ValidationError(f"In {field}, found non-string value: {field_value}")
|
raise ValidationError(f"In {field}, found non-string value: {field_value}")
|
||||||
string_to_sign += field + '\n' + field_value + '\n'
|
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)
|
string_to_sign = get_string_to_sign(sns_payload)
|
||||||
|
|
||||||
# Key signing cert url via Lambda and via webhook are slightly different
|
# 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):
|
if not isinstance(signing_cert_url, str):
|
||||||
raise ValidationError("Signing cert url must be a string")
|
raise ValidationError("Signing cert url must be a string")
|
||||||
cert_scheme, cert_netloc, *_ = urlparse(signing_cert_url)
|
cert_scheme, cert_netloc, *_ = urlparse(signing_cert_url)
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ def sns_notification_handler(data, headers):
|
|||||||
try:
|
try:
|
||||||
validate_sns_cert(message)
|
validate_sns_cert(message)
|
||||||
except Exception as e:
|
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)
|
raise InvalidRequest("SES-SNS callback failed: validation failed", 400)
|
||||||
|
|
||||||
if message.get('Type') == 'SubscriptionConfirmation':
|
if message.get('Type') == 'SubscriptionConfirmation':
|
||||||
@@ -55,8 +57,14 @@ def sns_notification_handler(data, headers):
|
|||||||
try:
|
try:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except Exception as e:
|
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}")
|
current_app.logger.warning(
|
||||||
raise InvalidRequest("SES-SNS callback failed: attempt to raise_for_status()SubscriptionConfirmation Type message failed", 400)
|
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")
|
current_app.logger.info("SES-SNS auto-confirm subscription callback succeeded")
|
||||||
return message
|
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):
|
def test_notifications_ses_200_call_process_task(client, mocker):
|
||||||
process_mock = mocker.patch("app.notifications.notifications_ses_callback.process_ses_results.apply_async")
|
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)
|
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)
|
mocker.patch("app.notifications.sns_handlers.sns_notification_handler", return_value=data)
|
||||||
json_data = json.dumps(data)
|
json_data = json.dumps(data)
|
||||||
response = client.post(
|
response = client.post(
|
||||||
@@ -156,7 +156,10 @@ def test_ses_callback_should_update_notification_status(
|
|||||||
status='sending',
|
status='sending',
|
||||||
sent_at=datetime.utcnow()
|
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 get_notification_by_id(notification.id).status == 'sending'
|
||||||
assert process_ses_results(ses_notification_callback(reference='ref'))
|
assert process_ses_results(ses_notification_callback(reference='ref'))
|
||||||
assert get_notification_by_id(notification.id).status == 'delivered'
|
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 process_ses_results(ses_notification_callback(reference='ref')) is None
|
||||||
assert mock_logger.call_count == 0
|
assert mock_logger.call_count == 0
|
||||||
assert mock_retry.call_count == 1
|
assert mock_retry.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_log_if_notification_is_missing(client, _notify_db, mocker):
|
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_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')
|
mock_logger = mocker.patch('app.celery.process_ses_receipts_tasks.current_app.logger.warning')
|
||||||
with freeze_time('2017-11-17T12:34:03.646Z'):
|
with freeze_time('2017-11-17T12:34:03.646Z'):
|
||||||
assert process_ses_results(ses_notification_callback(reference='ref')) is None
|
assert process_ses_results(ses_notification_callback(reference='ref')) is None
|
||||||
assert mock_retry.call_count == 0
|
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):
|
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_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')
|
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 process_ses_results(ses_notification_callback(reference='ref')) is None
|
||||||
assert mock_logger.call_count == 0
|
assert mock_logger.call_count == 0
|
||||||
assert mock_retry.call_count == 0
|
assert mock_retry.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_does_not_call_send_delivery_status_if_no_db_entry(
|
def test_ses_callback_does_not_call_send_delivery_status_if_no_db_entry(
|
||||||
client,
|
client,
|
||||||
_notify_db,
|
_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 process_ses_results(ses_notification_callback(reference='ref'))
|
||||||
assert get_notification_by_id(notification.id).status == 'delivered'
|
assert get_notification_by_id(notification.id).status == 'delivered'
|
||||||
send_mock.assert_not_called()
|
send_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_update_multiple_notification_status_sent(
|
def test_ses_callback_should_update_multiple_notification_status_sent(
|
||||||
client,
|
client,
|
||||||
_notify_db,
|
_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='ref2'))
|
||||||
assert process_ses_results(ses_notification_callback(reference='ref3'))
|
assert process_ses_results(ses_notification_callback(reference='ref3'))
|
||||||
assert send_mock.called
|
assert send_mock.called
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_set_status_to_temporary_failure(client,
|
def test_ses_callback_should_set_status_to_temporary_failure(client,
|
||||||
_notify_db,
|
_notify_db,
|
||||||
notify_db_session,
|
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 process_ses_results(ses_soft_bounce_callback(reference='ref'))
|
||||||
assert get_notification_by_id(notification.id).status == 'temporary-failure'
|
assert get_notification_by_id(notification.id).status == 'temporary-failure'
|
||||||
assert send_mock.called
|
assert send_mock.called
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_set_status_to_permanent_failure(client,
|
def test_ses_callback_should_set_status_to_permanent_failure(client,
|
||||||
_notify_db,
|
_notify_db,
|
||||||
notify_db_session,
|
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 process_ses_results(ses_hard_bounce_callback(reference='ref'))
|
||||||
assert get_notification_by_id(notification.id).status == 'permanent-failure'
|
assert get_notification_by_id(notification.id).status == 'permanent-failure'
|
||||||
assert send_mock.called
|
assert send_mock.called
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_send_on_complaint_to_user_callback_api(sample_email_template, mocker):
|
def test_ses_callback_should_send_on_complaint_to_user_callback_api(sample_email_template, mocker):
|
||||||
send_mock = mocker.patch(
|
send_mock = mocker.patch(
|
||||||
'app.celery.service_callback_tasks.send_complaint_to_service.apply_async'
|
'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',
|
'service_callback_api_url': 'https://original_url.com',
|
||||||
'to': 'recipient1@example.com'
|
'to': 'recipient1@example.com'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,9 +5,9 @@ import pytest
|
|||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
|
||||||
from app import statsd_client
|
from app import statsd_client
|
||||||
# from app.celery.process_sms_client_response_tasks import (
|
from app.celery.process_sms_client_response_tasks import (
|
||||||
# process_sms_client_response,
|
process_sms_client_response,
|
||||||
# )
|
)
|
||||||
from app.clients import ClientException
|
from app.clients import ClientException
|
||||||
from app.models import NOTIFICATION_TECHNICAL_FAILURE
|
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.delivery.send_to_providers.send_email_to_provider.assert_not_called()
|
||||||
app.celery.provider_tasks.deliver_email.retry.assert_called_with(queue="retry-tasks")
|
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.skip(reason="Needs updating for TTS: Failing for unknown reason")
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'exception_class', [
|
'exception_class', [
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ def test_create_nightly_billing_for_day_different_sent_by(
|
|||||||
assert record.billable_units == 1
|
assert record.billable_units == 1
|
||||||
assert record.rate_multiplier == 1.0
|
assert record.rate_multiplier == 1.0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: Remove mail")
|
@pytest.mark.skip(reason="Needs updating for TTS: Remove mail")
|
||||||
def test_create_nightly_billing_for_day_different_letter_postage(
|
def test_create_nightly_billing_for_day_different_letter_postage(
|
||||||
notify_db_session,
|
notify_db_session,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ def fake_client(notify_api):
|
|||||||
fake_client.init_app(notify_api, statsd_client)
|
fake_client.init_app(notify_api, statsd_client)
|
||||||
return fake_client
|
return fake_client
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: New SMS client")
|
@pytest.mark.skip(reason="Needs updating for TTS: New SMS client")
|
||||||
def test_send_sms(fake_client, mocker):
|
def test_send_sms(fake_client, mocker):
|
||||||
mock_send = mocker.patch.object(fake_client, 'try_send_sms')
|
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'
|
'to', 'content', 'reference', False, 'testing'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: New SMS client")
|
@pytest.mark.skip(reason="Needs updating for TTS: New SMS client")
|
||||||
def test_send_sms_error(fake_client, mocker):
|
def test_send_sms_error(fake_client, mocker):
|
||||||
mocker.patch.object(
|
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.created_by.id == notify_user.id
|
||||||
assert mmg_provider.priority == 50
|
assert mmg_provider.priority == 50
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
||||||
@freeze_time('2016-01-01 00:30')
|
@freeze_time('2016-01-01 00:30')
|
||||||
def test_adjust_provider_priority_adds_history(
|
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].version - old_provider_history_rows[0].version == 1
|
||||||
assert updated_provider_history_rows[0].priority == 50
|
assert updated_provider_history_rows[0].priority == 50
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
||||||
@freeze_time('2016-01-01 01:00')
|
@freeze_time('2016-01-01 01:00')
|
||||||
def test_get_sms_providers_for_update_returns_providers(restore_provider_details):
|
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.
|
# state of the cache is not shared between tests.
|
||||||
send_to_providers.provider_cache.clear()
|
send_to_providers.provider_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: Update with new providers")
|
@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):
|
def test_provider_to_use_should_return_random_provider(mocker, notify_db_session):
|
||||||
mmg = get_provider_details_by_identifier('mmg')
|
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 all(result == results[0] for result in results)
|
||||||
assert len(mock_choices.call_args_list) == 1
|
assert len(mock_choices.call_args_list) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: Update with new providers")
|
@pytest.mark.skip(reason="Needs updating for TTS: Update with new providers")
|
||||||
@pytest.mark.parametrize('international_provider_priority', (
|
@pytest.mark.parametrize('international_provider_priority', (
|
||||||
# Since there’s only one international provider it should always
|
# 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 persisted_notification.billable_units == 0
|
||||||
assert update_mock.called
|
assert update_mock.called
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: Update with new providers")
|
@pytest.mark.skip(reason="Needs updating for TTS: Update with new providers")
|
||||||
@pytest.mark.parametrize("starting_status, expected_status", [
|
@pytest.mark.parametrize("starting_status, expected_status", [
|
||||||
("delivered", "delivered"),
|
("delivered", "delivered"),
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ from tests.app.db import create_notification
|
|||||||
|
|
||||||
FROZEN_DATE_TIME = "2018-03-14 17:00:00"
|
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')
|
@pytest.fixture(name='sample_precompiled_letter_notification')
|
||||||
def _sample_precompiled_letter_notification(sample_letter_notification):
|
def _sample_precompiled_letter_notification(sample_letter_notification):
|
||||||
sample_letter_notification.template.hidden = True
|
sample_letter_notification.template.hidden = True
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ def mmg_post(client, data):
|
|||||||
data=data,
|
data=data,
|
||||||
headers=[('Content-Type', 'application/json')])
|
headers=[('Content-Type', 'application/json')])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
||||||
def test_firetext_callback_should_not_need_auth(client, mocker):
|
def test_firetext_callback_should_not_need_auth(client, mocker):
|
||||||
mocker.patch('app.notifications.notifications_sms_callback.process_sms_client_response')
|
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['result'] == 'error'
|
||||||
assert json_resp['message'] == ['Firetext callback failed: reference missing']
|
assert json_resp['message'] == ['Firetext callback failed: reference missing']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
||||||
def test_firetext_callback_should_return_400_if_no_reference(client, mocker):
|
def test_firetext_callback_should_return_400_if_no_reference(client, mocker):
|
||||||
data = 'mobile=441234123123&status=0&time=2016-03-10 14:17:00'
|
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['result'] == 'error'
|
||||||
assert json_resp['message'] == ['Firetext callback failed: reference missing']
|
assert json_resp['message'] == ['Firetext callback failed: reference missing']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
||||||
def test_firetext_callback_should_return_400_if_no_status(client, mocker):
|
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'
|
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['result'] == 'error'
|
||||||
assert json_resp['message'] == ['Firetext callback failed: status missing']
|
assert json_resp['message'] == ['Firetext callback failed: status missing']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
@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):
|
def test_firetext_callback_should_return_200_and_call_task_with_valid_data(client, mocker):
|
||||||
mock_celery = mocker.patch(
|
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',
|
queue='sms-callbacks',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: Firetext removal")
|
@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):
|
def test_firetext_callback_including_a_code_should_return_200_and_call_task_with_valid_data(client, mocker):
|
||||||
mock_celery = mocker.patch(
|
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',
|
queue='sms-callbacks',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
||||||
def test_mmg_callback_should_not_need_auth(client, mocker, sample_notification):
|
def test_mmg_callback_should_not_need_auth(client, mocker, sample_notification):
|
||||||
mocker.patch('app.notifications.notifications_sms_callback.process_sms_client_response')
|
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)
|
response = mmg_post(client, data)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
@pytest.mark.skip(reason="Needs updating for TTS: MMG removal")
|
||||||
def test_process_mmg_response_returns_400_for_malformed_data(client):
|
def test_process_mmg_response_returns_400_for_malformed_data(client):
|
||||||
data = json.dumps({"reference": "mmg_reference",
|
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', 'status') in json_data['message']
|
||||||
assert "{} callback failed: {} missing".format('MMG', 'CID') 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")
|
@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):
|
def test_mmg_callback_should_return_200_and_call_task_with_valid_data(client, mocker):
|
||||||
mock_celery = mocker.patch(
|
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
|
assert Notification.query.count() == 5
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Needs updating for TTS: cloud.gov redis fails, local docker works, mock redis fails")
|
@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):
|
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 json.loads(templates_call[0][1]) == {'data': template_dict}
|
||||||
assert templates_call[1]['ex'] == 604_800
|
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")
|
@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):
|
def test_should_return_template_if_found_in_redis(mocker, client, sample_template):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user