Merge branch 'master' into split-sms-and-retry

This commit is contained in:
Martyn Inglis
2016-06-13 11:39:13 +01:00
24 changed files with 352 additions and 87 deletions

View File

@@ -29,7 +29,6 @@ export ADMIN_CLIENT_USER_NAME='dev-notify-admin'
export AWS_REGION='eu-west-1' export AWS_REGION='eu-west-1'
export DANGEROUS_SALT='dev-notify-salt' export DANGEROUS_SALT='dev-notify-salt'
export FIRETEXT_API_KEY=[contact team member for api key] export FIRETEXT_API_KEY=[contact team member for api key]
export FIRETEXT_NUMBER="Firetext"
export INVITATION_EMAIL_FROM='invites@notifications.service.gov.uk' export INVITATION_EMAIL_FROM='invites@notifications.service.gov.uk'
export INVITATION_EXPIRATION_DAYS=2 export INVITATION_EXPIRATION_DAYS=2
export NOTIFY_EMAIL_DOMAIN='notify.works' export NOTIFY_EMAIL_DOMAIN='notify.works'
@@ -41,11 +40,11 @@ export TWILIO_ACCOUNT_SID=[contact team member for account sid]
export TWILIO_AUTH_TOKEN=[contact team member for auth token] export TWILIO_AUTH_TOKEN=[contact team member for auth token]
export VERIFY_CODE_FROM_EMAIL_ADDRESS='no-reply@notify.works' export VERIFY_CODE_FROM_EMAIL_ADDRESS='no-reply@notify.works'
export MMG_API_KEY=mmg=secret-key export MMG_API_KEY=mmg=secret-key
export MMG_FROM_NUMBER="MMG
export STATSD_ENABLED=True export STATSD_ENABLED=True
export STATSD_HOST="localhost" export STATSD_HOST="localhost"
export STATSD_PORT=1000 export STATSD_PORT=1000
export STATSD_PREFIX="stats-prefix" export STATSD_PREFIX="stats-prefix"
export FROM_NUMBER='from_number'
"> environment.sh "> environment.sh
``` ```

View File

@@ -19,7 +19,7 @@ def send_sms_response(provider, reference, to):
body = mmg_callback(reference, to) body = mmg_callback(reference, to)
headers = {"Content-type": "application/json"} headers = {"Content-type": "application/json"}
else: else:
headers = {"Content-type": "text/plain"} headers = {"Content-type": "application/x-www-form-urlencoded"}
body = firetext_callback(reference, to) body = firetext_callback(reference, to)
make_request('sms', provider, body, headers) make_request('sms', provider, body, headers)
@@ -92,7 +92,12 @@ def firetext_callback(notification_id, to):
status = "1" status = "1"
else: else:
status = "0" status = "0"
return 'mobile={}&status={}&time=2016-03-10 14:17:00&reference={}'.format(to, status, notification_id) return {
'mobile': to,
'status': status,
'time': '2016-03-10 14:17:00',
'reference': notification_id
}
def ses_notification_callback(reference): def ses_notification_callback(reference):

View File

