Merge branch 'master' of github.com:alphagov/notifications-admin into api-keys-flow

Conflicts:
	tests/app/main/views/test_api_keys.py
This commit is contained in:
Rebecca Law
2016-01-21 12:31:28 +00:00
30 changed files with 419 additions and 316 deletions

View File

@@ -1,3 +1,3 @@
$(() => GOVUK.modules.start());
$(() => new GOVUK.SelectionButtons('.block-label input'));
$(() => new GOVUK.SelectionButtons('.block-label input, .sms-message-option input'));

View File

@@ -1,53 +1,63 @@
.sms-message {
position: relative;
%sms-message-wrapper,
.sms-message-wrapper {
width: 100%;
box-sizing: border-box;
padding: $gutter/2;
background: $panel-colour;
border: 1px solid $panel-colour;
border-radius: 5px;
white-space: normal;
margin: 0 0 $gutter 0;
}
.sms-message-wrapper-with-radio {
@extend %sms-message-wrapper;
padding-left: 45px;
cursor: pointer;
}
.sms-message-recipient {
@include copy-19;
color: $secondary-text-colour;
margin: -$gutter-half 0 $gutter 0;
}
.sms-message-name {
@include bold-19;
margin: 30px 0 10px 0;
}
.sms-message-picker {
display: block;
margin: 7px 0 0 0;
position: absolute;
left: 15px;
top: 50%;
z-index: 50;
}
label.sms-message-option {
display: block;
position: relative;
&.selected {
.sms-message-wrapper-with-radio {
background: $white;
border: 1px solid $text-colour;
}
&:before {
content: '';
position: absolute;
z-index: 10;
bottom: -12px;
right: 5px;
width: 20px;
height: 25px;
border-radius: 100%;
background: $panel-colour;
}
&:after {
content: '';
position: absolute;
z-index: 20;
bottom: -15px;
right: -5px;
border-radius: 100%;
width: 20px;
height: 20px;
background: $white;
}
&.focused {
&-wrapper {
width: 100%;
display: inline-block;
box-sizing: border-box;
position: relative;
z-index: 30;
padding: $gutter/2;
background: $panel-colour;
border-radius: 5px;
white-space: normal;
}
outline: none;
&-recipient {
@include copy-19;
color: $secondary-text-colour;
margin: -$gutter-half 0 $gutter 0;
}
.sms-message-wrapper-with-radio {
box-shadow: 0 0 0 3px $yellow;
}
&-name {
@include bold-19;
margin: 50px 0 10px 0;
}
}

View File

@@ -1,33 +0,0 @@
.template-picker {
&-name {
@include bold-19;
padding-left: 5px;
}
&-option {
white-space: nowrap;
clear: both;
&-radio {
padding-right: 10px;
box-sizing: border-box;
vertical-align: top;
width: 33%;
display: inline-block;
}
.block-label {
display: inline-block;
}
.sms-message {
display: inline-block;
vertical-align: top;
max-width: 66%;
}
}
}

View File

@@ -32,7 +32,6 @@
// Specific to this application
@import 'grids';
@import 'components/template-picker';
@import 'components/placeholder';
@import 'components/sms-message';
@import 'components/page-footer';

View File

@@ -6,6 +6,8 @@ from app import db, login_manager
from app.models import User
from app.main.encryption import hashpw
from app import user_api_client
@login_manager.user_loader
def load_user(user_id):
@@ -21,7 +23,7 @@ def insert_user(user):
# TODO Would be better to have a generic get and update for user
# something that replicates the sql functionality.
def get_user_by_id(id):
return User.query.filter_by(id=id).first()
return user_api_client.get_user(id)
def get_all_users():
@@ -38,11 +40,9 @@ def increment_failed_login_count(id):
db.session.commit()
def activate_user(id):
user = get_user_by_id(id)
def activate_user(user):
user.state = 'active'
db.session.add(user)
db.session.commit()
return user_api_client.update_user(user)
def update_email_address(id, email_address):

View File

@@ -1,39 +1,12 @@
templates = [
{
'type': 'sms',
'name': 'Confirmation',
'body': 'Lasting power of attorney: Weve received your application. Applications take between 8 and 10 weeks to process.' # noqa
'name': 'Confirmation with details Jan 2016',
'body': '((name)), weve received your ((thing)). Well contact you again within 1 week.'
},
{
'type': 'sms',
'name': 'Reminder',
'body': 'Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)). Tax your vehicle at www.gov.uk/vehicle-tax' # noqa
},
{
'type': 'sms',
'name': 'Warning',
'body': 'Vehicle tax: Your vehicle tax for ((registration number)) has expired. Tax your vehicle at www.gov.uk/vehicle-tax' # noqa
},
{
'type': 'email',
'name': 'Application alert 06/2016',
'subject': 'Your lasting power of attorney application',
'body': """Dear ((name)),
When youve made your lasting power of attorney (LPA), you need to register it \
with the Office of the Public Guardian (OPG).
You can apply to register your LPA yourself if youre able to make your own decisions.
Your attorney can also register it for you. Youll be told if they do and you can \
object to the registration.
It takes between 8 and 10 weeks to register an LPA if there are no mistakes in the application.
"""
},
{
'type': 'sms',
'name': 'Air quality alert',
'body': 'Air pollution levels will be ((level)) in ((region)) tomorrow.'
},
'name': 'Confirmation Jan 2016',
'body': 'Weve received your payment. Well contact you again within 1 week.'
}
]

View File

@@ -4,14 +4,12 @@ from flask import (
render_template,
redirect,
session,
current_app,
abort
)
from client.errors import HTTPError
from app.main import main
from app.models import User
from app.main.dao import users_dao
from app.main.forms import RegisterUserForm
@@ -27,7 +25,6 @@ def register():
form = RegisterUserForm(users_dao.get_user_by_email)
if form.validate_on_submit():
try:
user = user_api_client.register_user(form.name.data,
form.email_address.data,

View File

@@ -16,6 +16,8 @@ def sign_in():
if form.validate_on_submit():
user = users_dao.get_user_by_email(form.email_address.data)
if user:
# TODO move to user API in next pr to actually do password check as this
# is totally broken now
if not user.is_locked() and user.is_active() and check_hash(form.password.data, user.password):
send_sms_code(user.id, user.mobile_number)
session['user_email'] = user.email_address

View File

@@ -1,11 +1,13 @@
from flask import (
render_template,
redirect,
jsonify,
session,
url_for
url_for,
abort
)
from client.errors import HTTPError
from flask_login import login_user
from app.main import main
@@ -24,9 +26,15 @@ def verify():
verify_codes_dao.use_code_for_user_and_type(user_id=user_id, code_type='email')
verify_codes_dao.use_code_for_user_and_type(user_id=user_id, code_type='sms')
# TODO complete verify and login flow
# users_dao.activate_user(user.id)
# login_user(user)
try:
user = users_dao.get_user_by_id(user_id)
activated_user = users_dao.activate_user(user)
login_user(activated_user)
return redirect(url_for('main.add_service', first='first'))
except HTTPError as e:
if e.status_code == 404:
abort(404)
else:
raise e
return redirect(url_for('.add_service', first='first'))
return render_template('views/verify.html', form=form)

View File

@@ -24,6 +24,17 @@ class UserApiClient(BaseAPIClient):
user_data = self.post("/user", data)
return User(user_data['data'], max_failed_login_count=self.user_max_failed_login_count)
def get_user(self, id):
url = "{}/user/{}".format(self.base_url, id)
user_data = self.get(url)
return User(user_data['data'], max_failed_login_count=self.user_max_failed_login_count)
def update_user(self, user):
data = user.serialize()
url = "{}/user/{}".format(self.base_url, user.id)
user_data = self.put(url, data=data)
return User(user_data['data'], max_failed_login_count=self.user_max_failed_login_count)
class User(object):
@@ -51,24 +62,28 @@ class User(object):
def password_changed_at(self):
return self.fields.get('password_changed_at')
@property
def get_id(self):
return self.id
def is_authenticated(self):
return self.fields.get('is_authenticated')
return True
@property
def is_active(self):
if self.fields.get('state') != 'active':
return False
else:
return True
return self.state == 'active'
@property
def state(self):
return self.fields['state']
@state.setter
def state(self, state):
self.fields['state'] = state
def is_anonymous(self):
return False
@property
def is_locked(self):
if self.fields.get('failed_login_count') < self.max_failed_login_count:
return False
else:
return True
return self.fields.get('failed_login_count') > self.max_failed_login_count
def serialize(self):
return self.fields

View File

@@ -55,8 +55,10 @@
<summary class="dropdown-toggle">
Service name
</summary>
<a href="#">Switch to A N Other service</a>
<a href="{{ url_for('.add_service') }}">Add a new service to GOV.UK Notify</a>
<div>
<a href="#">Switch to A N Other service</a>
<a href="{{ url_for('.add_service') }}">Add a new service to GOV.UK Notify</a>
</div>
</details>
</div>
<div class="column-half management-navigation-account">

View File

@@ -1,4 +1,9 @@
{% macro sms_message(body, recipient=None, name=None, edit_link=None) %}
{% macro sms_message(
body, recipient=None, name=None, edit_link=None, input_name=None, input_index=None
) %}
{% if input_name %}
<label class="sms-message-option" for="{{ input_name }}-{{ input_index }}">
{% endif %}
{% if name %}
<h3 class="sms-message-name">
{% if edit_link %}
@@ -8,14 +13,18 @@
{% endif %}
</h3>
{% endif %}
<div class="sms-message">
<div class="sms-message-wrapper">
{{ body|placeholders }}
</div>
{% if input_name %}
<input class="sms-message-picker" type="radio" id="{{ input_name }}-{{ input_index }}" name="{{ input_name }}" />
{% endif %}
<div class="sms-message-wrapper{% if input_name %}-with-radio{% endif %}">
{{ body|placeholders }}
</div>
{% if recipient %}
<p class="sms-message-recipient">
{{ recipient }}
</p>
{% endif %}
{% if input_name %}
</label>
{% endif %}
{% endmacro %}

View File

@@ -10,35 +10,36 @@
{% block maincolumn_content %}
<form method="POST" enctype="multipart/form-data">
<h1 class="heading-xlarge">Send text messages</h1>
<div class="grid-row">
<div class="column-three-quarters">
<fieldset class='form-group'>
<legend class="heading-medium">1. Choose text message template</legend>
{% for template in message_templates %}
<label class="block-label" for="template-{{loop.index}}">
{{ template.name }}
<input type="radio" name="template" id="template-{{loop.index}}" value="{{ template.name }}" />
</label>
{% endfor %}
</fieldset>
<h1 class="heading-xlarge">Send text messages</h1>
<p>
or <a href="{{ url_for('.add_service_template', service_id=service_id) }}">create a new template</a>
</p>
<fieldset class='form-group'>
<legend class="heading-medium">1. Choose text message template</legend>
{% for template in message_templates %}
{{ sms_message(
template.body, name=template.name, input_name='template', input_index=loop.index
) }}
{% endfor %}
</fieldset>
<h2 class="heading-medium">2. Add recipients</h2>
<h2 class="heading-medium">2. Add recipients</h2>
<p>
Upload a CSV file to add your recipients details.
</p>
<p>
You can also <a href="#">download an example CSV</a>.
</p>
<p>
{{textbox(form.file)}}
</p>
<p>
Upload a CSV file to add your recipients details.
</p>
<p>
You can also <a href="#">download an example CSV</a>.
</p>
<p>
{{textbox(form.file)}}
</p>
{{ page_footer("Continue") }}
{{ page_footer("Continue") }}
</div>
</div>
</form>

View File

@@ -92,7 +92,7 @@
<h2 class="heading-large">SMS message</h2>
<p>Used to show or preview an SMS message.</p>
<p>Used to show, preview or choose an SMS message.</p>
<div class="grid-row">
<div class="column-half">
@@ -102,6 +102,12 @@
"Your vehicle tax for registration number is due on date. Renew online at www.gov.uk/vehicle-tax",
"+44 7700 900 306"
) }}
{{ sms_message(
"Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax",
name="Reminder",
input_name="template",
input_index=1
) }}
</div>
</div>

