Skip to content

Commit

Permalink
Use UserAccountEmail from utils for supplier contrib invite
Browse files Browse the repository at this point in the history
The mechanism for sending invites to new suppliers user contributors is
very similar to the mechanism for inviting a supplier user during
supplier account creation, so we can reuse the code.

The Notify template ID's for the preview and staging/production versions
of the invite template are added to the config.
  • Loading branch information
Wynndow committed Sep 22, 2017
1 parent 861bf84 commit c3dcb9b
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 104 deletions.
58 changes: 19 additions & 39 deletions app/main/views/login.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from __future__ import absolute_import
import six
from flask_login import current_user
from flask import current_app, flash, redirect, render_template, url_for, abort
from flask import flash, redirect, render_template, url_for, current_app

from dmapiclient.audit import AuditTypes
from dmutils.email import generate_token, send_email
from dmutils.email.exceptions import EmailError
from dmutils.email import UserAccountEmail

from .. import main
from ..forms.auth_forms import EmailAddressForm
from ..helpers import hash_email, login_required
from ..helpers import login_required
from ... import data_api_client


Expand All @@ -36,41 +34,23 @@ def send_invite_user():
form = EmailAddressForm()

if form.validate_on_submit():
token = generate_token(
{
"role": "supplier",
"supplier_id": current_user.supplier_id,
"supplier_name": current_user.supplier_name,
"email_address": form.email_address.data
},
current_app.config['SHARED_EMAIL_KEY'],
current_app.config['INVITE_EMAIL_SALT']
)
url = url_for('external.create_user', encoded_token=token, _external=True)
email_body = render_template(
"emails/invite_user_email.html",
url=url,
user=current_user.name,
supplier=current_user.supplier_name)
token_data = {
"role": "supplier",
"supplier_id": current_user.supplier_id,
"supplier_name": current_user.supplier_name,
"email_address": form.email_address.data
}

try:
send_email(
form.email_address.data,
email_body,
current_app.config['DM_MANDRILL_API_KEY'],
current_app.config['INVITE_EMAIL_SUBJECT'],
current_app.config['INVITE_EMAIL_FROM'],
current_app.config['INVITE_EMAIL_NAME'],
["user-invite"]
)
except EmailError as e:
current_app.logger.error(
"Invitation email failed to send. "
"error {error} supplier_id {supplier_id} email_hash {email_hash}",
extra={'error': six.text_type(e),
'supplier_id': current_user.supplier_id,
'email_hash': hash_email(current_user.email_address)})
abort(503, "Failed to send user invite reset")
user_invite_email = UserAccountEmail(
token_data,
current_app.config['NOTIFY_TEMPLATES']['invite_contributor']
)
invite_link = url_for('external.create_user', encoded_token=user_invite_email.token, _external=True)
user_invite_email.send_email({
'user': current_user.name,
'supplier': current_user.supplier_name,
'url': invite_link
})