@@ -1,11 +1,12 @@
import itertools import itertools
from datetime import datetime from datetime import (datetime, timedelta)
from flask import current_app from flask import current_app
from monotonic import monotonic from monotonic import monotonic
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from app import clients, statsd_client from app import clients, statsd_client
from app.clients import STATISTICS_FAILURE
from app.clients.email import EmailClientException from app.clients.email import EmailClientException
from app.clients.sms import SmsClientException from app.clients.sms import SmsClientException
from app.dao.services_dao import dao_fetch_service_by_id from app.dao.services_dao import dao_fetch_service_by_id
@@ -40,7 +41,9 @@ from app.dao.notifications_dao import (
dao_update_notification, dao_update_notification,
delete_notifications_created_more_than_a_week_ago, delete_notifications_created_more_than_a_week_ago,
dao_get_notification_statistics_for_service_and_day, dao_get_notification_statistics_for_service_and_day,
update_provider_stats update_provider_stats,
get_notifications,
update_notification_status_by_id
) )
from app.dao.jobs_dao import ( from app.dao.jobs_dao import (
@@ -337,20 +340,6 @@ def send_email(service_id, notification_id, from_address, encrypted_notification
current_app.logger.exception(e) current_app.logger.exception(e)
@notify_celery.task(name='send-sms-code')
def send_sms_code(encrypted_verification):
provider = provider_to_use('sms', 'send-sms-code')
verification_message = encryption.decrypt(encrypted_verification)
try:
provider.send_sms(validate_and_format_phone_number(verification_message['to']),
"{} is your Notify authentication code".format(
verification_message['secret_code']),
'send-sms-code')
except SmsClientException as e:
current_app.logger.exception(e)
# TODO: when placeholders in templates work, this will be a real template # TODO: when placeholders in templates work, this will be a real template
def invitation_template(user_name, service_name, url, expiry_date): def invitation_template(user_name, service_name, url, expiry_date):
from string import Template from string import Template
@@ -489,3 +478,23 @@ def provider_to_use(notification_type, notification_id):
raise Exception("No active {} providers".format(notification_type)) raise Exception("No active {} providers".format(notification_type))
return clients.get_client_by_name_and_type(active_providers_in_order[0].identifier, notification_type) return clients.get_client_by_name_and_type(active_providers_in_order[0].identifier, notification_type)
@notify_celery.task(name='timeout-sending-notifications')
def timeout_notifications():
notifications = get_notifications(filter_dict={'status': 'sending'})
now = datetime.utcnow()
for noti in notifications:
try:
if (now - noti.created_at) > timedelta(
seconds=current_app.config.get('SENDING_NOTIFICATIONS_TIMEOUT_PERIOD')
):
update_notification_status_by_id(noti.id, 'temporary-failure', STATISTICS_FAILURE)
current_app.logger.info((
"Timeout period reached for notification ({})"
", status has been updated.").format(noti.id))
except Exception as e:
current_app.logger.exception(e)
current_app.logger.error((
"Exception raised trying to timeout notification ({})"
", skipping notification update.").format(noti.id))

View File

@@ -60,7 +60,7 @@ class FiretextClient(SmsClient):
super(SmsClient, self).__init__(*args, **kwargs) super(SmsClient, self).__init__(*args, **kwargs)
self.current_app = current_app self.current_app = current_app
self.api_key = current_app.config.get('FIRETEXT_API_KEY') self.api_key = current_app.config.get('FIRETEXT_API_KEY')
self.from_number = current_app.config.get('FIRETEXT_NUMBER') self.from_number = current_app.config.get('FROM_NUMBER')
self.name = 'firetext' self.name = 'firetext'
self.statsd_client = statsd_client self.statsd_client = statsd_client
@@ -93,7 +93,7 @@ class FiretextClient(SmsClient):
"POST", "POST",
"https://www.firetext.co.uk/api/sendsms", "https://www.firetext.co.uk/api/sendsms",
response.status_code, response.status_code,
firetext_response firetext_response.items()
) )
) )
except RequestException as e: except RequestException as e:

View File

@@ -60,7 +60,7 @@ class MMGClient(SmsClient):
super(SmsClient, self).__init__(*args, **kwargs) super(SmsClient, self).__init__(*args, **kwargs)
self.current_app = current_app self.current_app = current_app
self.api_key = current_app.config.get('MMG_API_KEY') self.api_key = current_app.config.get('MMG_API_KEY')
self.from_number = current_app.config.get('MMG_FROM_NUMBER') self.from_number = current_app.config.get('FROM_NUMBER')
self.name = 'mmg' self.name = 'mmg'
self.statsd_client = statsd_client self.statsd_client = statsd_client
@@ -91,7 +91,7 @@ class MMGClient(SmsClient):
"POST", "POST",
"https://www.mmgrp.co.uk/API/json/api.php", "https://www.mmgrp.co.uk/API/json/api.php",
response.status_code, response.status_code,
response.json() response.json().items()
) )
) )
except RequestException as e: except RequestException as e:

View File

