Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create user account email class #335

Merged
merged 4 commits into from
Sep 22, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dmutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
import flask_featureflags # noqa


__version__ = '28.3.0'
__version__ = '28.4.0'
1 change: 1 addition & 0 deletions dmutils/email/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@

from .exceptions import EmailError
from .dm_notify import DMNotifyClient
from .user_account_email import UserAccountEmail
40 changes: 40 additions & 0 deletions dmutils/email/user_account_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from flask import current_app, session, abort
from . import DMNotifyClient, generate_token, EmailError
from .helpers import hash_string


class UserAccountEmail():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like this could be a function. Class attributes are essentially used as arguments for the single method.
Do you think this will get extended in the future in a way that would be easier to do with a class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have updated to not be a class, and have imported the external blueprint from the supplier frontend.


def __init__(self, token_data, template_id):
self.role = token_data['role']
self.email_address = token_data['email_address']
self.token = self._generate_create_token(token_data)
self.template_id = template_id

def send_email(self, personalisation):
notify_client = DMNotifyClient(current_app.config['DM_NOTIFY_API_KEY'])

try:
notify_client.send_email(
self.email_address,
template_id=self.template_id,
personalisation=personalisation,
reference='create-user-account-{}'.format(hash_string(self.email_address))
)
session['email_sent_to'] = self.email_address
except EmailError as e:
current_app.logger.error(
"{code}: Create user email for email_hash {email_hash} failed to send. Error: {error}",
extra={
'error': str(e),
'email_hash': hash_string(self.email_address),
'code': '{}create.fail'.format(self.role)
})
abort(503, response="Failed to send user creation email.")

def _generate_create_token(self, token_data):
return generate_token(
token_data,
current_app.config['SHARED_EMAIL_KEY'],
current_app.config['INVITE_EMAIL_SALT']
)
86 changes: 86 additions & 0 deletions tests/email/test_user_account_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import mock
import pytest
from flask import session

from dmutils.config import init_app
from dmutils.email import UserAccountEmail, EmailError
from dmutils.email.tokens import decode_invitation_token


@pytest.yield_fixture
def email_app(app):
init_app(app)
app.config['SHARED_EMAIL_KEY'] = 'shared_email_key'
app.config['INVITE_EMAIL_SALT'] = 'invite_email_salt'
app.config['SECRET_KEY'] = 'secet_key'
app.config['DM_NOTIFY_API_KEY'] = 'dm_notify_api_key'
app.config['NOTIFY_TEMPLATES'] = {'create_user_account': 'this-would-be-the-id-of-the-template'}
yield app


class TestUserAccountEmail():

def test_UserAccountEmail_object_creates_token_on_instantiation(self, email_app):
with email_app.app_context():
token_data = {
'role': 'buyer',
'email_address': '[email protected]'
}

create_user_email = UserAccountEmail(token_data, 'notify-template-id')

assert decode_invitation_token(create_user_email.token) == {
'role': 'buyer',
'email_address': '[email protected]'
}

@mock.patch('dmutils.email.user_account_email.DMNotifyClient')
def test_send_email_correctly_calls_notify_client(self, DMNotifyClient, email_app):
with email_app.test_request_context():
notify_client_mock = mock.Mock()
DMNotifyClient.return_value = notify_client_mock

token_data = {
'role': 'buyer',
'email_address': '[email protected]'
}

create_buyer_email = UserAccountEmail(token_data, 'notify-template-id')
create_buyer_email.send_email({'url': 'http://link.to/create-user'})

notify_client_mock.send_email.assert_called_once_with(
'[email protected]',
template_id='notify-template-id',
personalisation={
'url': 'http://link.to/create-user'
},
reference='create-user-account-KmmJkEa5sLyv7vuxG3xja3S3fnnM6Rgq5EZY0S_kCjE='
)
assert session['email_sent_to'] == '[email protected]'

@mock.patch('dmutils.email.user_account_email.current_app')
@mock.patch('dmutils.email.user_account_email.abort')
@mock.patch('dmutils.email.user_account_email.DMNotifyClient')
def test_abort_with_503_if_send_email_fails_with_EmailError(self, DMNotifyClient, abort, current_app, email_app):
with email_app.test_request_context():
notify_client_mock = mock.Mock()
notify_client_mock.send_email.side_effect = EmailError('OMG!')
DMNotifyClient.return_value = notify_client_mock

token_data = {
'role': 'buyer',
'email_address': '[email protected]'
}

create_buyer_email = UserAccountEmail(token_data, 'notify-template-id')
create_buyer_email.send_email({'url': 'http://link.to/create-user'})

current_app.logger.error.assert_called_once_with(
"{code}: Create user email for email_hash {email_hash} failed to send. Error: {error}",
extra={
'error': 'OMG!',
'email_hash': 'KmmJkEa5sLyv7vuxG3xja3S3fnnM6Rgq5EZY0S_kCjE=',
'code': 'buyercreate.fail'
}
)
abort.assert_called_once_with(503, response="Failed to send user creation email.")