data_api_client.create_audit_event(
audit_type=AuditTypes.invite_user,
Expand Down
2 changes: 2 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Config(object):

NOTIFY_TEMPLATES = {
'create_user_account': '1d1e38a6-744a-4d5a-84af-aefccde70a6c',
'invite_contributor': '1cca85e8-d647-46e6-9c0d-6af90b9e69b0'
}

DM_AGREEMENTS_BUCKET = None
Expand Down Expand Up @@ -168,6 +169,7 @@ class Production(Live):

NOTIFY_TEMPLATES = {
'create_user_account': '84f5d812-df9d-4ab8-804a-06f64f5abd30',
'invite_contributor': '5eefe42d-1694-4388-8908-991cdfba0a71'
}

# Check we didn't forget any live template IDs
Expand Down
108 changes: 43 additions & 65 deletions tests/app/main/test_login.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# coding: utf-8
from __future__ import unicode_literals

from flask import current_app

from dmapiclient.audit import AuditTypes
from dmutils.email.exceptions import EmailError

Expand Down Expand Up @@ -52,8 +54,8 @@ def test_should_be_an_error_for_missing_email(self):
assert res.status_code == 400

@mock.patch('app.main.views.login.data_api_client')
@mock.patch('app.main.views.login.send_email')
def test_should_redirect_to_list_users_on_success_invite(self, send_email, data_api_client):
@mock.patch('app.main.views.login.UserAccountEmail')
def test_should_redirect_to_list_users_on_success_invite(self, UserAccountEmail, data_api_client):
with self.app.app_context():
self.login()
res = self.client.post(
Expand All @@ -66,8 +68,8 @@ def test_should_redirect_to_list_users_on_success_invite(self, send_email, data_
assert res.location == 'http://localhost/suppliers/users'

@mock.patch('app.main.views.login.data_api_client')
@mock.patch('app.main.views.login.send_email')
def test_should_strip_whitespace_surrounding_invite_user_email_address_field(self, send_email, data_api_client):
@mock.patch('app.main.views.login.UserAccountEmail')
def test_should_strip_whitespace_around_invite_user_email_address_field(self, UserAccountEmail, data_api_client):
with self.app.app_context():
self.login()
self.client.post(
Expand All @@ -76,46 +78,18 @@ def test_should_strip_whitespace_surrounding_invite_user_email_address_field(sel
'email_address': ' [email protected] '
}
)
send_email.assert_called_once_with(
'[email protected]',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
)

@mock.patch('app.main.views.login.data_api_client')
@mock.patch('app.main.views.login.generate_token')
@mock.patch('app.main.views.login.send_email')
def test_should_call_generate_token_with_correct_params(self, send_email, generate_token, data_api_client):
with self.app.app_context():

self.app.config['SHARED_EMAIL_KEY'] = "KEY"
self.app.config['INVITE_EMAIL_SALT'] = "SALT"

self.login()
res = self.client.post(
'/suppliers/invite-user',
data={
'email_address': '[email protected]'
})
assert res.status_code == 302
generate_token.assert_called_once_with(
UserAccountEmail.assert_called_once_with(
{
"role": "supplier",
"supplier_id": 1234,
"supplier_name": "Supplier NĀme",
"email_address": "[email protected]"
'role': 'supplier',
'supplier_id': mock.ANY,
'supplier_name': mock.ANY,
'email_address': '[email protected]'
},
'KEY',
'SALT'
current_app.config['NOTIFY_TEMPLATES']['invite_contributor']
)

@mock.patch('app.main.views.login.send_email')
@mock.patch('app.main.views.login.generate_token')
def test_should_not_generate_token_or_send_email_if_invalid_email(self, send_email, generate_token):
@mock.patch('app.main.views.login.UserAccountEmail')
def test_should_not_send_email_if_invalid_email(self, UserAccountEmail):
with self.app.app_context():

self.login()
Expand All @@ -125,16 +99,17 @@ def test_should_not_generate_token_or_send_email_if_invalid_email(self, send_ema
'email_address': 'total rubbish'
})
assert res.status_code == 400
assert send_email.called is False
assert generate_token.called is False
assert UserAccountEmail.send_email.called is False

@mock.patch('dmutils.email.user_account_email.DMNotifyClient')
def test_should_be_an_error_if_send_invitation_email_fails(self, DMNotifyClient):
notify_client_mock = mock.Mock()
notify_client_mock.send_email.side_effect = EmailError()
DMNotifyClient.return_value = notify_client_mock

@mock.patch('app.main.views.login.send_email')
def test_should_be_an_error_if_send_invitation_email_fails(self, send_email):
with self.app.app_context():
self.login()

send_email.side_effect = EmailError(Exception('API is down'))

res = self.client.post(
'/suppliers/invite-user',
data={'email_address': '[email protected]', 'name': 'valid'}
Expand All @@ -143,37 +118,40 @@ def test_should_be_an_error_if_send_invitation_email_fails(self, send_email):
assert res.status_code == 503

@mock.patch('app.main.views.login.data_api_client')
@mock.patch('app.main.views.login.send_email')
def test_should_call_send_invitation_email_with_correct_params(self, send_email, data_api_client):
with self.app.app_context():
@mock.patch('app.main.views.login.UserAccountEmail')
def test_should_send_invitation_email_with_correct_params(self, UserAccountEmail, data_api_client):
user_invite_email_mock = mock.Mock()
user_invite_email_mock.token = 'pretty-good-made-up-token'
UserAccountEmail.return_value = user_invite_email_mock

with self.app.app_context():
self.login()

self.app.config['DM_MANDRILL_API_KEY'] = "API KEY"
self.app.config['INVITE_EMAIL_SUBJECT'] = "SUBJECT"
self.app.config['INVITE_EMAIL_FROM'] = "EMAIL FROM"
self.app.config['INVITE_EMAIL_NAME'] = "EMAIL NAME"

res = self.client.post(
'/suppliers/invite-user',
data={'email_address': '[email protected]', 'name': 'valid'}
)

assert res.status_code == 302

send_email.assert_called_once_with(
"[email protected]",
mock.ANY,
"API KEY",
"SUBJECT",
"EMAIL FROM",
"EMAIL NAME",
["user-invite"]
UserAccountEmail.assert_called_once_with(
{
'role': 'supplier',
'supplier_id': 1234,
'supplier_name': 'Supplier NĀme',
'email_address': '[email protected]'
},
current_app.config['NOTIFY_TEMPLATES']['invite_contributor']
)

user_invite_email_mock.send_email.assert_called_once_with({
'user': 'Năme',
'supplier': 'Supplier NĀme',
'url': 'http://localhost/user/create/pretty-good-made-up-token'
})

@mock.patch('app.main.views.login.data_api_client')
@mock.patch('app.main.views.login.send_email')
def test_should_create_audit_event(self, send_email, data_api_client):
@mock.patch('app.main.views.login.UserAccountEmail')
def test_should_create_audit_event(self, UserAccountEmail, data_api_client):
with self.app.app_context():
self.login()

Expand Down

0 comments on commit c3dcb9b

Please sign in to comment.