@@ -139,6 +139,14 @@ def dao_get_template_statistics_for_service(service_id, limit_days=None):
desc(TemplateStatistics.updated_at)).all() desc(TemplateStatistics.updated_at)).all()
def dao_get_template_statistics_for_template(template_id):
return TemplateStatistics.query.filter(
TemplateStatistics.template_id == template_id
).order_by(
desc(TemplateStatistics.updated_at)
).all()
@transactional @transactional
def dao_create_notification(notification, notification_type): def dao_create_notification(notification, notification_type):
if notification.job_id: if notification.job_id:
@@ -332,6 +340,10 @@ def get_notification_by_id(notification_id):
return Notification.query.filter_by(id=notification_id).first() return Notification.query.filter_by(id=notification_id).first()
def get_notifications(filter_dict=None):
return _filter_query(Notification.query, filter_dict=filter_dict)
def get_notifications_for_service(service_id, def get_notifications_for_service(service_id,
filter_dict=None, filter_dict=None,
page=1, page=1,

View File

@@ -32,6 +32,8 @@ class PermissionDAO(DAOClass):
class Meta: class Meta:
model = Permission model = Permission
# TODO rework this as last filter wins, whereas what is needed is
# append to filter so that semantics are 'and'
def get_query(self, filter_by_dict=None): def get_query(self, filter_by_dict=None):
if filter_by_dict is None: if filter_by_dict is None:
filter_by_dict = MultiDict() filter_by_dict = MultiDict()
@@ -60,13 +62,14 @@ class PermissionDAO(DAOClass):
self.create_instance(permission, _commit=False) self.create_instance(permission, _commit=False)
def remove_user_service_permissions(self, user, service): def remove_user_service_permissions(self, user, service):
query = self.get_query(filter_by_dict={'user': user.id, 'service': service.id}) query = self.Meta.model.query.filter_by(user=user, service=service)
query.delete() query.delete()
def set_user_service_permission(self, user, service, permissions, _commit=False): def set_user_service_permission(self, user, service, permissions, _commit=False, replace=False):
try: try:
query = self.get_query(filter_by_dict={'user': user.id, 'service': service.id}) if replace:
query.delete() query = self.Meta.model.query.filter_by(user=user, service=service)
query.delete()
for p in permissions: for p in permissions:
p.user = user p.user = user
p.service = service p.service = service

View File

@@ -176,9 +176,12 @@ def process_firetext_response():
current_app.logger.info(validation_errors) current_app.logger.info(validation_errors)
return jsonify(result='error', message=validation_errors), 400 return jsonify(result='error', message=validation_errors), 400
statsd_client.incr('notifications.callback.firetext.code.{}'.format(request.form.get('code'))) response_code = request.form.get('code')
status = request.form.get('status')
statsd_client.incr('notifications.callback.firetext.code.{}'.format(response_code))
current_app.logger.info('Firetext status: {}, extended error code: {}'.format(status, response_code))
success, errors = process_sms_client_response(status=request.form.get('status'), success, errors = process_sms_client_response(status=status,
reference=request.form.get('reference'), reference=request.form.get('reference'),
client_name=client_name) client_name=client_name)
if errors: if errors:

View File

@@ -160,6 +160,7 @@ class JobSchema(BaseSchema):
class Meta: class Meta:
model = models.Job model = models.Job
exclude = ('notifications',)
class RequestVerifyCodeSchema(ma.Schema): class RequestVerifyCodeSchema(ma.Schema):

View File

@@ -1,9 +1,13 @@
from datetime import (datetime, date) from datetime import (
datetime,
date
)
from flask import Blueprint
from flask import ( from flask import (
jsonify, jsonify,
request request,
abort,
Blueprint
) )
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
@@ -152,6 +156,8 @@ def add_user_to_service(service_id, user_id):
message='User id: {} already part of service id: {}'.format(user_id, service_id)), 400 message='User id: {} already part of service id: {}'.format(user_id, service_id)), 400
permissions, errors = permission_schema.load(request.get_json(), many=True) permissions, errors = permission_schema.load(request.get_json(), many=True)
if errors:
abort(400, errors)
dao_add_user_to_service(service, user, permissions) dao_add_user_to_service(service, user, permissions)
data, errors = service_schema.dump(service) data, errors = service_schema.dump(service)
@@ -174,15 +180,6 @@ def remove_user_from_service(service_id, user_id):
return jsonify({}), 204 return jsonify({}), 204
def _process_permissions(user, service, permission_groups):
from app.permissions_utils import get_permissions_by_group
permissions = get_permissions_by_group(permission_groups)
for permission in permissions:
permission.user = user
permission.service = service
return permissions
@service.route('/<uuid:service_id>/fragment/aggregate_statistics') @service.route('/<uuid:service_id>/fragment/aggregate_statistics')
def get_service_provider_aggregate_statistics(service_id): def get_service_provider_aggregate_statistics(service_id):
service = dao_fetch_service_by_id(service_id) service = dao_fetch_service_by_id(service_id)

View File

@@ -5,7 +5,10 @@ from flask import (
current_app current_app
) )
from app.dao.notifications_dao import dao_get_template_statistics_for_service from app.dao.notifications_dao import (
dao_get_template_statistics_for_service,
dao_get_template_statistics_for_template
)
from app.schemas import template_statistics_schema from app.schemas import template_statistics_schema
@@ -34,3 +37,12 @@ def get_template_statistics_for_service(service_id):
if errors: if errors:
return jsonify(result="error", message=errors), 400 return jsonify(result="error", message=errors), 400
return jsonify(data=data) return jsonify(data=data)
@template_statistics.route('/<template_id>')
def get_template_statistics_for_template_id(service_id, template_id):
stats = dao_get_template_statistics_for_template(template_id)
data, errors = template_statistics_schema.dump(stats, many=True)
if errors:
return jsonify(result="error", message=errors), 400
return jsonify(data=data)

View File