View File

@@ -11,6 +11,6 @@ Flask-Bcrypt==0.6.2
credstash==1.8.0
boto3==1.2.3
git+https://github.com/alphagov/notifications-python-client.git@0.1.9#egg=notifications-python-client==0.1.9
git+https://github.com/alphagov/notifications-python-client.git@0.2.0#egg=notifications-python-client==0.2.0
git+https://github.com/alphagov/notifications-utils.git@0.0.3#egg=notifications-utils==0.0.3

View File

@@ -6,6 +6,7 @@ from app.models import User
from app.main.dao import users_dao
@pytest.mark.xfail(reason='Tests will be moved to api')
def test_insert_user_should_add_user(db_, db_session):
user = User(name='test insert',
password='somepassword',
@@ -18,6 +19,7 @@ def test_insert_user_should_add_user(db_, db_session):
assert saved_user == user
@pytest.mark.xfail(reason='Tests will be moved to api')
def test_insert_user_with_role_that_does_not_exist_fails(db_, db_session):
user = User(name='role does not exist',
password='somepassword',
@@ -29,6 +31,7 @@ def test_insert_user_with_role_that_does_not_exist_fails(db_, db_session):
assert 'insert or update on table "users" violates foreign key constraint "users_role_id_fkey"' in str(error.value)
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_get_user_by_email(db_, db_session):
user = User(name='test_get_by_email',
password='somepassword',
@@ -41,6 +44,7 @@ def test_get_user_by_email(db_, db_session):
assert retrieved == user
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_get_all_users_returns_all_users(db_, db_session):
user1 = User(name='test one',
password='somepassword',
@@ -66,6 +70,7 @@ def test_get_all_users_returns_all_users(db_, db_session):
assert users == [user1, user2, user3]
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_increment_failed_lockout_count_should_increade_count_by_1(db_, db_session):
user = User(name='cannot remember password',
password='somepassword',
@@ -80,6 +85,7 @@ def test_increment_failed_lockout_count_should_increade_count_by_1(db_, db_sessi
assert users_dao.get_user_by_id(user.id).failed_login_count == 1
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_user_is_locked_if_failed_login_count_is_10_or_greater(db_, db_session):
user = User(name='cannot remember password',
password='somepassword',
@@ -98,6 +104,7 @@ def test_user_is_locked_if_failed_login_count_is_10_or_greater(db_, db_session):
assert saved_user.is_locked() is True
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_user_is_active_is_false_if_state_is_inactive(db_, db_session):
user = User(name='inactive user',
password='somepassword',
@@ -111,25 +118,27 @@ def test_user_is_active_is_false_if_state_is_inactive(db_, db_session):
assert saved_user.is_active() is False
def test_should_update_user_to_active(db_, db_session):
user = User(name='Make user active',
password='somepassword',
email_address='activate@user.gov.uk',
mobile_number='+441234123412',
role_id=1,
state='pending')
users_dao.insert_user(user)
users_dao.activate_user(user.id)
updated_user = users_dao.get_user_by_id(user.id)
assert updated_user.state == 'active'
def test_should_update_user_to_active(mock_activate_user):
from app.notify_client.user_api_client import User
user_data = {'name': 'Make user active',
'password': 'somepassword',
'email_address': 'activate@user.gov.uk',
'mobile_number': '+441234123412',
'state': 'pending'
}
user = User(user_data)
activated_user = users_dao.activate_user(user)
assert activated_user.state == 'active'
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_should_throws_error_when_id_does_not_exist(db_, db_session):
with pytest.raises(AttributeError) as error:
users_dao.activate_user(123)
assert '''object has no attribute 'state''''' in str(error.value)
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_should_update_email_address(db_, db_session):
user = User(name='Update Email',
password='somepassword',
@@ -146,6 +155,7 @@ def test_should_update_email_address(db_, db_session):
assert updated.email_address == 'new_email@testit.gov.uk'
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_should_update_password(db_, db_session):
user = User(name='Update Email',
password='somepassword',
@@ -166,6 +176,7 @@ def test_should_update_password(db_, db_session):
assert updated.password_changed_at > start
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_should_return_list_of_all_email_addresses(db_, db_session):
first = User(name='First Person',
password='somepassword',
@@ -187,6 +198,7 @@ def test_should_return_list_of_all_email_addresses(db_, db_session):
assert expected == [x.email_address for x in email_addresses]
@pytest.mark.xfail(reason='Not implemented yet on api client')
def test_should_update_state_to_request_password_reset(db_, db_session):
user = User(name='Requesting Password Resest',
password='somepassword',

View File

@@ -9,7 +9,8 @@ def test_get_should_render_add_service_template(app_,
db_session,
active_user,
mock_get_service,
mock_get_services):
mock_get_services,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -22,7 +23,8 @@ def test_should_add_service_and_redirect_to_next_page(app_,
db_,
db_session,
mock_create_service,
mock_get_services):
mock_get_services,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
user = User.query.first()
@@ -41,7 +43,8 @@ def test_should_return_form_errors_when_service_name_is_empty(app_,
db_session,
active_user,
mock_get_service,
mock_get_services):
mock_get_services,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -53,7 +56,8 @@ def test_should_return_form_errors_when_service_name_is_empty(app_,
def test_should_return_form_errors_with_duplicate_service_name(app_,
db_,
db_session,
mock_get_services):
mock_get_services,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
user = User.query.first()

View File

@@ -2,7 +2,10 @@ from tests import create_test_user
from flask import url_for
from app.models import User
import pytest
@pytest.mark.xfail(reason='Requires completed move of user dao methods to api methods')
def test_should_show_choose_services_page(app_,
db_,
db_session,

View File

@@ -1,5 +1,5 @@
from app.main.dao import verify_codes_dao, users_dao
from tests import create_test_user
from app.main.dao import verify_codes_dao
from tests import create_test_api_user
from flask import url_for
@@ -7,12 +7,13 @@ def test_should_render_email_code_not_received_template_and_populate_email_addre
db_,
db_session,
mock_send_sms,
mock_send_email):
mock_send_email,
mock_api_user,
mock_user_dao_get_by_email):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_user('pending')
session['user_email'] = user.email_address
session['user_email'] = mock_api_user.email_address
response = client.get(url_for('main.check_and_resend_email_code'))
assert response.status_code == 200
assert 'Check your email address is correct and then resend the confirmation code' \
@@ -24,13 +25,15 @@ def test_should_check_and_resend_email_code_redirect_to_verify(app_,
db_,
db_session,
mock_send_sms,
mock_send_email):
mock_send_email,
mock_api_user,
mock_user_dao_get_by_email,
mock_user_dao_update_email):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_user('pending')
session['user_email'] = user.email_address
verify_codes_dao.add_code(user.id, code='12345', code_type='email')
session['user_email'] = mock_api_user.email_address
verify_codes_dao.add_code(mock_api_user.id, code='12345', code_type='email')
response = client.post(url_for('main.check_and_resend_email_code'),
data={'email_address': 'test@user.gov.uk'})
assert response.status_code == 302
@@ -41,13 +44,14 @@ def test_should_render_text_code_not_received_template(app_,
db_,
db_session,
mock_send_sms,
mock_send_email):
mock_send_email,
mock_api_user,
mock_user_dao_get_by_email):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_user('pending')
session['user_email'] = user.email_address
verify_codes_dao.add_code(user.id, code='12345', code_type='sms')
session['user_email'] = mock_api_user.email_address
verify_codes_dao.add_code(mock_api_user.id, code='12345', code_type='sms')
response = client.get(url_for('main.check_and_resend_text_code'))
assert response.status_code == 200
assert 'Check your mobile phone number is correct and then resend the confirmation code.' \
@@ -59,13 +63,15 @@ def test_should_check_and_redirect_to_verify(app_,
db_,
db_session,
mock_send_sms,
mock_send_email):
mock_send_email,
mock_api_user,
mock_user_dao_get_by_email,
mock_user_dao_update_mobile):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_user('pending')
session['user_email'] = user.email_address
verify_codes_dao.add_code(user.id, code='12345', code_type='sms')
session['user_email'] = mock_api_user.email_address
verify_codes_dao.add_code(mock_api_user.id, code='12345', code_type='sms')
response = client.post(url_for('main.check_and_resend_text_code'),
data={'mobile_number': '+447700900460'})
assert response.status_code == 302
@@ -76,48 +82,51 @@ def test_should_update_email_address_resend_code(app_,
db_,
db_session,
mock_send_sms,
mock_send_email):
mock_send_email,
mock_api_user,
mock_user_dao_get_by_email,
mock_user_dao_update_email):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_user('pending')
session['user_email'] = user.email_address
verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='email')
session['user_email'] = mock_api_user.email_address
verify_codes_dao.add_code(user_id=mock_api_user.id, code='12345', code_type='email')
response = client.post(url_for('main.check_and_resend_email_code'),
data={'email_address': 'new@address.gov.uk'})
assert response.status_code == 302
assert response.location == url_for('main.verify', _external=True)
updated_user = users_dao.get_user_by_id(user.id)
assert updated_user.email_address == 'new@address.gov.uk'
assert mock_api_user.email_address == 'new@address.gov.uk'
def test_should_update_mobile_number_resend_code(app_,
db_,
db_session,
mock_send_sms,
mock_send_email):
mock_send_email,
mock_api_user,
mock_user_dao_get_by_email,
mock_user_dao_update_mobile):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_user('pending')
session['user_email'] = user.email_address
verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms')
session['user_email'] = mock_api_user.email_address
verify_codes_dao.add_code(user_id=mock_api_user.id, code='12345', code_type='sms')
response = client.post(url_for('main.check_and_resend_text_code'),
data={'mobile_number': '+447700900460'})
assert response.status_code == 302
assert response.location == url_for('main.verify', _external=True)
updated_user = users_dao.get_user_by_id(user.id)
assert updated_user.mobile_number == '+447700900460'
assert mock_api_user.mobile_number == '+447700900460'
def test_should_render_verification_code_not_received(app_,
db_,
db_session,
active_user):
active_user,
mock_api_user):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
session['user_email'] = active_user.email_address
session['user_email'] = mock_api_user.email_address
response = client.get(url_for('main.verification_code_not_received'))
assert response.status_code == 200
assert 'Resend verification code' in response.get_data(as_text=True)

View File

@@ -6,7 +6,8 @@ def test_should_show_recent_jobs_on_dashboard(app_,
db_,
db_session,
active_user,
mock_get_service):
mock_get_service,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)

View File

@@ -15,7 +15,10 @@ def test_should_redirect_to_password_reset_sent_and_state_updated(app_,
db_,
db_session,
active_user,
mock_send_email):
mock_send_email,
mock_api_user,
mock_user_dao_get_by_email,
mock_user_dao_password_reset):
with app_.test_request_context():
response = app_.test_client().post(
url_for('.forgot_password'),
@@ -24,4 +27,4 @@ def test_should_redirect_to_password_reset_sent_and_state_updated(app_,
assert (
'You have been sent an email containing a link'
' to reset your password.') in response.get_data(as_text=True)
assert users_dao.get_user_by_id(active_user.id).state == 'request_password_reset'
assert mock_api_user.state == 'request_password_reset'

View File

@@ -3,25 +3,23 @@ from app.models import User
from tests import create_test_user
def test_should_return_list_of_all_jobs(app_, db_, db_session, service_one):
def test_should_return_list_of_all_jobs(app_, db_, db_session, service_one, active_user, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
user = User.query.first()
client.login(user)
client.login(active_user)
response = client.get(url_for('main.view_jobs', service_id=101))
assert response.status_code == 200
assert 'You havent sent any notifications yet' in response.get_data(as_text=True)
def test_should_show_page_for_one_job(app_, db_, db_session, service_one):
def test_should_show_page_for_one_job(app_, db_, db_session, service_one, active_user, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
# TODO filename will be part of job metadata not in session
with client.session_transaction() as s:
s[456] = 'dispatch_20151114.csv'
user = User.query.first()
client.login(user)
client.login(active_user)
response = client.get(url_for('main.view_job', service_id=123, job_id=456))
assert response.status_code == 200
@@ -29,11 +27,10 @@ def test_should_show_page_for_one_job(app_, db_, db_session, service_one):
assert 'Test message 1' in response.get_data(as_text=True)
def test_should_show_page_for_one_notification(app_, db_, db_session, service_one):
def test_should_show_page_for_one_notification(app_, db_, db_session, service_one, active_user, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
user = User.query.first()
client.login(user)
client.login(active_user)
response = client.get(url_for(
'main.view_notification',
service_id=101,

View File

@@ -5,12 +5,13 @@ from app.main.encryption import check_hash
from app.notify_client.sender import generate_token
from tests import create_test_user
import pytest
def test_should_render_new_password_template(app_, db_, db_session):
def test_should_render_new_password_template(app_, db_, db_session, mock_api_user, mock_user_dao_get_new_password):
with app_.test_request_context():
with app_.test_client() as client:
user = create_test_user('request_password_reset')
token = generate_token(user.email_address)
token = generate_token(mock_api_user.email_address)
response = client.get(url_for('.new_password', token=token))
assert response.status_code == 200
assert ' You can now create a new password for your account.' in response.get_data(as_text=True)
@@ -27,10 +28,13 @@ def test_should_render_new_password_template_with_message_of_bad_token(app_, db_
response.get_data(as_text=True)
@pytest.mark.xfail(reason='Password reset not implemented')
def test_should_redirect_to_two_factor_when_password_reset_is_successful(app_,
db_,
db_session,
mock_send_sms):
mock_send_sms,
mock_api_user,
mock_user_dao_get_new_password):
with app_.test_request_context():
with app_.test_client() as client:
user = create_test_user('request_password_reset')

View File

@@ -15,7 +15,7 @@ def test_process_register_creates_new_user(app_,
db_session,
mock_send_sms,
mock_send_email,
mocker):
mock_register_user):
user_data = {
'name': 'Some One Valid',
'email_address': 'someone@example.gov.uk',
@@ -23,8 +23,6 @@ def test_process_register_creates_new_user(app_,
'password': 'validPassword!'
}
mock_user(mocker, user_data)
with app_.test_request_context():
response = app_.test_client().post('/register',
data=user_data)
@@ -67,7 +65,7 @@ def test_should_add_verify_codes_on_session(app_,
db_session,
mock_send_sms,
mock_send_email,
mocker):
mock_register_user):
user_data = {
'name': 'Test Codes',
'email_address': 'test@example.gov.uk',
@@ -75,7 +73,6 @@ def test_should_add_verify_codes_on_session(app_,
'password': 'validPassword!'
}
mock_user(mocker, user_data)
with app_.test_client() as client:
response = client.post('/register',
data=user_data)

View File

@@ -1,7 +1,7 @@
from flask import (url_for, session)
def test_should_show_overview(app_, db_, db_session, active_user, mock_get_service):
def test_should_show_overview(app_, db_, db_session, active_user, mock_get_service, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -16,7 +16,7 @@ def test_should_show_overview(app_, db_, db_session, active_user, mock_get_servi
assert mock_get_service.called
def test_should_show_service_name(app_, db_, db_session, active_user, mock_get_service):
def test_should_show_service_name(app_, db_, db_session, active_user, mock_get_service, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -30,7 +30,8 @@ def test_should_show_service_name(app_, db_, db_session, active_user, mock_get_s
service = mock_get_service.side_effect(service_id)['data']
def test_should_redirect_after_change_service_name(app_, db_, db_session, active_user, mock_get_service):
def test_should_redirect_after_change_service_name(app_, db_, db_session, active_user, mock_get_service,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -49,7 +50,8 @@ def test_should_show_service_name_confirmation(app_,
db_,
db_session,
active_user,
mock_get_service):
mock_get_service,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -68,7 +70,8 @@ def test_should_redirect_after_service_name_confirmation(app_,
db_session,
active_user,
mock_get_service,
mock_update_service):
mock_update_service,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -88,7 +91,7 @@ def test_should_redirect_after_service_name_confirmation(app_,
assert mock_update_service.called
def test_should_show_request_to_go_live(app_, db_, db_session, active_user, mock_get_service):
def test_should_show_request_to_go_live(app_, db_, db_session, active_user, mock_get_service, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -107,7 +110,8 @@ def test_should_redirect_after_request_to_go_live(app_,
db_session,
active_user,
mock_get_service,
mock_update_service):
mock_update_service,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -123,7 +127,7 @@ def test_should_redirect_after_request_to_go_live(app_,
assert mock_update_service.called
def test_should_show_status_page(app_, db_, db_session, active_user, mock_get_service):
def test_should_show_status_page(app_, db_, db_session, active_user, mock_get_service, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -141,7 +145,8 @@ def test_should_show_redirect_after_status_change(app_,
db_,
db_session,
active_user,
mock_get_service):
mock_get_service,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -156,7 +161,7 @@ def test_should_show_redirect_after_status_change(app_,
assert mock_get_service.called
def test_should_show_status_confirmation(app_, db_, db_session, active_user, mock_get_service):
def test_should_show_status_confirmation(app_, db_, db_session, active_user, mock_get_service, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -175,7 +180,8 @@ def test_should_redirect_after_status_confirmation(app_,
db_session,
active_user,
mock_get_service,
mock_update_service):
mock_update_service,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -191,7 +197,7 @@ def test_should_redirect_after_status_confirmation(app_,
assert mock_update_service.called
def test_should_show_delete_page(app_, db_, db_session, active_user, mock_get_service):
def test_should_show_delete_page(app_, db_, db_session, active_user, mock_get_service, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -204,7 +210,8 @@ def test_should_show_delete_page(app_, db_, db_session, active_user, mock_get_se
assert mock_get_service.called
def test_should_show_redirect_after_deleting_service(app_, db_, db_session, active_user, mock_get_service):
def test_should_show_redirect_after_deleting_service(app_, db_, db_session, active_user, mock_get_service,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -218,7 +225,7 @@ def test_should_show_redirect_after_deleting_service(app_, db_, db_session, acti
assert delete_url == response.location
def test_should_show_delete_confirmation(app_, db_, db_session, active_user, mock_get_service):
def test_should_show_delete_confirmation(app_, db_, db_session, active_user, mock_get_service, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -236,7 +243,8 @@ def test_should_redirect_delete_confirmation(app_,
db_session,
active_user,
mock_get_service,
mock_delete_service):
mock_delete_service,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)

View File

@@ -18,7 +18,8 @@ def test_sign_out_user(app_,
db_session,
mock_send_sms,
mock_send_email,
mock_get_service):
mock_get_service,
mock_user_loader):
with app_.test_request_context():
email = 'valid@example.gov.uk'
password = 'val1dPassw0rd!'

View File

@@ -4,7 +4,8 @@ from flask import url_for
import moto
def test_upload_empty_csvfile_returns_to_upload_page(app_, db_, db_session, active_user):
def test_upload_empty_csvfile_returns_to_upload_page(app_, db_, db_session, active_user,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -22,7 +23,8 @@ def test_upload_csvfile_with_invalid_phone_shows_check_page_with_errors(app_,
db_,
db_session,
mocker,
active_user):
active_user,
mock_user_loader):
contents = 'phone\n+44 123\n+44 456'
file_data = (BytesIO(contents.encode('utf-8')), 'invalid.csv')
@@ -47,7 +49,8 @@ def test_upload_csvfile_with_valid_phone_shows_first3_and_last3_numbers(app_,
db_,
db_session,
mocker,
active_user):
active_user,
mock_user_loader):
contents = 'phone\n+44 7700 900981\n+44 7700 900982\n+44 7700 900983\n+44 7700 900984\n+44 7700 900985\n+44 7700 900986\n+44 7700 900987\n+44 7700 900988\n+44 7700 900989' # noqa
@@ -83,7 +86,8 @@ def test_upload_csvfile_with_valid_phone_shows_all_if_6_or_less_numbers(app_,
db_,
db_session,
mocker,
active_user):
active_user,
mock_user_loader):
contents = 'phone\n+44 7700 900981\n+44 7700 900982\n+44 7700 900983\n+44 7700 900984\n+44 7700 900985\n+44 7700 900986' # noqa
@@ -111,7 +115,7 @@ def test_upload_csvfile_with_valid_phone_shows_all_if_6_or_less_numbers(app_,
@moto.mock_s3
def test_should_redirect_to_job(app_, db_, db_session, mocker, active_user):
def test_should_redirect_to_job(app_, db_, db_session, mocker, active_user, mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)

View File

@@ -6,7 +6,8 @@ def test_should_return_list_of_all_templates(app_,
db_,
db_session,
active_user,
mock_get_service_templates):
mock_get_service_templates,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -22,7 +23,8 @@ def test_should_show_page_for_one_templates(app_,
db_,
db_session,
active_user,
mock_get_service_template):
mock_get_service_template,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -43,7 +45,8 @@ def test_should_redirect_when_saving_a_template(app_,
db_session,
active_user,
mock_get_service_template,
mock_update_service_template):
mock_update_service_template,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -75,7 +78,8 @@ def test_should_show_delete_template_page(app_,
db_,
db_session,
active_user,
mock_get_service_template):
mock_get_service_template,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)
@@ -97,7 +101,8 @@ def test_should_redirect_when_deleting_a_template(app_,
db_session,
active_user,
mock_get_service_template,
mock_delete_service_template):
mock_delete_service_template,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
client.login(active_user)

View File

@@ -5,14 +5,13 @@ from tests import create_test_api_user
import pytest
def test_should_return_verify_template(app_, db_, db_session):
def test_should_return_verify_template(app_, db_, db_session, mock_api_user):
with app_.test_request_context():
with app_.test_client() as client:
# TODO this lives here until we work out how to
# reassign the session after it is lost mid register process
with client.session_transaction() as session:
user = create_test_api_user('pending')
session['user_details'] = {'email_address': user.email_address, 'id': user.id}
session['user_details'] = {'email_address': mock_api_user.email_address, 'id': mock_api_user.id}
response = client.get(url_for('main.verify'))
assert response.status_code == 200
assert (
@@ -22,14 +21,17 @@ def test_should_return_verify_template(app_, db_, db_session):
def test_should_redirect_to_add_service_when_code_are_correct(app_,
db_,
db_session):
db_session,
mock_api_user,
mock_user_dao_get_user,
mock_activate_user,
mock_user_loader):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_api_user('pending')
session['user_details'] = {'email_address': user.email_address, 'id': user.id}
verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms')
verify_codes_dao.add_code(user_id=user.id, code='23456', code_type='email')
session['user_details'] = {'email_address': mock_api_user.email_address, 'id': mock_api_user.id}
verify_codes_dao.add_code(user_id=mock_api_user.id, code='12345', code_type='sms')
verify_codes_dao.add_code(user_id=mock_api_user.id, code='23456', code_type='email')
response = client.post(url_for('main.verify'),
data={'sms_code': '12345',
'email_code': '23456'})
@@ -37,31 +39,26 @@ def test_should_redirect_to_add_service_when_code_are_correct(app_,
assert response.location == url_for('main.add_service', first='first', _external=True)
@pytest.mark.xfail(reason='Activation refactor to use api not completed')
def test_should_activate_user_after_verify(app_, db_, db_session):
# def test_should_activate_user_after_verify(app_, db_, db_session, mock_api_user, mock_activate_user):
# with app_.test_request_context():
# with app_.test_client() as client:
# with client.session_transaction() as session:
# session['user_details'] = {'email_address': mock_api_user.email_address, 'id': mock_api_user.id}
# verify_codes_dao.add_code(user_id=mock_api_user.id, code='12345', code_type='sms')
# verify_codes_dao.add_code(user_id=mock_api_user.id, code='23456', code_type='email')
# client.post(url_for('main.verify'),
# data={'sms_code': '12345',
# 'email_code': '23456'})
# assert mock_api_user.state == 'active'
def test_should_return_200_when_codes_are_wrong(app_, db_, db_session, mock_api_user, mock_user_dao_get_user):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_api_user('pending')
session['user_details'] = {'email_address': user.email_address, 'id': user.id}
verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms')
verify_codes_dao.add_code(user_id=user.id, code='23456', code_type='email')
client.post(url_for('main.verify'),
data={'sms_code': '12345',
'email_code': '23456'})
after_verify = users_dao.get_user_by_id(user.id)
assert after_verify.state == 'active'
def test_should_return_200_when_codes_are_wrong(app_, db_, db_session):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_api_user('pending')
session['user_details'] = {'email_address': user.email_address, 'id': user.id}
verify_codes_dao.add_code(user_id=user.id, code='23345', code_type='sms')
verify_codes_dao.add_code(user_id=user.id, code='98456', code_type='email')
session['user_details'] = {'email_address': mock_api_user.email_address, 'id': mock_api_user.id}
verify_codes_dao.add_code(user_id=mock_api_user.id, code='23345', code_type='sms')
verify_codes_dao.add_code(user_id=mock_api_user.id, code='98456', code_type='email')
response = client.post(url_for('main.verify'),
data={'sms_code': '12345',
'email_code': '23456'})
@@ -71,24 +68,23 @@ def test_should_return_200_when_codes_are_wrong(app_, db_, db_session):
assert resp_data.count('Code does not match') == 2
@pytest.mark.xfail(reason='Activation refactor to use api not completed')
def test_should_mark_all_codes_as_used_when_many_codes_exist(app_,
db_,
db_session):
with app_.test_request_context():
with app_.test_client() as client:
with client.session_transaction() as session:
user = create_test_api_user('pending')
session['user_details'] = {'email_address': user.email_address, 'id': user.id}
code1 = verify_codes_dao.add_code(user_id=user.id, code='23345', code_type='sms')
code2 = verify_codes_dao.add_code(user_id=user.id, code='98456', code_type='email')
code3 = verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms')
code4 = verify_codes_dao.add_code(user_id=user.id, code='23412', code_type='email')
response = client.post(url_for('main.verify'),
data={'sms_code': '23345',
'email_code': '23412'})
assert response.status_code == 302
assert verify_codes_dao.get_code_by_id(code1).code_used is True
assert verify_codes_dao.get_code_by_id(code2).code_used is True
assert verify_codes_dao.get_code_by_id(code3).code_used is True
assert verify_codes_dao.get_code_by_id(code4).code_used is True
# def test_should_mark_all_codes_as_used_when_many_codes_exist(app_,
# db_,
# db_session,
# mock_api_user):
# with app_.test_request_context():
# with app_.test_client() as client:
# with client.session_transaction() as session:
# session['user_details'] = {'email_address': mock_api_user.email_address, 'id': mock_api_user.id}
# code1 = verify_codes_dao.add_code(user_id=mock_api_user.id, code='23345', code_type='sms')
# code2 = verify_codes_dao.add_code(user_id=mock_api_user.id, code='98456', code_type='email')
# code3 = verify_codes_dao.add_code(user_id=mock_api_user.id, code='12345', code_type='sms')
# code4 = verify_codes_dao.add_code(user_id=mock_api_user.id, code='23412', code_type='email')
# response = client.post(url_for('main.verify'),
# data={'sms_code': '23345',
# 'email_code': '23412'})
# assert response.status_code == 302
# assert verify_codes_dao.get_code_by_id(code1).code_used is True
# assert verify_codes_dao.get_code_by_id(code2).code_used is True
# assert verify_codes_dao.get_code_by_id(code3).code_used is True
# assert verify_codes_dao.get_code_by_id(code4).code_used is True

View File

@@ -216,12 +216,82 @@ def mock_delete_service_template(mocker):
'app.notifications_api_client.delete_service_template', side_effect=_delete)
def mock_register_user(mocker, user_data):
user_data['id'] = 1
@pytest.fixture(scope='function')
def mock_api_user(mocker):
from app.notify_client.user_api_client import User
user_data = {'id': 1,
'name': 'Test User',
'password': 'somepassword',
'email_address': 'test@user.gov.uk',
'mobile_number': '+441234123412',
'state': 'pending'
}
user = User(user_data)
return user
@pytest.fixture(scope='function')
def mock_register_user(mocker, mock_api_user):
mock_class = mocker.patch('app.user_api_client.register_user')
mock_class.return_value = user
mock_class.return_value = mock_api_user
return mock_class
@pytest.fixture(scope='function')
def mock_user_loader(mocker, mock_api_user):
mock_class = mocker.patch('app.main.dao.users_dao.get_user_by_id')
mock_class.return_value = mock_api_user
return mock_class
@pytest.fixture(scope='function')
def mock_activate_user(mocker, mock_api_user):
def _activate(mock_api_user):
mock_api_user.state = 'active'
return mock_api_user
return mocker.patch('app.user_api_client.update_user', side_effect=_activate)
@pytest.fixture(scope='function')
def mock_user_dao_get_user(mocker):
mock_class = mocker.patch('app.main.dao.users_dao.get_user_by_id')
mock_class.return_value = mock_api_user
return mock_class
@pytest.fixture(scope='function')
def mock_user_dao_get_by_email(mocker, mock_api_user):
mock_class = mocker.patch('app.main.dao.users_dao.get_user_by_email')
mock_class.return_value = mock_api_user
return mock_class
@pytest.fixture(scope='function')
def mock_user_dao_update_email(mocker, mock_api_user):
def _update(id, email_address):
mock_api_user.fields['email_address'] = email_address
return mocker.patch('app.main.dao.users_dao.update_email_address', side_effect=_update)
@pytest.fixture(scope='function')
def mock_user_dao_update_mobile(mocker, mock_api_user):
def _update(id, mobile_number):
mock_api_user.fields['mobile_number'] = mobile_number
return mocker.patch('app.main.dao.users_dao.update_mobile_number', side_effect=_update)
@pytest.fixture(scope='function')
def mock_user_dao_password_reset(mocker, mock_api_user):
def _reset(email):
mock_api_user.state = 'request_password_reset'
return mocker.patch('app.main.dao.users_dao.request_password_reset', side_effect=_reset)
@pytest.fixture(scope='function')
def mock_user_dao_get_new_password(mocker, mock_api_user):
mock_api_user.state = 'request_password_reset'
mock_class = mocker.patch('app.main.dao.users_dao.get_user_by_email')
mock_class.return_value = mock_api_user
return mock_class