Merge branch 'master' into rate-limit

Conflicts:
	app/celery/tasks.py
	tests/app/celery/test_tasks.py
This commit is contained in:
Martyn Inglis
2016-03-09 14:16:59 +00:00
15 changed files with 247 additions and 90 deletions

View File

@@ -16,7 +16,7 @@ from sqlalchemy.exc import SQLAlchemyError
from app.aws import s3 from app.aws import s3
from datetime import datetime from datetime import datetime
from utils.template import Template from utils.template import Template
from utils.recipients import RecipientCSV, first_column_heading from utils.recipients import RecipientCSV
@notify_celery.task(name="process-job") @notify_celery.task(name="process-job")
@@ -47,19 +47,24 @@ def process_job(job_id):
job.status = 'in progress' job.status = 'in progress'
dao_update_job(job) dao_update_job(job)
template = Template(
dao_get_template_by_id(job.template_id).__dict__
)
for recipient, personalisation in RecipientCSV( for recipient, personalisation in RecipientCSV(
s3.get_job_from_s3(job.bucket_name, job_id), s3.get_job_from_s3(job.bucket_name, job_id),
template_type=job.template.template_type template_type=template.template_type,
placeholders=template.placeholders
).recipients_and_personalisation: ).recipients_and_personalisation:
encrypted = encryption.encrypt({ encrypted = encryption.encrypt({
'template': job.template_id, 'template': template.id,
'job': str(job.id), 'job': str(job.id),
'to': recipient, 'to': recipient,
'personalisation': personalisation 'personalisation': personalisation
}) })
if job.template.template_type == 'sms': if template.template_type == 'sms':
send_sms.apply_async(( send_sms.apply_async((
str(job.service_id), str(job.service_id),
str(create_uuid()), str(create_uuid()),
@@ -68,11 +73,11 @@ def process_job(job_id):
queue='bulk-sms' queue='bulk-sms'
) )
if job.template.template_type == 'email': if template.template_type == 'email':
send_email.apply_async(( send_email.apply_async((
str(job.service_id), str(job.service_id),
str(create_uuid()), str(create_uuid()),
job.template.subject, template.subject,
"{}@{}".format(job.service.email_from, current_app.config['NOTIFY_EMAIL_DOMAIN']), "{}@{}".format(job.service.email_from, current_app.config['NOTIFY_EMAIL_DOMAIN']),
encrypted, encrypted,
datetime.utcnow().strftime(DATETIME_FORMAT)), datetime.utcnow().strftime(DATETIME_FORMAT)),
@@ -126,8 +131,7 @@ def send_sms(service_id, notification_id, encrypted_notification, created_at):
template = Template( template = Template(
dao_get_template_by_id(notification['template']).__dict__, dao_get_template_by_id(notification['template']).__dict__,
values=notification.get('personalisation', {}), values=notification.get('personalisation', {}),
prefix=service.name, prefix=service.name
drop_values={first_column_heading['sms']}
) )
client.send_sms( client.send_sms(
@@ -194,8 +198,7 @@ def send_email(service_id, notification_id, subject, from_address, encrypted_not
try: try:
template = Template( template = Template(
dao_get_template_by_id(notification['template']).__dict__, dao_get_template_by_id(notification['template']).__dict__,
values=notification.get('personalisation', {}), values=notification.get('personalisation', {})
drop_values={first_column_heading['email']}
) )
client.send_email( client.send_email(
@@ -222,7 +225,7 @@ def send_sms_code(encrypted_verification):
try: try:
firetext_client.send_sms(verification_message['to'], verification_message['secret_code']) firetext_client.send_sms(verification_message['to'], verification_message['secret_code'])
except FiretextClientException as e: except FiretextClientException as e:
current_app.logger.error(e) current_app.logger.exception(e)
@notify_celery.task(name='send-email-code') @notify_celery.task(name='send-email-code')
@@ -234,7 +237,7 @@ def send_email_code(encrypted_verification_message):
"Verification code", "Verification code",
verification_message['secret_code']) verification_message['secret_code'])
except AwsSesClientException as e: except AwsSesClientException as e:
current_app.logger.error(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
@@ -276,4 +279,27 @@ def email_invited_user(encrypted_invitation):
subject_line, subject_line,
invitation_content) invitation_content)
except AwsSesClientException as e: except AwsSesClientException as e:
current_app.logger.error(e) current_app.logger.exception(e)
def password_reset_message(name, url):
from string import Template
t = Template("Hi $user_name,\n\n"
"We received a request to reset your password on GOV.UK Notify.\n\n"
"If you didn't request this email, you can ignore it your password has not been changed.\n\n"
"To reset your password, click this link:\n\n"
"$url")
return t.substitute(user_name=name, url=url)
@notify_celery.task(name='email-reset-password')
def email_reset_password(encrypted_reset_password_message):
reset_password_message = encryption.decrypt(encrypted_reset_password_message)
try:
aws_ses_client.send_email(current_app.config['VERIFY_CODE_FROM_EMAIL_ADDRESS'],
reset_password_message['to'],
"Reset your GOV.UK Notify password",
password_reset_message(name=reset_password_message['name'],
url=reset_password_message['reset_password_url']))
except AwsSesClientException as e:
current_app.logger.exception(e)

View File

@@ -16,6 +16,7 @@ def save_model_user(usr, update_dict={}, pwd=None):
if update_dict: if update_dict:
if update_dict.get('id'): if update_dict.get('id'):
del update_dict['id'] del update_dict['id']
update_dict.pop('password_changed_at')
db.session.query(User).filter_by(id=usr.id).update(update_dict) db.session.query(User).filter_by(id=usr.id).update(update_dict)
else: else:
db.session.add(usr) db.session.add(usr)

View File

@@ -41,7 +41,7 @@ def register_errors(blueprint):
@blueprint.app_errorhandler(500) @blueprint.app_errorhandler(500)
def internal_server_error(e): def internal_server_error(e):
if isinstance(e, str): if isinstance(e, str):
current_app.logger.error(e) current_app.logger.exception(e)
elif isinstance(e, Exception): elif isinstance(e, Exception):
current_app.logger.exception(e) current_app.logger.exception(e)
return jsonify(result='error', message="Internal server error"), 500 return jsonify(result='error', message="Internal server error"), 500

View File

@@ -1,10 +1,8 @@
import re
from flask import current_app
from flask_marshmallow.fields import fields from flask_marshmallow.fields import fields
from . import ma from . import ma
from . import models from . import models
from app.dao.permissions_dao import permission_dao from app.dao.permissions_dao import permission_dao
from marshmallow import (post_load, ValidationError, validates, validates_schema) from marshmallow import (post_load, ValidationError, validates)
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from utils.recipients import ( from utils.recipients import (
validate_email_address, InvalidEmailError, validate_email_address, InvalidEmailError,
@@ -50,6 +48,8 @@ class BaseSchema(ma.ModelSchema):
class UserSchema(BaseSchema): class UserSchema(BaseSchema):
permissions = fields.Method("user_permissions", dump_only=True) permissions = fields.Method("user_permissions", dump_only=True)
password_changed_at = field_for(models.User, 'password_changed_at', format='%Y-%m-%d %H:%M:%S.%f')
created_at = field_for(models.User, 'created_at', format='%Y-%m-%d %H:%M:%S.%f')
def user_permissions(self, usr): def user_permissions(self, usr):
retval = {} retval = {}
@@ -95,18 +95,6 @@ class JobSchema(BaseSchema):
model = models.Job model = models.Job
# TODO: Remove this schema once the admin app has stopped using the /user/<user_id>code endpoint
class OldRequestVerifyCodeSchema(ma.Schema):
code_type = fields.Str(required=True)
to = fields.Str(required=False)
@validates('code_type')
def validate_code_type(self, code):
if code not in models.VERIFY_CODE_TYPES:
raise ValidationError('Invalid code type')
class RequestVerifyCodeSchema(ma.Schema): class RequestVerifyCodeSchema(ma.Schema):
to = fields.Str(required=False) to = fields.Str(required=False)
@@ -142,8 +130,8 @@ class EmailNotificationSchema(NotificationSchema):
def validate_to(self, value): def validate_to(self, value):
try: try:
validate_email_address(value) validate_email_address(value)
except InvalidEmailError: except InvalidEmailError as e:
raise ValidationError('Invalid email') raise ValidationError(e.message)
class SmsTemplateNotificationSchema(SmsNotificationSchema): class SmsTemplateNotificationSchema(SmsNotificationSchema):
@@ -180,8 +168,8 @@ class InvitedUserSchema(BaseSchema):
def validate_to(self, value): def validate_to(self, value):
try: try:
validate_email_address(value) validate_email_address(value)
except InvalidEmailError: except InvalidEmailError as e:
raise ValidationError('Invalid email') raise ValidationError(e.message)
class PermissionSchema(BaseSchema): class PermissionSchema(BaseSchema):
@@ -201,6 +189,16 @@ class PermissionSchema(BaseSchema):
exclude = ("created_at",) exclude = ("created_at",)
class EmailDataSchema(ma.Schema):
email = fields.Str(required=False)
@validates('email')
def validate_email(self, value):
try:
validate_email_address(value)
except InvalidEmailError as e:
raise ValidationError(e.message)
user_schema = UserSchema() user_schema = UserSchema()
user_schema_load_json = UserSchema(load_json=True) user_schema_load_json = UserSchema(load_json=True)
service_schema = ServiceSchema() service_schema = ServiceSchema()
@@ -211,7 +209,6 @@ api_key_schema = ApiKeySchema()
api_key_schema_load_json = ApiKeySchema(load_json=True) api_key_schema_load_json = ApiKeySchema(load_json=True)
job_schema = JobSchema() job_schema = JobSchema()
job_schema_load_json = JobSchema(load_json=True) job_schema_load_json = JobSchema(load_json=True)
old_request_verify_code_schema = OldRequestVerifyCodeSchema()
request_verify_code_schema = RequestVerifyCodeSchema() request_verify_code_schema = RequestVerifyCodeSchema()
sms_admin_notification_schema = SmsAdminNotificationSchema() sms_admin_notification_schema = SmsAdminNotificationSchema()
sms_template_notification_schema = SmsTemplateNotificationSchema() sms_template_notification_schema = SmsTemplateNotificationSchema()
@@ -222,4 +219,5 @@ notification_status_schema = NotificationStatusSchema()
notification_status_schema_load_json = NotificationStatusSchema(load_json=True) notification_status_schema_load_json = NotificationStatusSchema(load_json=True)
invited_user_schema = InvitedUserSchema() invited_user_schema = InvitedUserSchema()
permission_schema = PermissionSchema() permission_schema = PermissionSchema()
email_data_request_schema = EmailDataSchema()
notifications_statistics_schema = NotificationsStatisticsSchema() notifications_statistics_schema = NotificationsStatisticsSchema()

View File

@@ -1,5 +1,5 @@
from datetime import datetime from datetime import datetime
from flask import (jsonify, request, abort, Blueprint) from flask import (jsonify, request, abort, Blueprint, current_app)
from app import encryption from app import encryption
from app.dao.users_dao import ( from app.dao.users_dao import (
@@ -17,14 +17,14 @@ from app.dao.permissions_dao import permission_dao
from app.dao.services_dao import dao_fetch_service_by_id from app.dao.services_dao import dao_fetch_service_by_id
from app.schemas import ( from app.schemas import (
old_request_verify_code_schema, email_data_request_schema,
user_schema, user_schema,
request_verify_code_schema, request_verify_code_schema,
user_schema_load_json, user_schema_load_json,
permission_schema permission_schema
) )
from app.celery.tasks import (send_sms_code, send_email_code) from app.celery.tasks import (send_sms_code, send_email_code, email_reset_password)
from app.errors import register_errors from app.errors import register_errors
user = Blueprint('user', __name__) user = Blueprint('user', __name__)
@@ -49,7 +49,7 @@ def create_user():
def update_user(user_id): def update_user(user_id):
user_to_update = get_model_users(user_id=user_id) user_to_update = get_model_users(user_id=user_id)
if not user_to_update: if not user_to_update:
return jsonify(result="error", message="User not found"), 404 return _user_not_found(user_id)
req_json = request.get_json() req_json = request.get_json()
update_dct, errors = user_schema_load_json.load(req_json) update_dct, errors = user_schema_load_json.load(req_json)
@@ -118,7 +118,7 @@ def send_user_sms_code(user_id):
user_to_send_to = get_model_users(user_id=user_id) user_to_send_to = get_model_users(user_id=user_id)
if not user_to_send_to: if not user_to_send_to:
return jsonify(result="error", message="No user found"), 404 return _user_not_found(user_id)
verify_code, errors = request_verify_code_schema.load(request.get_json()) verify_code, errors = request_verify_code_schema.load(request.get_json())
if errors: if errors:
@@ -140,7 +140,7 @@ def send_user_sms_code(user_id):
def send_user_email_code(user_id): def send_user_email_code(user_id):
user_to_send_to = get_model_users(user_id=user_id) user_to_send_to = get_model_users(user_id=user_id)
if not user_to_send_to: if not user_to_send_to:
return jsonify(result="error", message="No user found"), 404 return _user_not_found(user_id)
verify_code, errors = request_verify_code_schema.load(request.get_json()) verify_code, errors = request_verify_code_schema.load(request.get_json())
if errors: if errors:
@@ -174,7 +174,7 @@ def set_permissions(user_id, service_id):
# who is making this request has permission to make the request. # who is making this request has permission to make the request.
user = get_model_users(user_id=user_id) user = get_model_users(user_id=user_id)
if not user: if not user:
abort(404, 'User not found for id: {}'.format(user_id)) _user_not_found(user_id)
service = dao_fetch_service_by_id(service_id=service_id) service = dao_fetch_service_by_id(service_id=service_id)
if not service: if not service:
abort(404, 'Service not found for id: {}'.format(service_id)) abort(404, 'Service not found for id: {}'.format(service_id))
@@ -193,9 +193,45 @@ def get_by_email():
email = request.args.get('email') email = request.args.get('email')
if not email: if not email:
return jsonify(result="error", message="invalid request"), 400 return jsonify(result="error", message="invalid request"), 400
user = get_user_by_email(email) fetched_user = get_user_by_email(email)
if not user: if not fetched_user:
return jsonify(result="error", message="not found"), 404 return _user_not_found_for_email()
result = user_schema.dump(user) result = user_schema.dump(fetched_user)
return jsonify(data=result.data) return jsonify(data=result.data)
@user.route('/reset-password', methods=['POST'])
def send_user_reset_password():
email, errors = email_data_request_schema.load(request.get_json())
if errors:
return jsonify(result="error", message=errors), 400
user_to_send_to = get_user_by_email(email['email'])
if not user_to_send_to:
return _user_not_found_for_email()
reset_password_message = {'to': user_to_send_to.email_address,
'name': user_to_send_to.name,
'reset_password_url': _create_reset_password_url(user_to_send_to.email_address)}
email_reset_password.apply_async([encryption.encrypt(reset_password_message)], queue='email-reset-password')
return jsonify({}), 204
def _user_not_found(user_id):
return abort(404, 'User not found for id: {}'.format(user_id))
def _user_not_found_for_email():
return abort(404, 'User not found for email address')
def _create_reset_password_url(email):
from utils.url_safe_token import generate_token
import json
data = json.dumps({'email': email, 'created_at': str(datetime.now())})
token = generate_token(data, current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT'])
return current_app.config['ADMIN_BASE_URL'] + '/new-password/' + token

View File

@@ -46,6 +46,7 @@ class Config(object):
Queue('email', Exchange('default'), routing_key='email'), Queue('email', Exchange('default'), routing_key='email'),
Queue('sms-code', Exchange('default'), routing_key='sms-code'), Queue('sms-code', Exchange('default'), routing_key='sms-code'),
Queue('email-code', Exchange('default'), routing_key='email-code'), Queue('email-code', Exchange('default'), routing_key='email-code'),
Queue('email-reset-password', Exchange('default'), routing_key='email-reset-password'),
Queue('process-job', Exchange('default'), routing_key='process-job'), Queue('process-job', Exchange('default'), routing_key='process-job'),
Queue('bulk-sms', Exchange('default'), routing_key='bulk-sms'), Queue('bulk-sms', Exchange('default'), routing_key='bulk-sms'),
Queue('bulk-email', Exchange('default'), routing_key='bulk-email'), Queue('bulk-email', Exchange('default'), routing_key='bulk-email'),

View File

@@ -21,4 +21,4 @@ monotonic==0.3
git+https://github.com/alphagov/notifications-python-client.git@0.2.6#egg=notifications-python-client==0.2.6 git+https://github.com/alphagov/notifications-python-client.git@0.2.6#egg=notifications-python-client==0.2.6
git+https://github.com/alphagov/notifications-utils.git@2.0.0#egg=notifications-utils==2.0.0 git+https://github.com/alphagov/notifications-utils.git@2.0.1#egg=notifications-utils==2.0.1

View File

@@ -1,11 +1,11 @@
phone number phone number,name
+441234123121 +441234123121,chris
+441234123122 +441234123122,chris
+441234123123 +441234123123,chris
+441234123124 +441234123124,chris
+441234123125 +441234123125,chris
+441234123126 +441234123126,chris
+441234123127 +441234123127,chris
+441234123128 +441234123128,chris
+441234123129 +441234123129,chris
+441234123120 +441234123120,chris
1 phone number name
2 +441234123121 chris
3 +441234123122 chris
4 +441234123123 chris
5 +441234123124 chris
6 +441234123125 chris
7 +441234123126 chris
8 +441234123127 chris
9 +441234123128 chris
10 +441234123129 chris
11 +441234123120 chris

View File

@@ -1,2 +1,2 @@
phone number phone number, ignore this column
+441234123123 +441234123123, nope
1 phone number ignore this column
2 +441234123123 nope

View File

@@ -1,7 +1,13 @@
import uuid import uuid
import pytest import pytest
from flask import current_app from flask import current_app
from app.celery.tasks import (send_sms, send_sms_code, send_email_code, send_email, process_job, email_invited_user) from app.celery.tasks import (send_sms,
send_sms_code,
send_email_code,
send_email,
process_job,
email_invited_user,
email_reset_password)
from app import (firetext_client, aws_ses_client, encryption, DATETIME_FORMAT) from app import (firetext_client, aws_ses_client, encryption, DATETIME_FORMAT)
from app.clients.email.aws_ses import AwsSesClientException from app.clients.email.aws_ses import AwsSesClientException
from app.clients.sms.firetext import FiretextClientException from app.clients.sms.firetext import FiretextClientException
@@ -33,6 +39,8 @@ def test_should_process_sms_job(sample_job, mocker):
process_job(sample_job.id) process_job(sample_job.id)
s3.get_job_from_s3.assert_called_once_with(sample_job.bucket_name, sample_job.id) s3.get_job_from_s3.assert_called_once_with(sample_job.bucket_name, sample_job.id)
assert encryption.encrypt.call_args[0][0]['to'] == '+441234123123'
assert encryption.encrypt.call_args[0][0]['personalisation'] == {}
tasks.send_sms.apply_async.assert_called_once_with( tasks.send_sms.apply_async.assert_called_once_with(
(str(sample_job.service_id), (str(sample_job.service_id),
"uuid", "uuid",
@@ -160,7 +168,7 @@ def test_should_not_create_send_task_for_empty_file(sample_job, mocker):
@freeze_time("2016-01-01 11:09:00.061258") @freeze_time("2016-01-01 11:09:00.061258")
def test_should_process_email_job(sample_email_job, mocker): def test_should_process_email_job(sample_email_job, sample_template, mocker):
mocker.patch('app.celery.tasks.s3.get_job_from_s3', return_value=load_example_csv('email')) mocker.patch('app.celery.tasks.s3.get_job_from_s3', return_value=load_example_csv('email'))
mocker.patch('app.celery.tasks.send_email.apply_async') mocker.patch('app.celery.tasks.send_email.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted") mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
@@ -169,6 +177,8 @@ def test_should_process_email_job(sample_email_job, mocker):
process_job(sample_email_job.id) process_job(sample_email_job.id)
s3.get_job_from_s3.assert_called_once_with(sample_email_job.bucket_name, sample_email_job.id) s3.get_job_from_s3.assert_called_once_with(sample_email_job.bucket_name, sample_email_job.id)
assert encryption.encrypt.call_args[0][0]['to'] == 'test@test.com'
assert encryption.encrypt.call_args[0][0]['personalisation'] == {}
tasks.send_email.apply_async.assert_called_once_with( tasks.send_email.apply_async.assert_called_once_with(
(str(sample_email_job.service_id), (str(sample_email_job.service_id),
"uuid", "uuid",
@@ -182,17 +192,22 @@ def test_should_process_email_job(sample_email_job, mocker):
assert job.status == 'finished' assert job.status == 'finished'
def test_should_process_all_sms_job(sample_job, mocker): def test_should_process_all_sms_job(sample_job, sample_job_with_placeholdered_template, mocker):
mocker.patch('app.celery.tasks.s3.get_job_from_s3', return_value=load_example_csv('multiple_sms')) mocker.patch('app.celery.tasks.s3.get_job_from_s3', return_value=load_example_csv('multiple_sms'))
mocker.patch('app.celery.tasks.send_sms.apply_async') mocker.patch('app.celery.tasks.send_sms.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted") mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
mocker.patch('app.celery.tasks.create_uuid', return_value="uuid") mocker.patch('app.celery.tasks.create_uuid', return_value="uuid")
process_job(sample_job.id) process_job(sample_job_with_placeholdered_template.id)
s3.get_job_from_s3.assert_called_once_with(sample_job.bucket_name, sample_job.id) s3.get_job_from_s3.assert_called_once_with(
sample_job_with_placeholdered_template.bucket_name,
sample_job_with_placeholdered_template.id
)
assert encryption.encrypt.call_args[0][0]['to'] == '+441234123120'
assert encryption.encrypt.call_args[0][0]['personalisation'] == {'name': 'chris'}
tasks.send_sms.apply_async.call_count == 10 tasks.send_sms.apply_async.call_count == 10
job = jobs_dao.dao_get_job_by_id(sample_job.id) job = jobs_dao.dao_get_job_by_id(sample_job_with_placeholdered_template.id)
assert job.status == 'finished' assert job.status == 'finished'
@@ -610,3 +625,22 @@ def test_email_invited_user_should_send_email(notify_api, mocker):
invitation['to'], invitation['to'],
expected_subject, expected_subject,
expected_content) expected_content)
def test_email_reset_password_should_send_email(notify_api, mocker):
with notify_api.test_request_context():
reset_password_message = {'to': 'someone@it.gov.uk',
'name': 'Some One',
'reset_password_url': 'bah'}
mocker.patch('app.aws_ses_client.send_email')
mocker.patch('app.encryption.decrypt', return_value=reset_password_message)
encrypted_message = encryption.encrypt(reset_password_message)
email_reset_password(encrypted_message)
message = tasks.password_reset_message(reset_password_message['name'],
reset_password_message['reset_password_url'])
aws_ses_client.send_email(current_app.config['VERIFY_CODE_FROM_EMAIL_ADDRESS'],
reset_password_message['to'],
"Reset password for GOV.UK Notify",
message)

View File

@@ -221,6 +221,20 @@ def sample_job(notify_db,
return job return job
@pytest.fixture(scope='function')
def sample_job_with_placeholdered_template(
notify_db,
notify_db_session,
service=None
):
return sample_job(
notify_db,
notify_db_session,
service=service,
template=sample_template_with_placeholders(notify_db, notify_db_session)
)
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def sample_email_job(notify_db, def sample_email_job(notify_db,
notify_db_session, notify_db_session,

View File

@@ -90,7 +90,7 @@ def test_create_invited_user_invalid_email(notify_api, sample_service, mocker):
assert response.status_code == 400 assert response.status_code == 400
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'] == {'email_address': ['Invalid email']} assert json_resp['message'] == {'email_address': ['Not a valid email address']}
app.celery.tasks.email_invited_user.apply_async.assert_not_called() app.celery.tasks.email_invited_user.apply_async.assert_not_called()

View File

@@ -578,7 +578,7 @@ def test_should_reject_email_notification_with_bad_email(notify_api, sample_emai
app.celery.tasks.send_email.apply_async.assert_not_called() app.celery.tasks.send_email.apply_async.assert_not_called()
assert response.status_code == 400 assert response.status_code == 400
assert data['result'] == 'error' assert data['result'] == 'error'
assert data['message']['to'][0] == 'Invalid email' assert data['message']['to'][0] == 'Not a valid email address'
def test_should_reject_email_notification_with_template_id_that_cant_be_found( def test_should_reject_email_notification_with_template_id_that_cant_be_found(

View File

@@ -1,10 +1,12 @@
import json import json
import uuid
from flask import url_for from flask import url_for
import app
from app.models import (User, Permission, MANAGE_SETTINGS, MANAGE_TEMPLATES) from app.models import (User, Permission, MANAGE_SETTINGS, MANAGE_TEMPLATES)
from app.dao.permissions_dao import default_service_permissions from app.dao.permissions_dao import default_service_permissions
from app import db from app import db, encryption
from tests import create_authorization_header from tests import create_authorization_header
@@ -256,7 +258,7 @@ def test_put_user_not_exists(notify_api, notify_db, notify_db_session, sample_us
user = User.query.filter_by(id=sample_user.id).first() user = User.query.filter_by(id=sample_user.id).first()
json_resp = json.loads(resp.get_data(as_text=True)) json_resp = json.loads(resp.get_data(as_text=True))
assert json_resp['result'] == "error" assert json_resp['result'] == "error"
assert json_resp['message'] == "User not found" assert json_resp['message'] == "User not found for id: {}".format("9999")
assert user == sample_user assert user == sample_user
assert user.email_address != new_email assert user.email_address != new_email
@@ -284,7 +286,7 @@ def test_get_user_by_email(notify_api, notify_db, notify_db_session, sample_serv
assert sorted(expected_permissions) == sorted(fetched['permissions'][str(sample_service.id)]) assert sorted(expected_permissions) == sorted(fetched['permissions'][str(sample_service.id)])
def test_get_user_by_email_not_found_returns_400(notify_api, def test_get_user_by_email_not_found_returns_404(notify_api,
notify_db, notify_db,
notify_db_session, notify_db_session,
sample_user): sample_user):
@@ -297,7 +299,7 @@ def test_get_user_by_email_not_found_returns_400(notify_api,
assert resp.status_code == 404 assert resp.status_code == 404
json_resp = json.loads(resp.get_data(as_text=True)) json_resp = json.loads(resp.get_data(as_text=True))
assert json_resp['result'] == 'error' assert json_resp['result'] == 'error'
assert json_resp['message'] == 'not found' assert json_resp['message'] == 'User not found for email address'
def test_get_user_by_email_bad_url_returns_404(notify_api, def test_get_user_by_email_bad_url_returns_404(notify_api,
@@ -426,3 +428,64 @@ def test_set_user_permissions_remove_old(notify_api,
query = Permission.query.filter_by(user=sample_user) query = Permission.query.filter_by(user=sample_user)
assert query.count() == 1 assert query.count() == 1
assert query.first().permission == MANAGE_SETTINGS assert query.first().permission == MANAGE_SETTINGS
def test_send_user_reset_password_should_send_reset_password_link(notify_api,
sample_user,
mocker,
mock_encryption):
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch('app.celery.tasks.email_reset_password.apply_async')
data = json.dumps({'email': sample_user.email_address})
auth_header = create_authorization_header(
path=url_for('user.send_user_reset_password'),
method='POST',
request_body=data)
resp = client.post(
url_for('user.send_user_reset_password'),
data=data,
headers=[('Content-Type', 'application/json'), auth_header])
assert resp.status_code == 204
app.celery.tasks.email_reset_password.apply_async.assert_called_once_with(['something_encrypted'],
queue='email-reset-password')
def test_send_user_reset_password_should_return_400_when_user_doesnot_exist(notify_api,
mocker):
with notify_api.test_request_context():
with notify_api.test_client() as client:
bad_email_address = 'bad@email.gov.uk'
data = json.dumps({'email': bad_email_address})
auth_header = create_authorization_header(
path=url_for('user.send_user_reset_password'),
method='POST',
request_body=data)
resp = client.post(
url_for('user.send_user_reset_password'),
data=data,
headers=[('Content-Type', 'application/json'), auth_header])
assert resp.status_code == 404
assert json.loads(resp.get_data(as_text=True))['message'] == 'User not found for email address'
def test_send_user_reset_password_should_return_400_when_data_is_not_email_address(notify_api, mocker):
with notify_api.test_request_context():
with notify_api.test_client() as client:
bad_email_address = 'bad.email.gov.uk'
data = json.dumps({'email': bad_email_address})
auth_header = create_authorization_header(
path=url_for('user.send_user_reset_password'),
method='POST',
request_body=data)
resp = client.post(
url_for('user.send_user_reset_password'),
data=data,
headers=[('Content-Type', 'application/json'), auth_header])
assert resp.status_code == 400
assert json.loads(resp.get_data(as_text=True))['message'] == {'email': ['Not a valid email address']}

View File

@@ -10,8 +10,6 @@ from freezegun import freeze_time
def test_user_verify_code_sms(notify_api, def test_user_verify_code_sms(notify_api,
notify_db,
notify_db_session,
sample_sms_code): sample_sms_code):
""" """
Tests POST endpoint '/<user_id>/verify/code' Tests POST endpoint '/<user_id>/verify/code'
@@ -35,8 +33,6 @@ def test_user_verify_code_sms(notify_api,
def test_user_verify_code_sms_missing_code(notify_api, def test_user_verify_code_sms_missing_code(notify_api,
notify_db,
notify_db_session,
sample_sms_code): sample_sms_code):
""" """
Tests POST endpoint '/<user_id>/verify/code' Tests POST endpoint '/<user_id>/verify/code'
@@ -59,8 +55,6 @@ def test_user_verify_code_sms_missing_code(notify_api,
@moto.mock_sqs @moto.mock_sqs
def test_user_verify_code_email(notify_api, def test_user_verify_code_email(notify_api,
notify_db,
notify_db_session,
sqs_client_conn, sqs_client_conn,
sample_email_code): sample_email_code):
""" """
@@ -85,8 +79,6 @@ def test_user_verify_code_email(notify_api,
def test_user_verify_code_email_bad_code(notify_api, def test_user_verify_code_email_bad_code(notify_api,
notify_db,
notify_db_session,
sample_email_code): sample_email_code):
""" """
Tests POST endpoint '/<user_id>/verify/code' Tests POST endpoint '/<user_id>/verify/code'
@@ -110,8 +102,6 @@ def test_user_verify_code_email_bad_code(notify_api,
def test_user_verify_code_email_expired_code(notify_api, def test_user_verify_code_email_expired_code(notify_api,
notify_db,
notify_db_session,
sample_email_code): sample_email_code):
""" """
Tests POST endpoint '/<user_id>/verify/code' Tests POST endpoint '/<user_id>/verify/code'
@@ -162,8 +152,6 @@ def test_user_verify_password(notify_api,
def test_user_verify_password_invalid_password(notify_api, def test_user_verify_password_invalid_password(notify_api,
notify_db,
notify_db_session,
sample_user): sample_user):
""" """
Tests POST endpoint '/<user_id>/verify/password' invalid endpoint. Tests POST endpoint '/<user_id>/verify/password' invalid endpoint.
@@ -189,8 +177,6 @@ def test_user_verify_password_invalid_password(notify_api,
def test_user_verify_password_valid_password_resets_failed_logins(notify_api, def test_user_verify_password_valid_password_resets_failed_logins(notify_api,
notify_db,
notify_db_session,
sample_user): sample_user):
with notify_api.test_request_context(): with notify_api.test_request_context():
with notify_api.test_client() as client: with notify_api.test_client() as client:
@@ -227,8 +213,6 @@ def test_user_verify_password_valid_password_resets_failed_logins(notify_api,
def test_user_verify_password_missing_password(notify_api, def test_user_verify_password_missing_password(notify_api,
notify_db,
notify_db_session,
sample_user): sample_user):
""" """
Tests POST endpoint '/<user_id>/verify/password' missing password. Tests POST endpoint '/<user_id>/verify/password' missing password.
@@ -314,7 +298,7 @@ def test_send_sms_code_returns_404_for_bad_input_data(notify_api, notify_db, not
data=data, data=data,
headers=[('Content-Type', 'application/json'), auth_header]) headers=[('Content-Type', 'application/json'), auth_header])
assert resp.status_code == 404 assert resp.status_code == 404
assert json.loads(resp.get_data(as_text=True))['message'] == 'No user found' assert json.loads(resp.get_data(as_text=True))['message'] == 'User not found for id: {}'.format(int(uuid_))
def test_send_user_email_code(notify_api, def test_send_user_email_code(notify_api,
@@ -356,4 +340,4 @@ def test_send_user_email_code_returns_404_for_when_user_does_not_exist(notify_ap
data=data, data=data,
headers=[('Content-Type', 'application/json'), auth_header]) headers=[('Content-Type', 'application/json'), auth_header])
assert resp.status_code == 404 assert resp.status_code == 404
assert json.loads(resp.get_data(as_text=True))['message'] == 'No user found' assert json.loads(resp.get_data(as_text=True))['message'] == 'User not found for id: {}'.format(1)