@@ -188,7 +188,7 @@ def set_permissions(user_id, service_id):
for p in permissions: for p in permissions:
p.user = user p.user = user
p.service = service p.service = service
permission_dao.set_user_service_permission(user, service, permissions, _commit=True) permission_dao.set_user_service_permission(user, service, permissions, _commit=True, replace=True)
return jsonify({}), 204 return jsonify({}), 204

View File

@@ -18,7 +18,6 @@ class Config(object):
NOTIFY_JOB_QUEUE = os.environ['NOTIFY_JOB_QUEUE'] NOTIFY_JOB_QUEUE = os.environ['NOTIFY_JOB_QUEUE']
# Notification Queue names are a combination of a prefix plus a name # Notification Queue names are a combination of a prefix plus a name
NOTIFICATION_QUEUE_PREFIX = os.environ['NOTIFICATION_QUEUE_PREFIX'] NOTIFICATION_QUEUE_PREFIX = os.environ['NOTIFICATION_QUEUE_PREFIX']
MMG_FROM_NUMBER = os.environ['MMG_FROM_NUMBER']
SECRET_KEY = os.environ['SECRET_KEY'] SECRET_KEY = os.environ['SECRET_KEY']
SQLALCHEMY_COMMIT_ON_TEARDOWN = False SQLALCHEMY_COMMIT_ON_TEARDOWN = False
SQLALCHEMY_DATABASE_URI = os.environ['SQLALCHEMY_DATABASE_URI'] SQLALCHEMY_DATABASE_URI = os.environ['SQLALCHEMY_DATABASE_URI']
@@ -67,6 +66,11 @@ class Config(object):
'task': 'delete-successful-notifications', 'task': 'delete-successful-notifications',
'schedule': crontab(minute=0, hour='0,1,2'), 'schedule': crontab(minute=0, hour='0,1,2'),
'options': {'queue': 'periodic'} 'options': {'queue': 'periodic'}
},
'timeout-sending-notifications': {
'task': 'timeout-sending-notifications',
'schedule': crontab(minute=0, hour='0,1,2'),
'options': {'queue': 'periodic'}
} }
} }
CELERY_QUEUES = [ CELERY_QUEUES = [
@@ -87,19 +91,20 @@ class Config(object):
] ]
TWILIO_ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID') TWILIO_ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID')
TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN') TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN')
TWILIO_NUMBER = os.getenv('TWILIO_NUMBER')
FIRETEXT_NUMBER = os.getenv('FIRETEXT_NUMBER')
FIRETEXT_API_KEY = os.getenv("FIRETEXT_API_KEY") FIRETEXT_API_KEY = os.getenv("FIRETEXT_API_KEY")
LOADTESTING_NUMBER = os.getenv('LOADTESTING_NUMBER') LOADTESTING_NUMBER = os.getenv('LOADTESTING_NUMBER')
LOADTESTING_API_KEY = os.getenv("LOADTESTING_API_KEY") LOADTESTING_API_KEY = os.getenv("LOADTESTING_API_KEY")
CSV_UPLOAD_BUCKET_NAME = 'local-notifications-csv-upload' CSV_UPLOAD_BUCKET_NAME = 'local-notifications-csv-upload'
NOTIFICATIONS_ALERT = 5 # five mins NOTIFICATIONS_ALERT = 5 # five mins
FROM_NUMBER = os.getenv('FROM_NUMBER')
STATSD_ENABLED = False STATSD_ENABLED = False
STATSD_HOST = "localhost" STATSD_HOST = "localhost"
STATSD_PORT = None STATSD_PORT = None
STATSD_PREFIX = None STATSD_PREFIX = None
SENDING_NOTIFICATIONS_TIMEOUT_PERIOD = 259200
class Development(Config): class Development(Config):
DEBUG = True DEBUG = True

View File

@@ -14,7 +14,6 @@ class Live(Config):
VERIFY_CODE_FROM_EMAIL_ADDRESS = os.environ['LIVE_VERIFY_CODE_FROM_EMAIL_ADDRESS'] VERIFY_CODE_FROM_EMAIL_ADDRESS = os.environ['LIVE_VERIFY_CODE_FROM_EMAIL_ADDRESS']
NOTIFY_EMAIL_DOMAIN = os.environ['LIVE_NOTIFY_EMAIL_DOMAIN'] NOTIFY_EMAIL_DOMAIN = os.environ['LIVE_NOTIFY_EMAIL_DOMAIN']
FIRETEXT_API_KEY = os.getenv("LIVE_FIRETEXT_API_KEY") FIRETEXT_API_KEY = os.getenv("LIVE_FIRETEXT_API_KEY")
FIRETEXT_NUMBER = os.getenv("LIVE_FIRETEXT_NUMBER")
TWILIO_AUTH_TOKEN = os.getenv('LIVE_TWILIO_AUTH_TOKEN') TWILIO_AUTH_TOKEN = os.getenv('LIVE_TWILIO_AUTH_TOKEN')
MMG_API_KEY = os.environ['LIVE_MMG_API_KEY'] MMG_API_KEY = os.environ['LIVE_MMG_API_KEY']
CSV_UPLOAD_BUCKET_NAME = 'live-notifications-csv-upload' CSV_UPLOAD_BUCKET_NAME = 'live-notifications-csv-upload'
@@ -22,6 +21,7 @@ class Live(Config):
STATSD_HOST = os.getenv('LIVE_STATSD_HOST') STATSD_HOST = os.getenv('LIVE_STATSD_HOST')
STATSD_PORT = os.getenv('LIVE_STATSD_PORT') STATSD_PORT = os.getenv('LIVE_STATSD_PORT')
STATSD_PREFIX = os.getenv('LIVE_STATSD_PREFIX') STATSD_PREFIX = os.getenv('LIVE_STATSD_PREFIX')
FROM_NUMBER = os.getenv('LIVE_FROM_NUMBER')
BROKER_TRANSPORT_OPTIONS = { BROKER_TRANSPORT_OPTIONS = {
'region': 'eu-west-1', 'region': 'eu-west-1',

View File

@@ -14,10 +14,10 @@ class Staging(Config):
VERIFY_CODE_FROM_EMAIL_ADDRESS = os.environ['STAGING_VERIFY_CODE_FROM_EMAIL_ADDRESS'] VERIFY_CODE_FROM_EMAIL_ADDRESS = os.environ['STAGING_VERIFY_CODE_FROM_EMAIL_ADDRESS']
NOTIFY_EMAIL_DOMAIN = os.environ['STAGING_NOTIFY_EMAIL_DOMAIN'] NOTIFY_EMAIL_DOMAIN = os.environ['STAGING_NOTIFY_EMAIL_DOMAIN']
FIRETEXT_API_KEY = os.getenv("STAGING_FIRETEXT_API_KEY") FIRETEXT_API_KEY = os.getenv("STAGING_FIRETEXT_API_KEY")
FIRETEXT_NUMBER = os.getenv("STAGING_FIRETEXT_NUMBER")
TWILIO_AUTH_TOKEN = os.getenv('STAGING_TWILIO_AUTH_TOKEN') TWILIO_AUTH_TOKEN = os.getenv('STAGING_TWILIO_AUTH_TOKEN')
MMG_API_KEY = os.environ['STAGING_MMG_API_KEY'] MMG_API_KEY = os.environ['STAGING_MMG_API_KEY']
CSV_UPLOAD_BUCKET_NAME = 'staging-notifications-csv-upload' CSV_UPLOAD_BUCKET_NAME = 'staging-notifications-csv-upload'
FROM_NUMBER = os.getenv('STAGING_FROM_NUMBER')
BROKER_TRANSPORT_OPTIONS = { BROKER_TRANSPORT_OPTIONS = {
'region': 'eu-west-1', 'region': 'eu-west-1',

View File

@@ -14,12 +14,9 @@ export SQLALCHEMY_DATABASE_URI='postgresql://localhost/test_notification_api'
export VERIFY_CODE_FROM_EMAIL_ADDRESS='no-reply@notify.works' export VERIFY_CODE_FROM_EMAIL_ADDRESS='no-reply@notify.works'
export TWILIO_ACCOUNT_SID="test" export TWILIO_ACCOUNT_SID="test"
export TWILIO_AUTH_TOKEN="test" export TWILIO_AUTH_TOKEN="test"
export TWILIO_NUMBER="test"
export FIRETEXT_API_KEY="Firetext" export FIRETEXT_API_KEY="Firetext"
export FIRETEXT_NUMBER="Firetext"
export NOTIFY_EMAIL_DOMAIN="test.notify.com" export NOTIFY_EMAIL_DOMAIN="test.notify.com"
export MMG_API_KEY='mmg-secret-key' export MMG_API_KEY='mmg-secret-key'
export MMG_FROM_NUMBER='test'
export LOADTESTING_API_KEY="loadtesting" export LOADTESTING_API_KEY="loadtesting"
export LOADTESTING_NUMBER="loadtesting" export LOADTESTING_NUMBER="loadtesting"
export STATSD_ENABLED=True export STATSD_ENABLED=True
@@ -27,3 +24,4 @@ export STATSD_HOST="localhost"
export STATSD_PORT=1000 export STATSD_PORT=1000
export STATSD_PREFIX="stats-prefix" export STATSD_PREFIX="stats-prefix"
export API_HOST_NAME="http://localhost:6011" export API_HOST_NAME="http://localhost:6011"
export FROM_NUMBER='from_number'

View File

@@ -0,0 +1,35 @@
"""empty message
Revision ID: 0027_update_provider_rates
Revises: 0026_rename_notify_service
Create Date: 2016-06-08 01:00:00.000000
"""
# revision identifiers, used by Alembic.
revision = '0027_update_provider_rates'
down_revision = '0026_rename_notify_service'
from alembic import op
import sqlalchemy as sa
from datetime import datetime
import uuid
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.get_bind()
op.execute((
"INSERT INTO provider_rates (id, valid_from, rate, provider_id) VALUES ('{}', '{}', 1.8, "
"(SELECT id FROM provider_details WHERE identifier = 'mmg'))").format(uuid.uuid4(), datetime.utcnow()))
op.execute((
"INSERT INTO provider_rates (id, valid_from, rate, provider_id) VALUES ('{}', '{}', 2.5, "
"(SELECT id FROM provider_details WHERE identifier = 'firetext'))").format(uuid.uuid4(), datetime.utcnow()))
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.get_bind()
op.execute("DELETE FROM provider_rates")
### end Alembic commands ###

View File

@@ -22,4 +22,4 @@ statsd==3.2.1
git+https://github.com/alphagov/notifications-python-client.git@1.0.0#egg=notifications-python-client==1.0.0 git+https://github.com/alphagov/notifications-python-client.git@1.0.0#egg=notifications-python-client==1.0.0
git+https://github.com/alphagov/notifications-utils.git@6.2.0#egg=notifications-utils==6.2.0 git+https://github.com/alphagov/notifications-utils.git@6.3.1#egg=notifications-utils==6.3.1

View File

@@ -20,6 +20,8 @@ def test_make_mmg_callback(notify_api, rmock):
send_sms_response("mmg", "1234", "07811111111") send_sms_response("mmg", "1234", "07811111111")
assert rmock.called assert rmock.called
assert rmock.request_history[0].url == endpoint
assert json.loads(rmock.request_history[0].text)['MSISDN'] == '07811111111'
def test_make_firetext_callback(notify_api, rmock): def test_make_firetext_callback(notify_api, rmock):
@@ -32,6 +34,8 @@ def test_make_firetext_callback(notify_api, rmock):
send_sms_response("firetext", "1234", "07811111111") send_sms_response("firetext", "1234", "07811111111")
assert rmock.called assert rmock.called
assert rmock.request_history[0].url == endpoint
assert 'mobile=07811111111' in rmock.request_history[0].text
def test_make_ses_callback(notify_api, rmock): def test_make_ses_callback(notify_api, rmock):
@@ -71,11 +75,21 @@ def test_temp_failure_mmg_callback():
def test_delivered_firetext_callback(): def test_delivered_firetext_callback():
assert firetext_callback("1234", "07811111111") == "mobile=07811111111&status=0&time=2016-03-10 14:17:00&reference=1234" # noqa assert firetext_callback('1234', '07811111111') == {
'mobile': '07811111111',
'status': '0',
'time': '2016-03-10 14:17:00',
'reference': '1234'
}
def test_failure_firetext_callback(): def test_failure_firetext_callback():
assert firetext_callback("1234", "07822222222") == "mobile=07822222222&status=1&time=2016-03-10 14:17:00&reference=1234" # noqa assert firetext_callback('1234', '07822222222') == {
'mobile': '07822222222',
'status': '1',
'time': '2016-03-10 14:17:00',
'reference': '1234'
}
def test_delivered_ses_callback(): def test_delivered_ses_callback():

View File

@@ -7,7 +7,6 @@ from notifications_utils.recipients import validate_phone_number, format_phone_n
from app.celery import provider_tasks from app.celery import provider_tasks
from app.celery.tasks import ( from app.celery.tasks import (
send_sms, send_sms,
send_sms_code,
send_email, send_email,
process_job, process_job,
email_invited_user, email_invited_user,
@@ -16,7 +15,8 @@ from app.celery.tasks import (
delete_invitations, delete_invitations,
delete_failed_notifications, delete_failed_notifications,
delete_successful_notifications, delete_successful_notifications,
provider_to_use provider_to_use,
timeout_notifications
) )
from app.celery.research_mode_tasks import ( from app.celery.research_mode_tasks import (
send_email_response, send_email_response,
@@ -807,33 +807,6 @@ def test_should_not_send_email_if_db_peristance_failed(sample_email_template, mo
assert 'No row was found for one' in str(e.value) assert 'No row was found for one' in str(e.value)
def test_should_send_sms_code(mocker):
notification = {'to': '+447234123123',
'secret_code': '12345'}
encrypted_notification = encryption.encrypt(notification)
mocker.patch('app.mmg_client.send_sms')
send_sms_code(encrypted_notification)
mmg_client.send_sms.assert_called_once_with(
format_phone_number(validate_phone_number(notification['to'])),
"{} is your Notify authentication code".format(notification['secret_code']),
'send-sms-code')
def test_should_throw_mmg_client_exception(mocker):
notification = {'to': '+447234123123',
'secret_code': '12345'}
encrypted_notification = encryption.encrypt(notification)
mocker.patch('app.mmg_client.send_sms', side_effect=MMGClientException(mmg_error))
send_sms_code(encrypted_notification)
mmg_client.send_sms.assert_called_once_with(
format_phone_number(validate_phone_number(notification['to'])),
"{} is your Notify authentication code".format(notification['secret_code']),
'send-sms-code')
def test_email_invited_user_should_send_email(notify_api, mocker): def test_email_invited_user_should_send_email(notify_api, mocker):
with notify_api.test_request_context(): with notify_api.test_request_context():
invitation = {'to': 'new_person@it.gov.uk', invitation = {'to': 'new_person@it.gov.uk',
@@ -1016,3 +989,41 @@ def _notification_json(template, to, personalisation=None, job_id=None, row_numb
if row_number: if row_number:
notification['row_number'] = row_number notification['row_number'] = row_number
return notification return notification
def test_update_status_of_notifications_after_timeout(notify_api,
notify_db,
notify_db_session,
sample_service,
sample_template,
mmg_provider):
with notify_api.test_request_context():
not1 = sample_notification(
notify_db,
notify_db_session,
service=sample_service,
template=sample_template,
status='sending',
created_at=datetime.utcnow() - timedelta(
seconds=current_app.config.get('SENDING_NOTIFICATIONS_TIMEOUT_PERIOD') + 10))
timeout_notifications()
assert not1.status == 'temporary-failure'
def test_not_update_status_of_notification_before_timeout(notify_api,
notify_db,
notify_db_session,
sample_service,
sample_template,
mmg_provider):
with notify_api.test_request_context():
not1 = sample_notification(
notify_db,
notify_db_session,
service=sample_service,
template=sample_template,
status='sending',
created_at=datetime.utcnow() - timedelta(
seconds=current_app.config.get('SENDING_NOTIFICATIONS_TIMEOUT_PERIOD') - 10))
timeout_notifications()
assert not1.status == 'sending'

View File

@@ -536,7 +536,7 @@ def mock_firetext_client(mocker, statsd_client=None):
statsd_client = statsd_client or mocker.Mock() statsd_client = statsd_client or mocker.Mock()
current_app = mocker.Mock(config={ current_app = mocker.Mock(config={
'FIRETEXT_API_KEY': 'foo', 'FIRETEXT_API_KEY': 'foo',
'FIRETEXT_NUMBER': 'bar' 'FROM_NUMBER': 'bar'
}) })
client.init_app(current_app, statsd_client) client.init_app(current_app, statsd_client)
return client return client
@@ -548,7 +548,7 @@ def mock_mmg_client(mocker, statsd_client=None):
statsd_client = statsd_client or mocker.Mock()() statsd_client = statsd_client or mocker.Mock()()
current_app = mocker.Mock(config={ current_app = mocker.Mock(config={
'MMG_API_KEY': 'foo', 'MMG_API_KEY': 'foo',
'MMG_FROM_NUMBER': 'bar' 'FROM_NUMBER': 'bar'
}) })
client.init_app(current_app, statsd_client) client.init_app(current_app, statsd_client)
return client return client

View File

@@ -334,3 +334,53 @@ def test_delete_service_and_associated_objects(notify_db,
assert InvitedUser.query.count() == 0 assert InvitedUser.query.count() == 0
assert Service.query.count() == 0 assert Service.query.count() == 0
assert Service.get_history_model().query.count() == 0 assert Service.get_history_model().query.count() == 0
def test_add_existing_user_to_another_service_doesnot_change_old_permissions(sample_user):
service_one = Service(name="service_one",
email_from="service_one",
message_limit=1000,
active=True,
restricted=False,
created_by=sample_user)
dao_create_service(service_one, sample_user)
assert sample_user.id == service_one.users[0].id
test_user_permissions = Permission.query.filter_by(service=service_one, user=sample_user).all()
assert len(test_user_permissions) == 8
other_user = User(
name='Other Test User',
email_address='other_user@digital.cabinet-office.gov.uk',
password='password',
mobile_number='+447700900987'
)
save_model_user(other_user)
service_two = Service(name="service_two",
email_from="service_two",
message_limit=1000,
active=True,
restricted=False,
created_by=other_user)
dao_create_service(service_two, other_user)
assert other_user.id == service_two.users[0].id
other_user_permissions = Permission.query.filter_by(service=service_two, user=other_user).all()
assert len(other_user_permissions) == 8
other_user_service_one_permissions = Permission.query.filter_by(service=service_one, user=other_user).all()
assert len(other_user_service_one_permissions) == 0
# adding the other_user to service_one should leave all other_user permissions on service_two intact
permissions = []
for p in ['send_emails', 'send_texts', 'send_letters']:
permissions.append(Permission(permission=p))
dao_add_user_to_service(service_one, other_user, permissions=permissions)
other_user_service_one_permissions = Permission.query.filter_by(service=service_one, user=other_user).all()
assert len(other_user_service_one_permissions) == 3
other_user_service_two_permissions = Permission.query.filter_by(service=service_two, user=other_user).all()
assert len(other_user_service_two_permissions) == 8

View File

@@ -1,10 +1,12 @@
from datetime import datetime
import json import json
from freezegun import freeze_time from freezegun import freeze_time
from app import db from app import db
from app.models import TemplateStatistics from app.models import TemplateStatistics
from tests import create_authorization_header from tests import create_authorization_header
from tests.app.conftest import sample_template as create_sample_template
@freeze_time('2016-04-09') @freeze_time('2016-04-09')
@@ -130,3 +132,102 @@ def test_get_all_template_statistics_with_bad_limit_arg_returns_400(notify_api,
json_resp = json.loads(response.get_data(as_text=True)) json_resp = json.loads(response.get_data(as_text=True))
assert json_resp['result'] == 'error' assert json_resp['result'] == 'error'
assert json_resp['message'] == {'limit_days': ['blurk is not an integer']} assert json_resp['message'] == {'limit_days': ['blurk is not an integer']}
def test_get_template_statistics_for_template_only_returns_for_provided_template(
notify_db,
notify_db_session,
notify_api,
sample_service
):
template_1 = create_sample_template(
notify_db,
notify_db_session,
template_name='Sample Template 1',
service=sample_service
)
template_2 = create_sample_template(
notify_db,
notify_db_session,
template_name='Sample Template 2',
service=sample_service
)
template_1_stats_1 = TemplateStatistics(
template_id=template_1.id,
service_id=sample_service.id,
day=datetime(2001, 1, 1)
)
template_1_stats_2 = TemplateStatistics(
template_id=template_1.id,
service_id=sample_service.id,
day=datetime(2001, 1, 2)
)
template_2_stats = TemplateStatistics(
template_id=template_2.id,
service_id=sample_service.id,
day=datetime(2001, 1, 1)
)
# separate commit to ensure stats_1 has earlier updated_at time
db.session.add(template_1_stats_1)
db.session.commit()
db.session.add_all([template_1_stats_2, template_2_stats])
db.session.commit()
with notify_api.test_request_context():
with notify_api.test_client() as client:
auth_header = create_authorization_header()
response = client.get(
'/service/{}/template-statistics/{}'.format(sample_service.id, template_1.id),
headers=[('Content-Type', 'application/json'), auth_header],
)
assert response.status_code == 200
json_resp = json.loads(response.get_data(as_text=True))
assert len(json_resp['data']) == 2
assert json_resp['data'][0]['id'] == str(template_1_stats_2.id)
assert json_resp['data'][1]['id'] == str(template_1_stats_1.id)
def test_get_template_statistics_for_template_returns_empty_if_no_statistics(
notify_db,
notify_db_session,
notify_api,
sample_service
):
template_1 = create_sample_template(
notify_db,
notify_db_session,
template_name='Sample Template 1',
service=sample_service
)
template_2 = create_sample_template(
notify_db,
notify_db_session,
template_name='Sample Template 2',
service=sample_service
)
template_1_stats = TemplateStatistics(
template_id=template_1.id,
service_id=sample_service.id,
day=datetime(2001, 1, 1)
)
db.session.add(template_1_stats)
db.session.commit()
with notify_api.test_request_context():
with notify_api.test_client() as client:
auth_header = create_authorization_header()
response = client.get(
'/service/{}/template-statistics/{}'.format(sample_service.id, template_2.id),
headers=[('Content-Type', 'application/json'), auth_header],
)
assert response.status_code == 200
json_resp = json.loads(response.get_data(as_text=True))
assert json_resp['data'] == []

10
tests/app/test_schemas.py Normal file
View File

@@ -0,0 +1,10 @@
def test_job_schema_doesnt_return_notifications(sample_notification):
from app.schemas import job_schema
job = sample_notification.job
assert job.notifications.count() == 1
data, errors = job_schema.dump(job)
assert not errors
assert 'notifications' not in data