diff --git a/.travis.yml b/.travis.yml index 520c6dc83..0ed81c30b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ sudo: false language: python python: - '3.4' -addons: - postgresql: '9.3' env: secure: jT9BIioqBMkOdLZhU+WJNdnRJ+06G7qUx4QqEVldp96dJwmWpPEvA0XbitdnQt/WXYkpMlDbgSApvvGj2ZNvdpowRRe5HFX8D2Udhi2g9+cXgKrQxH6zv0evJyQLOjCINW6KtgMCJ5wkYR3qQ4BQawlDt6ecpmeboKTmvs2W8jZ09aV4IKKvdd7BwFon10QVPF5ny10G83unLtKnKgRMjSSLnaEiA78pE/LSUkekK4mhmtl+yfQf60cIuQGcN9NCYIt5PrdYYyMkbUaht9ykwL2C11sp5JYPClI9k6lrlpGJCdL9wbJwejGhR/pEqwJ4tKK8Zv+mngmkbzE6fd5ehuRMnIUAifG4t3p6WbhKwY5pJsdVyPgWcRSPXOJA7yEcAeTAvWcC++6mCIFBeMxt/yQNw02jkFHeNKRh2twTRvr4xWZHq9FsVxTEVz89OOuue3IkkyDNmVusGJ9+AVRIn9Oa+U/r3bDnrs7jz+meSwb82GZUBzFpUe2pe8qeBE572Ay7yHB73VHUgp/2A1qkZ4SnTjTpMbnS5RdXTgwtMkOs5MLZgteCVxFL3sHcr9e/B3UIUnzKUSPXXOjHyDxBwrABWo81V9Vp2IPV7P9Ofv8zroudjQxK5MOcbmiPQF+eEB9L4DvkUBNsGxtJ/nmPp6tmN0Xjo0xXVdZCEVj29Og= before_install: @@ -12,8 +10,6 @@ install: - npm install - npm rebuild node-sass - pip install -r requirements_for_test.txt -before_script: -- psql -c 'create database test_notifications_admin;' -U postgres after_success: - ./scripts/trigger-dependent-build.sh script: diff --git a/app.py b/app.py index dc26c70cc..4a254ef93 100644 --- a/app.py +++ b/app.py @@ -1,15 +1,13 @@ import os from flask.ext.script import Manager, Server from flask_migrate import Migrate, MigrateCommand -from app import create_app, db +from app import create_app application = create_app(os.getenv('NOTIFICATIONS_ADMIN_ENVIRONMENT') or 'development') manager = Manager(application) port = int(os.environ.get('PORT', 6012)) manager.add_command("runserver", Server(host='0.0.0.0', port=port)) -migrate = Migrate(application, db) -manager.add_command('db', MigrateCommand) @manager.command diff --git a/app/__init__.py b/app/__init__.py index 80da769a3..cf2b6b3a9 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -16,7 +16,6 @@ import app.proxy_fix from config import configs from utils import logging -db = SQLAlchemy() login_manager = LoginManager() csrf = CsrfProtect() @@ -31,7 +30,6 @@ def create_app(config_name, config_overrides=None): application.config['NOTIFY_ADMIN_ENVIRONMENT'] = config_name application.config.from_object(configs[config_name]) init_app(application, config_overrides) - db.init_app(application) logging.init_app(application) init_csrf(application) diff --git a/app/main/dao/roles_dao.py b/app/main/dao/roles_dao.py deleted file mode 100644 index a9f6b1027..000000000 --- a/app/main/dao/roles_dao.py +++ /dev/null @@ -1,11 +0,0 @@ -from app import db -from app.models import Roles - - -def insert_role(role): - db.session.add(role) - db.session.commit() - - -def get_role_by_id(id): - return Roles.query.filter_by(id=id).first() diff --git a/app/main/dao/users_dao.py b/app/main/dao/users_dao.py index 2bfed65d5..93f84fba8 100644 --- a/app/main/dao/users_dao.py +++ b/app/main/dao/users_dao.py @@ -2,8 +2,7 @@ from datetime import datetime from sqlalchemy.orm import load_only -from app import db, login_manager -from app.models import User +from app import login_manager from app.main.encryption import hashpw from app import user_api_client @@ -14,12 +13,6 @@ def load_user(user_id): return get_user_by_id(user_id) -def insert_user(user): - user.password = hashpw(user.password) - db.session.add(user) - db.session.commit() - - # 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): @@ -34,8 +27,18 @@ def get_user_by_email(email_address): return user_api_client.get_user_by_email(email_address) -def verify_password(user, password): - return user_api_client.verify_password(user, password) +def verify_password(user_id, password): + return user_api_client.verify_password(user_id, password) + + +def update_user(user): + return user_api_client.update_user(user) + + +def increment_failed_login_count(id): + user = get_user_by_id(id) + user.failed_login_count += 1 + return user_api_client.update_user(user) def activate_user(user): @@ -43,29 +46,20 @@ def activate_user(user): return user_api_client.update_user(user) -def update_email_address(id, email_address): - user = get_user_by_id(id) - user.email_address = email_address - return user_api_client.update_user(user) +def is_email_unique(email_address): + if user_api_client.get_user_by_email(email_address): + return False + return True -def update_mobile_number(id, mobile_number): - user = get_user_by_id(id) - user.mobile_number = mobile_number - db.session.add(user) - db.session.commit() - - -def update_password(user, password): - user.password = hashpw(password) - user.password_changed_at = datetime.now() - user.state = 'active' - db.session.add(user) - db.session.commit() - - -def request_password_reset(email): - user = get_user_by_email(email) +def request_password_reset(user): user.state = 'request_password_reset' - db.session.add(user) - db.session.commit() + user_api_client.update_user(user) + + +def send_verify_code(user_id, code_type, to=None): + return user_api_client.send_verify_code(user_id, code_type) + + +def check_verify_code(user_id, code, code_type): + return user_api_client.check_verify_code(user_id, code, code_type) diff --git a/app/main/dao/verify_codes_dao.py b/app/main/dao/verify_codes_dao.py deleted file mode 100644 index 111753a89..000000000 --- a/app/main/dao/verify_codes_dao.py +++ /dev/null @@ -1,55 +0,0 @@ -from datetime import datetime, timedelta - -from app import db -from app.main.encryption import hashpw -from app.models import VerifyCodes - - -def add_code(user_id, code, code_type): - code = VerifyCodes(user_id=user_id, - code=hashpw(code), - code_type=code_type, - expiry_datetime=datetime.now() + timedelta(hours=1)) - - db.session.add(code) - db.session.commit() - return code - - -def get_codes(user_id, code_type=None): - if not code_type: - return VerifyCodes.query.filter_by(user_id=user_id, code_used=False).all() - return VerifyCodes.query.filter_by(user_id=user_id, code_type=code_type, code_used=False).all() - - -def get_code_by_code(user_id, code, code_type): - return VerifyCodes.query.filter_by(user_id=user_id, code=hashpw(code), code_type=code_type).first() - - -def use_code(id): - verify_code = VerifyCodes.query.get(id) - verify_code.code_used = True - db.session.add(verify_code) - db.session.commit() - - -def use_code_for_user_and_type(user_id, code_type): - codes = VerifyCodes.query.filter_by(user_id=user_id, code_type=code_type, code_used=False).all() - for verify_code in codes: - verify_code.code_used = True - db.session.add(verify_code) - db.session.commit() - - -def get_code_by_id(id): - return VerifyCodes.query.get(id) - - -def add_code_with_expiry(user_id, code, code_type, expiry): - code = VerifyCodes(user_id=user_id, - code=hashpw(code), - code_type=code_type, - expiry_datetime=expiry) - - db.session.add(code) - db.session.commit() diff --git a/app/main/forms.py b/app/main/forms.py index 1edf1ceb9..e7136b43c 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -11,8 +11,7 @@ from wtforms import ( ) from wtforms.validators import DataRequired, Email, Length, Regexp -from app.main.validators import Blacklist, ValidateUserCodes, CsvFileValidator -from app.main.dao import verify_codes_dao +from app.main.validators import Blacklist, CsvFileValidator from app.main.encryption import check_hash @@ -84,16 +83,14 @@ def sms_code(): return StringField('Text message confirmation code', validators=[DataRequired(message='Text message confirmation code can not be empty'), Regexp(regex=verify_code, - message='Text message confirmation code must be 5 digits'), - ValidateUserCodes(code_type='sms')]) + message='Text message confirmation code must be 5 digits')]) def email_code(): verify_code = '^\d{5}$' return StringField("Email confirmation code", validators=[DataRequired(message='Email confirmation code can not be empty'), - Regexp(regex=verify_code, message='Email confirmation code must be 5 digits'), - ValidateUserCodes(code_type='email')]) + Regexp(regex=verify_code, message='Email confirmation code must be 5 digits')]) class LoginForm(Form): @@ -108,8 +105,8 @@ class LoginForm(Form): class RegisterUserForm(Form): - def __init__(self, existing_email_addresses, *args, **kwargs): - self.existing_emails = existing_email_addresses + def __init__(self, unique_email_func, *args, **kwargs): + self.unique_email_func = unique_email_func super(RegisterUserForm, self).__init__(*args, **kwargs) name = StringField('Full name', @@ -120,36 +117,50 @@ class RegisterUserForm(Form): def validate_email_address(self, field): # Validate email address is unique. - if self.existing_emails(field.data): + if not self.unique_email_func(field.data): raise ValidationError('Email address already exists') class TwoFactorForm(Form): - def __init__(self, user_codes, *args, **kwargs): + def __init__(self, validate_code_func, *args, **kwargs): ''' Keyword arguments: - user_codes -- List of user code objects which have the fields - (code_type, expiry_datetime, code) + validate_code_func -- Validates the code with the API. ''' - self.user_codes = user_codes + self.validate_code_func = validate_code_func super(TwoFactorForm, self).__init__(*args, **kwargs) sms_code = sms_code() + def validate_sms_code(self, field): + is_valid, reason = self.validate_code_func(field.data) + if not is_valid: + raise ValidationError(reason) + class VerifyForm(Form): - def __init__(self, user_codes, *args, **kwargs): + def __init__(self, validate_code_func, *args, **kwargs): ''' Keyword arguments: - user_codes -- List of user code objects which have the fields - (code_type, expiry_datetime, code) + validate_code_func -- Validates the code with the API. ''' - self.user_codes = user_codes + self.validate_code_func = validate_code_func super(VerifyForm, self).__init__(*args, **kwargs) sms_code = sms_code() email_code = email_code() + def _validate_code(self, cde, code_type): + is_valid, reason = self.validate_code_func(cde, code_type) + if not is_valid: + raise ValidationError(reason) + + def validate_email_code(self, field): + self._validate_code(field.data, 'email') + + def validate_sms_code(self, field): + self._validate_code(field.data, 'sms') + class EmailNotReceivedForm(Form): email_address = email_address() @@ -186,8 +197,17 @@ class ServiceNameForm(Form): class ConfirmPasswordForm(Form): + + def __init__(self, validate_password_func, *args, **kwargs): + self.validate_password_func = validate_password_func + super(ConfirmPasswordForm, self).__init__(*args, **kwargs) + password = PasswordField(u'Enter password') + def validate_password(self, field): + if not self.validate_password_func(field.data): + raise ValidationError('Invalid password') + class TemplateForm(Form): name = StringField( @@ -201,17 +221,35 @@ class TemplateForm(Form): class ForgotPasswordForm(Form): + + def __init__(self, user_email_exists_func, *args, **kwargs): + self._user_email_exists_func = user_email_exists_func + super(ForgotPasswordForm, self).__init__(*args, **kwargs) + email_address = email_address() + def validate_email_address(self, field): + if not self._user_email_exists_func(field.data): + raise ValidationError('The email is not registered on our system') + class NewPasswordForm(Form): new_password = password() class ChangePasswordForm(Form): + + def __init__(self, validate_password_func, *args, **kwargs): + self.validate_password_func = validate_password_func + super(ChangePasswordForm, self).__init__(*args, **kwargs) + old_password = password('Current password') new_password = password('New password') + def validate_old_password(self, field): + if not self.validate_password_func(field.data): + raise ValidationError('Invalid password') + class CsvUploadForm(Form): file = FileField('File to upload', validators=[DataRequired( @@ -223,20 +261,50 @@ class ChangeNameForm(Form): class ChangeEmailForm(Form): + + def __init__(self, validate_email_func, *args, **kwargs): + self.validate_email_func = validate_email_func + super(ChangeEmailForm, self).__init__(*args, **kwargs) + email_address = email_address() + def validate_email_address(self, field): + is_valid = self.validate_email_func(field.data) + if not is_valid: + raise ValidationError("The email address is already in use") + class ConfirmEmailForm(Form): + + def __init__(self, validate_code_func, *args, **kwargs): + self.validate_code_func = validate_code_func + super(ConfirmEmailForm, self).__init__(*args, **kwargs) + email_code = email_code() + def validate_email_code(self, field): + is_valid, msg = self.validate_code_func(field.data) + if not is_valid: + raise ValidationError(msg) + class ChangeMobileNumberForm(Form): mobile_number = mobile_number() class ConfirmMobileNumberForm(Form): + + def __init__(self, validate_code_func, *args, **kwargs): + self.validate_code_func = validate_code_func + super(ConfirmMobileNumberForm, self).__init__(*args, **kwargs) + sms_code = sms_code() + def validate_sms_code(self, field): + is_valid, msg = self.validate_code_func(field.data) + if not is_valid: + raise ValidationError(msg) + class CreateKeyForm(Form): def __init__(self, existing_key_names=[], *args, **kwargs): diff --git a/app/main/validators.py b/app/main/validators.py index 31313a4e6..3149a7729 100644 --- a/app/main/validators.py +++ b/app/main/validators.py @@ -14,32 +14,6 @@ class Blacklist(object): raise ValidationError(self.message) -class ValidateUserCodes(object): - def __init__(self, - expiry_msg='Code has expired', - invalid_msg='Code does not match', - code_type=None): - self.expiry_msg = expiry_msg - self.invalid_msg = invalid_msg - self.code_type = code_type - - def __call__(self, form, field): - # TODO would be great to do this sql query but - # not couple those parts of the code. - user_codes = getattr(form, 'user_codes', []) - valid_code = False - for code in user_codes: - if check_hash(field.data, code.code) and self.code_type == code.code_type: - if code.expiry_datetime <= datetime.now(): - raise ValidationError(self.expiry_msg) - else: - # Valid code - valid_code = True - break - if not valid_code: - raise ValidationError(self.invalid_msg) - - class CsvFileValidator(object): def __init__(self, message='Not a csv file'): diff --git a/app/main/views/code_not_received.py b/app/main/views/code_not_received.py index 56d68025e..5c4ef76b6 100644 --- a/app/main/views/code_not_received.py +++ b/app/main/views/code_not_received.py @@ -1,20 +1,22 @@ from flask import ( render_template, redirect, session, url_for) +from flask_login import current_user + from app.main import main from app.main.dao import users_dao from app.main.forms import EmailNotReceivedForm, TextNotReceivedForm -from app.notify_client.sender import send_sms_code, send_email_code @main.route('/email-not-received', methods=['GET', 'POST']) def check_and_resend_email_code(): # TODO there needs to be a way to regenerate a session id - user = users_dao.get_user_by_email(session['user_email']) + user = users_dao.get_user_by_email(session['user_details']['email']) form = EmailNotReceivedForm(email_address=user.email_address) if form.validate_on_submit(): - users_dao.update_email_address(id=user.id, email_address=form.email_address.data) - send_email_code(user_id=user.id, email=user.email_address) + users_dao.send_verify_code(user.id, 'email', to=form.email_address.data) + user.email_address = form.email_address.data + users_dao.update_user(user) return redirect(url_for('.verify')) return render_template('views/email-not-received.html', form=form) @@ -22,11 +24,12 @@ def check_and_resend_email_code(): @main.route('/text-not-received', methods=['GET', 'POST']) def check_and_resend_text_code(): # TODO there needs to be a way to regenerate a session id - user = users_dao.get_user_by_email(session['user_email']) + user = users_dao.get_user_by_email(session['user_details']['email']) form = TextNotReceivedForm(mobile_number=user.mobile_number) if form.validate_on_submit(): - users_dao.update_mobile_number(id=user.id, mobile_number=form.mobile_number.data) - send_sms_code(user_id=user.id, mobile_number=user.mobile_number) + users_dao.send_verify_code(user.id, 'sms', to=form.mobile_number.data) + user.mobile_number = form.mobile_number.data + users_dao.update_user(user) return redirect(url_for('.verify')) return render_template('views/text-not-received.html', form=form) @@ -39,6 +42,6 @@ def verification_code_not_received(): @main.route('/send-new-code', methods=['GET']) def check_and_resend_verification_code(): # TODO there needs to be a way to generate a new session id - user = users_dao.get_user_by_email(session['user_email']) - send_sms_code(user.id, user.mobile_number) + user = users_dao.get_user_by_email(session['user_details']['email']) + users_dao.send_verify_code(user.id, 'sms') return redirect(url_for('main.two_factor')) diff --git a/app/main/views/forgot_password.py b/app/main/views/forgot_password.py index 21d7d8f1c..64629c28b 100644 --- a/app/main/views/forgot_password.py +++ b/app/main/views/forgot_password.py @@ -7,13 +7,15 @@ from app.notify_client.sender import send_change_password_email @main.route('/forgot-password', methods=['GET', 'POST']) def forgot_password(): - form = ForgotPasswordForm() + + def _email_exists(email): + return not users_dao.is_email_unique(email) + + form = ForgotPasswordForm(_email_exists) if form.validate_on_submit(): - if users_dao.get_user_by_email(form.email_address.data): - users_dao.request_password_reset(form.email_address.data) - send_change_password_email(form.email_address.data) - return render_template('views/password-reset-sent.html') - else: - current_app.logger.info('The email address used does not exist.') - else: - return render_template('views/forgot-password.html', form=form) + user = users_dao.get_user_by_email(form.email_address.data) + users_dao.request_password_reset(user) + send_change_password_email(form.email_address.data) + return render_template('views/password-reset-sent.html') + + return render_template('views/forgot-password.html', form=form) diff --git a/app/main/views/new_password.py b/app/main/views/new_password.py index 7a46f5c18..aeed71e13 100644 --- a/app/main/views/new_password.py +++ b/app/main/views/new_password.py @@ -3,7 +3,7 @@ from flask import (render_template, url_for, redirect, flash) from app.main import main from app.main.dao import users_dao from app.main.forms import NewPasswordForm -from app.notify_client.sender import check_token, send_sms_code +from app.notify_client.sender import check_token @main.route('/new-password/', methods=['GET', 'POST']) @@ -21,8 +21,11 @@ def new_password(token): form = NewPasswordForm() if form.validate_on_submit(): - users_dao.update_password(user, form.new_password.data) - send_sms_code(user.id, user.mobile_number) + users_dao.send_verify_code(user.id, 'sms') + session['user_details'] = { + 'id': user.id, + 'email': user.email_address, + 'password': form.new_password.data} return redirect(url_for('main.two_factor')) else: return render_template('views/new-password.html', token=token, form=form, user=user) diff --git a/app/main/views/register.py b/app/main/views/register.py index 7681dc696..66d0cca75 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -18,17 +18,13 @@ from app.main.forms import RegisterUserForm from app import user_api_client -# TODO how do we handle duplicate unverifed email addresses? -# malicious or otherwise. -from app.notify_client.sender import send_sms_code, send_email_code - @main.route('/register', methods=['GET', 'POST']) def register(): if current_user and current_user.is_authenticated(): return redirect(url_for('main.choose_service')) - form = RegisterUserForm(users_dao.get_user_by_email) + form = RegisterUserForm(users_dao.is_email_unique) if form.validate_on_submit(): try: @@ -47,10 +43,10 @@ def register(): # How do we report to the user there is a problem with # sending codes apart from service unavailable? # at the moment i believe http 500 is fine. - send_sms_code(user_id=user.id, mobile_number=user.mobile_number) - send_email_code(user_id=user.id, email=user.email_address) + users_dao.send_verify_code(user.id, 'sms') + users_dao.send_verify_code(user.id, 'email') session['expiry_date'] = str(datetime.now() + timedelta(hours=1)) session['user_details'] = {"email": user.email_address, "id": user.id} - return redirect('/verify') + return redirect(url_for('main.verify')) return render_template('views/register.html', form=form) diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index 412e2844d..81e218227 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -1,10 +1,11 @@ from flask import ( render_template, redirect, request, url_for, abort, session) -from flask_login import login_required +from flask_login import (login_required, current_user) from app.main import main from app.main.dao.services_dao import ( get_service_by_id, delete_service, update_service) +from app.main.dao.users_dao import verify_password from app.main.forms import ConfirmPasswordForm, ServiceNameForm from client.errors import HTTPError @@ -61,7 +62,10 @@ def service_name_change_confirm(service_id): else: raise e - form = ConfirmPasswordForm() + # Validate password for form + def _check_password(pwd): + return verify_password(current_user, pwd) + form = ConfirmPasswordForm(_check_password) if form.validate_on_submit(): service['name'] = session['service_name_change'] @@ -128,9 +132,10 @@ def service_status_change_confirm(service_id): else: raise e - # TODO validate password, will leave until - # user management has been moved to the api. - form = ConfirmPasswordForm() + # Validate password for form + def _check_password(pwd): + return verify_password(current_user, pwd) + form = ConfirmPasswordForm(_check_password) if form.validate_on_submit(): service['active'] = True @@ -175,9 +180,11 @@ def service_delete_confirm(service_id): abort(404) else: raise e - # TODO validate password, will leave until - # user management has been moved to the api. - form = ConfirmPasswordForm() + + # Validate password for form + def _check_password(pwd): + return verify_password(current_user, pwd) + form = ConfirmPasswordForm(_check_password) if form.validate_on_submit(): try: diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py index aa8f6834c..af96db8e9 100644 --- a/app/main/views/sign_in.py +++ b/app/main/views/sign_in.py @@ -6,37 +6,29 @@ from flask import ( abort ) - from flask.ext.login import current_user - from app.main import main from app.main.dao import users_dao from app.main.forms import LoginForm -from app.notify_client.sender import send_sms_code @main.route('/sign-in', methods=(['GET', 'POST'])) def sign_in(): if current_user and current_user.is_authenticated(): return redirect(url_for('main.choose_service')) - try: - form = LoginForm() - if form.validate_on_submit(): - user = _get_and_verify_user(form.email_address.data, form.password.data) - if user: - send_sms_code(user.id, user.mobile_number) - session['user_email'] = user.email_address - return redirect(url_for('.two_factor')) - else: - # Vague error message for login in case of user not known, locked, inactive or password not verified - form.password.errors.append('Username or password is incorrect') + form = LoginForm() + if form.validate_on_submit(): + user = _get_and_verify_user(form.email_address.data, form.password.data) + if user: + users_dao.send_verify_code(user.id, 'sms') + session['user_details'] = {"email": user.email_address, "id": user.id} + return redirect(url_for('.two_factor')) + else: + # Vague error message for login in case of user not known, locked, inactive or password not verified + form.password.errors.append('Username or password is incorrect') - return render_template('views/signin.html', form=form) - except: - import traceback - traceback.print_exc() - abort(500) + return render_template('views/signin.html', form=form) def _get_and_verify_user(email_address, password): diff --git a/app/main/views/two_factor.py b/app/main/views/two_factor.py index 82b828391..b7ef5bdac 100644 --- a/app/main/views/two_factor.py +++ b/app/main/views/two_factor.py @@ -5,20 +5,30 @@ from flask import ( from flask_login import login_user from app.main import main -from app.main.dao import users_dao, verify_codes_dao +from app.main.dao import users_dao from app.main.forms import TwoFactorForm @main.route('/two-factor', methods=['GET', 'POST']) def two_factor(): # TODO handle user_email not in session - user = users_dao.get_user_by_email(session['user_email']) - codes = verify_codes_dao.get_codes(user.id) - form = TwoFactorForm(codes) + user_id = session['user_details']['id'] + + def _check_code(code): + return users_dao.check_verify_code(user_id, code, "sms") + + form = TwoFactorForm(_check_code) if form.validate_on_submit(): - verify_codes_dao.use_code_for_user_and_type(user_id=user.id, code_type='sms') - login_user(user) + try: + user = users_dao.get_user_by_id(user_id) + # Check if coming from new password page + if 'password' in session['user_details']: + user.set_password(session['user_details']['password']) + users_dao.update_user(user) + login_user(user) + finally: + del session['user_details'] return redirect(url_for('.choose_service')) return render_template('views/two-factor.html', form=form) diff --git a/app/main/views/user_profile.py b/app/main/views/user_profile.py index 7053cf04c..1d230ee6a 100644 --- a/app/main/views/user_profile.py +++ b/app/main/views/user_profile.py @@ -1,139 +1,200 @@ -from flask import request, render_template, redirect, url_for +from flask import ( + request, render_template, redirect, url_for, session) from flask.ext.login import current_user +from flask_login import login_required from app.main import main +from app.main.dao.users_dao import ( + verify_password, update_user, check_verify_code, is_email_unique, + send_verify_code) from app.main.forms import ( ChangePasswordForm, ChangeNameForm, ChangeEmailForm, ConfirmEmailForm, ChangeMobileNumberForm, ConfirmMobileNumberForm, ConfirmPasswordForm ) +NEW_EMAIL = 'new-email' +NEW_MOBILE = 'new-mob' +NEW_EMAIL_PASSWORD_CONFIRMED = 'new-email-password-confirmed' +NEW_MOBILE_PASSWORD_CONFIRMED = 'new-mob-password-confirmed' + @main.route("/user-profile") +@login_required def user_profile(): return render_template('views/user-profile.html') @main.route("/user-profile/name", methods=['GET', 'POST']) +@login_required def user_profile_name(): - form = ChangeNameForm() + form = ChangeNameForm(new_name=current_user.name) - if request.method == 'GET': - if current_user.is_authenticated(): - form.new_name.data = current_user.name - return render_template( - 'views/user-profile/change.html', - thing='name', - form_field=form.new_name - ) - elif request.method == 'POST': + if form.validate_on_submit(): + current_user.name = form.new_name.data + update_user(current_user) return redirect(url_for('.user_profile')) + return render_template( + 'views/user-profile/change.html', + thing='name', + form_field=form.new_name + ) + @main.route("/user-profile/email", methods=['GET', 'POST']) +@login_required def user_profile_email(): - form = ChangeEmailForm() + def _is_email_unique(email): + return is_email_unique(email) + form = ChangeEmailForm(_is_email_unique, + email_address=current_user.email_address) - if request.method == 'GET': - if current_user.is_authenticated(): - form.email_address.data = current_user.email_address - return render_template( - 'views/user-profile/change.html', - thing='email address', - form_field=form.email_address - ) - elif request.method == 'POST': + if form.validate_on_submit(): + session[NEW_EMAIL] = form.email_address.data return redirect(url_for('.user_profile_email_authenticate')) + return render_template( + 'views/user-profile/change.html', + thing='email address', + form_field=form.email_address + ) @main.route("/user-profile/email/authenticate", methods=['GET', 'POST']) +@login_required def user_profile_email_authenticate(): - form = ConfirmPasswordForm() + # Validate password for form + def _check_password(pwd): + return verify_password(current_user.id, pwd) + form = ConfirmPasswordForm(_check_password) - if request.method == 'GET': - return render_template( - 'views/user-profile/authenticate.html', - thing='email address', - form=form, - back_link=url_for('.user_profile_email') - ) - elif request.method == 'POST': + if NEW_EMAIL not in session: + return redirect('main.user_profile_email') + + if form.validate_on_submit(): + session[NEW_EMAIL_PASSWORD_CONFIRMED] = True + send_verify_code(current_user.id, 'email', to=session[NEW_EMAIL]) return redirect(url_for('.user_profile_email_confirm')) + return render_template( + 'views/user-profile/authenticate.html', + thing='email address', + form=form, + back_link=url_for('.user_profile_email') + ) + @main.route("/user-profile/email/confirm", methods=['GET', 'POST']) +@login_required def user_profile_email_confirm(): - form = ConfirmEmailForm() + # Validate verify code for form + def _check_code(cde): + return check_verify_code(current_user.id, cde, 'email') + form = ConfirmEmailForm(_check_code) - if request.method == 'GET': - return render_template( - 'views/user-profile/confirm.html', - form_field=form.email_code, - thing='email address' - ) - elif request.method == 'POST': + if NEW_EMAIL_PASSWORD_CONFIRMED not in session: + return redirect('main.user_profile_email_authenticate') + + if form.validate_on_submit(): + current_user.email_address = session[NEW_EMAIL] + del session[NEW_EMAIL] + del session[NEW_EMAIL_PASSWORD_CONFIRMED] + update_user(current_user) return redirect(url_for('.user_profile')) + return render_template( + 'views/user-profile/confirm.html', + form_field=form.email_code, + thing='email address' + ) + @main.route("/user-profile/mobile-number", methods=['GET', 'POST']) +@login_required def user_profile_mobile_number(): - form = ChangeMobileNumberForm() + form = ChangeMobileNumberForm(mobile_number=current_user.mobile_number) - if request.method == 'GET': - if current_user.is_authenticated(): - form.mobile_number.data = current_user.mobile_number - return render_template( - 'views/user-profile/change.html', - thing='mobile number', - form_field=form.mobile_number - ) - elif request.method == 'POST': + if form.validate_on_submit(): + session[NEW_MOBILE] = form.mobile_number.data return redirect(url_for('.user_profile_mobile_number_authenticate')) + return render_template( + 'views/user-profile/change.html', + thing='mobile number', + form_field=form.mobile_number + ) + @main.route("/user-profile/mobile-number/authenticate", methods=['GET', 'POST']) +@login_required def user_profile_mobile_number_authenticate(): - form = ConfirmPasswordForm() + # Validate password for form + def _check_password(pwd): + return verify_password(current_user.id, pwd) + form = ConfirmPasswordForm(_check_password) - if request.method == 'GET': - return render_template( - 'views/user-profile/authenticate.html', - thing='mobile number', - form=form, - back_link=url_for('.user_profile_mobile_number_confirm') - ) - elif request.method == 'POST': + if NEW_MOBILE not in session: + return redirect(url_for('.user_profile_mobile_number')) + + if form.validate_on_submit(): + session[NEW_MOBILE_PASSWORD_CONFIRMED] = True + send_verify_code(current_user.id, 'sms', to=session[NEW_MOBILE]) return redirect(url_for('.user_profile_mobile_number_confirm')) + return render_template( + 'views/user-profile/authenticate.html', + thing='mobile number', + form=form, + back_link=url_for('.user_profile_mobile_number_confirm') + ) + @main.route("/user-profile/mobile-number/confirm", methods=['GET', 'POST']) +@login_required def user_profile_mobile_number_confirm(): - form = ConfirmMobileNumberForm() + # Validate verify code for form + def _check_code(cde): + return check_verify_code(current_user.id, cde, 'sms') - if request.method == 'GET': - return render_template( - 'views/user-profile/confirm.html', - form_field=form.sms_code, - thing='mobile number' - ) - elif request.method == 'POST': + if NEW_MOBILE_PASSWORD_CONFIRMED not in session: + return redirect(url_for('.user_profile_mobile_number')) + + form = ConfirmMobileNumberForm(_check_code) + + if form.validate_on_submit(): + current_user.mobile_number = session[NEW_MOBILE] + del session[NEW_MOBILE] + del session[NEW_MOBILE_PASSWORD_CONFIRMED] + update_user(current_user) return redirect(url_for('.user_profile')) + return render_template( + 'views/user-profile/confirm.html', + form_field=form.sms_code, + thing='mobile number' + ) + @main.route("/user-profile/password", methods=['GET', 'POST']) +@login_required def user_profile_password(): - form = ChangePasswordForm() + # Validate password for form + def _check_password(pwd): + return verify_password(current_user.id, pwd) + form = ChangePasswordForm(_check_password) - if request.method == 'GET': - return render_template( - 'views/user-profile/change-password.html', - form=form - ) - elif request.method == 'POST': + if form.validate_on_submit(): + current_user.set_password(form.new_password.data) + update_user(current_user) return redirect(url_for('.user_profile')) + + return render_template( + 'views/user-profile/change-password.html', + form=form + ) diff --git a/app/main/views/verify.py b/app/main/views/verify.py index 2abd822b3..6b4d4790d 100644 --- a/app/main/views/verify.py +++ b/app/main/views/verify.py @@ -11,7 +11,7 @@ from client.errors import HTTPError from flask_login import login_user from app.main import main -from app.main.dao import users_dao, verify_codes_dao +from app.main.dao import users_dao from app.main.forms import VerifyForm @@ -20,13 +20,11 @@ def verify(): # TODO there needs to be a way to regenerate a session id # or handle gracefully. user_id = session['user_details']['id'] - codes = verify_codes_dao.get_codes(user_id) - form = VerifyForm(codes) + def _check_code(code, code_type): + return users_dao.check_verify_code(user_id, code, code_type) + form = VerifyForm(_check_code) if form.validate_on_submit(): - 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') - try: user = users_dao.get_user_by_id(user_id) activated_user = users_dao.activate_user(user) @@ -37,5 +35,7 @@ def verify(): abort(404) else: raise e + finally: + del session['user_details'] return render_template('views/verify.html', form=form) diff --git a/app/models.py b/app/models.py deleted file mode 100644 index 5f81373f9..000000000 --- a/app/models.py +++ /dev/null @@ -1,126 +0,0 @@ -import datetime -from app import db -from flask import current_app - -DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" -DATE_FORMAT = "%Y-%m-%d" - - -class VerifyCodes(db.Model): - __tablename__ = 'verify_codes' - - code_types = ['email', 'sms'] - - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, index=True, unique=False, nullable=False) - code = db.Column(db.String, nullable=False) - code_type = db.Column(db.Enum(code_types, name='verify_code_types'), index=False, unique=False, nullable=False) - expiry_datetime = db.Column(db.DateTime, nullable=False) - code_used = db.Column(db.Boolean, default=False) - - -class Roles(db.Model): - __tablename__ = 'roles' - - id = db.Column(db.Integer, primary_key=True) - role = db.Column(db.String, nullable=False, unique=True) - - -class User(db.Model): - __tablename__ = 'users' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String, nullable=False, index=True, unique=False) - email_address = db.Column(db.String(255), nullable=False, index=True, unique=True) - password = db.Column(db.String, index=False, unique=False, nullable=False) - mobile_number = db.Column(db.String, index=False, unique=False, nullable=False) - created_at = db.Column(db.DateTime, - index=False, - unique=False, - nullable=False, - default=datetime.datetime.now) - updated_at = db.Column(db.DateTime, - index=False, - unique=False, - nullable=True, - onupdate=datetime.datetime.now) - password_changed_at = db.Column(db.DateTime, index=False, unique=False, nullable=True) - role_id = db.Column(db.Integer, db.ForeignKey('roles.id'), index=True, unique=False, nullable=False) - logged_in_at = db.Column(db.DateTime, nullable=True) - failed_login_count = db.Column(db.Integer, nullable=False, default=0) - # TODO should this be an enum? - state = db.Column(db.String, nullable=False, default='pending') - - def serialize(self): - serialized = { - 'id': self.id, - 'name': self.name, - 'emailAddress': self.email_address, - 'createdAt': self.created_at.strftime(DATETIME_FORMAT), - 'updatedAt': self.updated_at.strftime(DATETIME_FORMAT), - 'role': self.role, - 'passwordChangedAt': self.password_changed_at.strftime(DATETIME_FORMAT), - 'failedLoginCount': self.failed_login_count - } - - return filter_null_value_fields(serialized) - - def is_authenticated(self): - return True - - def is_active(self): - if self.state != 'active': - return False - else: - return True - - def is_anonymous(self): - return False - - def get_id(self): - return self.id - - def is_locked(self): - if self.failed_login_count < current_app.config['MAX_FAILED_LOGIN_COUNT']: - return False - else: - return True - - -# user_to_service = db.Table( -# 'user_to_service', -# db.Model.metadata, -# db.Column('user_id', db.Integer, db.ForeignKey('users.id')), -# db.Column('service_id', db.Integer, db.ForeignKey('services.id')) -# ) - - -# class Service(db.Model): -# __tablename__ = 'services' - -# id = db.Column(db.Integer, primary_key=True) -# name = db.Column(db.String(255), nullable=False, unique=True) -# created_at = db.Column(db.DateTime, index=False, unique=False, nullable=False) -# active = db.Column(db.Boolean, index=False, unique=False, nullable=False) -# limit = db.Column(db.BigInteger, index=False, unique=False, nullable=False) -# users = db.relationship('User', secondary=user_to_service, backref=db.backref('user_to_service', lazy='dynamic')) -# restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False) - -# def serialize(self): -# serialized = { -# 'id': self.id, -# 'name': self.name, -# 'createdAt': self.created_at.strftime(DATETIME_FORMAT), -# 'active': self.active, -# 'restricted': self.restricted, -# 'limit': self.limit, -# 'user': self.users.serialize() -# } - -# return filter_null_value_fields(serialized) - - -def filter_null_value_fields(obj): - return dict( - filter(lambda x: x[1] is not None, obj.items()) - ) diff --git a/app/notify_client/sender.py b/app/notify_client/sender.py index 85acc4080..3ae7951bf 100644 --- a/app/notify_client/sender.py +++ b/app/notify_client/sender.py @@ -1,32 +1,9 @@ from random import randint from flask import url_for, current_app from itsdangerous import URLSafeTimedSerializer, SignatureExpired -from app.main.dao import verify_codes_dao from app import notifications_api_client -def create_verify_code(): - return ''.join(["%s" % randint(0, 9) for _ in range(0, 5)]) - - -def send_sms_code(user_id, mobile_number): - sms_code = create_verify_code() - verify_codes_dao.add_code(user_id=user_id, code=sms_code, code_type='sms') - notifications_api_client.send_sms(mobile_number=mobile_number, - message=sms_code) - return sms_code - - -def send_email_code(user_id, email): - email_code = create_verify_code() - verify_codes_dao.add_code(user_id=user_id, code=email_code, code_type='email') - notifications_api_client.send_email(email_address=email, - from_address='notify@digital.cabinet-office.gov.uk', - message=email_code, - subject='Verification code') - return email_code - - def send_change_password_email(email): link_to_change_password = url_for('.new_password', token=generate_token(email), _external=True) notifications_api_client.send_email(email_address=email, diff --git a/app/notify_client/user_api_client.py b/app/notify_client/user_api_client.py index 250284de6..a083310d3 100644 --- a/app/notify_client/user_api_client.py +++ b/app/notify_client/user_api_client.py @@ -45,23 +45,15 @@ class UserApiClient(BaseAPIClient): user_data = self.put(url, data=data) return User(user_data['data'], max_failed_login_count=self.max_failed_login_count) - def verify_password(self, user, password): + def verify_password(self, user_id, password): try: - data = user.serialize() - url = "/user/{}/verify/password".format(user.id) - data["password"] = password - resp = self.post(url, data=data) - if resp.status_code == 204: - return True + url = "/user/{}/verify/password".format(user_id) + data = {"password": password} + self.post(url, data=data) + return True except HTTPError as e: if e.status_code == 400 or e.status_code == 404: return False - # TODO temp work around until client fixed - except InvalidResponse as e: - if e.status_code == 204: - return True - else: - raise e def get_user_by_email(self, email_address): users = self.get_users() @@ -70,6 +62,27 @@ class UserApiClient(BaseAPIClient): return user[0] return None + def send_verify_code(self, user_id, code_type, to=None): + data = {'code_type': code_type} + if to: + data['to'] = to + endpoint = '/user/{}/code'.format(user_id) + resp = self.post(endpoint, data=data) + + def check_verify_code(self, user_id, code, code_type): + data = {'code_type': code_type, 'code': code} + endpoint = '/user/{}/verify/code'.format(user_id) + try: + resp = self.post(endpoint, data=data) + return True, '' + except HTTPError as e: + if e.status_code == 400 or e.status_code == 404: + if 'Code not found' in e.message: + return False, 'Code not found' + elif 'Code has expired' in e.message: + return False, 'Code has expired' + raise e + class User(object): def __init__(self, fields, max_failed_login_count=3): @@ -154,11 +167,16 @@ class User(object): return self.failed_login_count >= self.max_failed_login_count def serialize(self): - return {"id": self.id, - "name": self.name, - "email_address": self.email_address, - "mobile_number": self.mobile_number, - "password_changed_at": self.password_changed_at, - "state": self.state, - "failed_login_count": self.failed_login_count - } + dct = {"id": self.id, + "name": self.name, + "email_address": self.email_address, + "mobile_number": self.mobile_number, + "password_changed_at": self.password_changed_at, + "state": self.state, + "failed_login_count": self.failed_login_count} + if getattr(self, '_password', None): + dct['password'] = self._password + return dct + + def set_password(self, pwd): + self._password = pwd diff --git a/db.py b/db.py deleted file mode 100644 index 7dab633c3..000000000 --- a/db.py +++ /dev/null @@ -1,15 +0,0 @@ -from flask.ext.script import Manager, Server -from flask_migrate import Migrate, MigrateCommand -from app import create_app, db -from credstash import getAllSecrets - -secrets = getAllSecrets(region="eu-west-1") - -application = create_app('live', secrets) - -manager = Manager(application) -migrate = Migrate(application, db) -manager.add_command('db', MigrateCommand) - -if __name__ == '__main__': - manager.run() diff --git a/migrations/README b/migrations/README deleted file mode 100755 index 98e4f9c44..000000000 --- a/migrations/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini deleted file mode 100644 index 0006599ec..000000000 --- a/migrations/alembic.ini +++ /dev/null @@ -1,45 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py deleted file mode 100644 index 70961ce2c..000000000 --- a/migrations/env.py +++ /dev/null @@ -1,73 +0,0 @@ -from __future__ import with_statement -from alembic import context -from sqlalchemy import engine_from_config, pool -from logging.config import fileConfig - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -fileConfig(config.config_file_name) - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -from flask import current_app -config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI')) -target_metadata = current_app.extensions['migrate'].db.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - -def run_migrations_offline(): - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure(url=url) - - with context.begin_transaction(): - context.run_migrations() - -def run_migrations_online(): - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - engine = engine_from_config( - config.get_section(config.config_ini_section), - prefix='sqlalchemy.', - poolclass=pool.NullPool) - - connection = engine.connect() - context.configure( - connection=connection, - target_metadata=target_metadata - ) - - try: - with context.begin_transaction(): - context.run_migrations() - finally: - connection.close() - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() - diff --git a/migrations/script.py.mako b/migrations/script.py.mako deleted file mode 100755 index 95702017e..000000000 --- a/migrations/script.py.mako +++ /dev/null @@ -1,22 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision} -Create Date: ${create_date} - -""" - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} - -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -def upgrade(): - ${upgrades if upgrades else "pass"} - - -def downgrade(): - ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/10_create_users.py b/migrations/versions/10_create_users.py deleted file mode 100644 index fbd32f153..000000000 --- a/migrations/versions/10_create_users.py +++ /dev/null @@ -1,42 +0,0 @@ -"""empty message - -Revision ID: 10_create_users -Revises: None -Create Date: 2015-11-24 10:39:19.827534 - -""" - -# revision identifiers, used by Alembic. -revision = '10_create_users' -down_revision = None - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - op.create_table('roles', - sa.Column('id', sa.Integer, primary_key=True), - sa.Column('role', sa.String, nullable=False, unique=True) - ) - - op.create_table('users', - sa.Column('id', sa.Integer, primary_key=True), - sa.Column('name', sa.String, nullable=False, unique=True), - sa.Column('email_address', sa.String(length=255), nullable=False), - sa.Column('password', sa.String, nullable=False), - sa.Column('mobile_number', sa.String, nullable=False), - sa.Column('created_at', sa.DateTime, nullable=False), - sa.Column('updated_at', sa.DateTime), - sa.Column('password_changed_at', sa.DateTime), - sa.Column('role_id', sa.Integer, nullable=False), - sa.Column('logged_in_at', sa.DateTime), - sa.Column('failed_login_count', sa.Integer, nullable=False), - sa.Column('state', sa.String, default='pending'), - sa.ForeignKeyConstraint(['role_id'], ['roles.id']) - ) - - -def downgrade(): - op.drop_table('users') - op.drop_table('roles') diff --git a/migrations/versions/20_initialise_data.py b/migrations/versions/20_initialise_data.py deleted file mode 100644 index ce5726cfc..000000000 --- a/migrations/versions/20_initialise_data.py +++ /dev/null @@ -1,14 +0,0 @@ -# revision identifiers, used by Alembic. -revision = '20_initialise_data' -down_revision = '10_create_users' -from app.models import Roles -from alembic import op - -def upgrade(): - op.execute("insert into roles(role) values('platform_admin')") - op.execute("insert into roles(role) values('service_user')") - - -def downgrade(): - op.drop_table('users') - op.drop_table('roles') diff --git a/migrations/versions/30_update_indexes.py b/migrations/versions/30_update_indexes.py deleted file mode 100644 index afb4603be..000000000 --- a/migrations/versions/30_update_indexes.py +++ /dev/null @@ -1,36 +0,0 @@ -"""empty message - -Revision ID: 1691efaa342 -Revises: 30_update_indexes.py -Create Date: 2015-12-09 13:15:00.773848 - -""" - -# revision identifiers, used by Alembic. -revision = '30_update_indexes' -down_revision = '20_initialise_data' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.alter_column('users', 'state', - existing_type=sa.VARCHAR(), - nullable=False) - op.create_index(op.f('ix_users_email_address'), 'users', ['email_address'], unique=False) - op.create_index(op.f('ix_users_name'), 'users', ['name'], unique=False) - op.create_index(op.f('ix_users_role_id'), 'users', ['role_id'], unique=False) - ### end Alembic commands ### - - -def downgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_users_role_id'), table_name='users') - op.drop_index(op.f('ix_users_name'), table_name='users') - op.drop_index(op.f('ix_users_email_address'), table_name='users') - op.alter_column('users', 'state', - existing_type=sa.VARCHAR(), - nullable=True) - ### end Alembic commands ### diff --git a/migrations/versions/40_verify_codes.py b/migrations/versions/40_verify_codes.py deleted file mode 100644 index 01ed89897..000000000 --- a/migrations/versions/40_verify_codes.py +++ /dev/null @@ -1,42 +0,0 @@ -"""empty message - -Revision ID: 40_verify_codes -Revises: 30_update_indexes -Create Date: 2015-12-09 16:39:44.673094 - -""" - -# revision identifiers, used by Alembic. -revision = '40_verify_codes' -down_revision = '30_update_indexes' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - code_type_enum = sa.Enum('email', 'sms', name='verify_code_types') - # code_type_enum.create(op.get_bind(), checkfirst=False) - - op.create_table('verify_codes', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('code', sa.String, nullable=False), - sa.Column('code_type', code_type_enum), - sa.Column('expiry_datetime', sa.DateTime(), nullable=False), - sa.Column('code_used', sa.Boolean(), nullable=True), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_verify_codes_user_id'), 'verify_codes', ['user_id'], unique=False) - op.drop_constraint('users_name_key', 'users', type_='unique') - ### end Alembic commands ### - - -def downgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.create_unique_constraint('users_name_key', 'users', ['name']) - op.drop_index(op.f('ix_verify_codes_user_id'), table_name='verify_codes') - op.drop_table('verify_codes') - op.execute('DROP TYPE verify_code_types') - ### end Alembic commands ### diff --git a/migrations/versions/50_alter_verify_code_type.py b/migrations/versions/50_alter_verify_code_type.py deleted file mode 100644 index 3915238b7..000000000 --- a/migrations/versions/50_alter_verify_code_type.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: 50_alter_verify_code_type -Revises: 40_verify_codes -Create Date: 2015-12-11 10:21:24.098275 - -""" - -# revision identifiers, used by Alembic. -revision = '50_alter_verify_code_type' -down_revision = '40_verify_codes' - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -def upgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.alter_column('verify_codes', 'code_type', - existing_type=postgresql.ENUM('email', 'sms', name='verify_code_types'), - nullable=False) - ### end Alembic commands ### - - -def downgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.alter_column('verify_codes', 'code_type', - existing_type=postgresql.ENUM('email', 'sms', name='verify_code_types'), - nullable=True) - ### end Alembic commands ### diff --git a/migrations/versions/60_add_service.py b/migrations/versions/60_add_service.py deleted file mode 100644 index 5dd28284d..000000000 --- a/migrations/versions/60_add_service.py +++ /dev/null @@ -1,42 +0,0 @@ -"""empty message - -Revision ID: 60_add_service -Revises: 50_alter_verify_code_type -Create Date: 2015-12-15 09:25:09.000431 - -""" - -# revision identifiers, used by Alembic. -revision = '60_add_service' -down_revision = '50_alter_verify_code_type' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.create_table('services', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=255), nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('active', sa.Boolean(), nullable=False), - sa.Column('limit', sa.BigInteger(), nullable=False), - sa.Column('restricted', sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('name') - ) - op.create_table('user_to_service', - sa.Column('user_id', sa.Integer(), nullable=True), - sa.Column('service_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['service_id'], ['services.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ) - ) - ### end Alembic commands ### - - -def downgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user_to_service') - op.drop_table('services') - ### end Alembic commands ### diff --git a/migrations/versions/70_unique_email.py b/migrations/versions/70_unique_email.py deleted file mode 100644 index 7a186dd73..000000000 --- a/migrations/versions/70_unique_email.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: 70_unique_email -Revises: 60_add_service -Create Date: 2015-12-17 14:46:52.831849 - -""" - -# revision identifiers, used by Alembic. -revision = '70_unique_email' -down_revision = '60_add_service' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.drop_index('ix_users_email_address', table_name='users') - op.create_index(op.f('ix_users_email_address'), 'users', ['email_address'], unique=True) - ### end Alembic commands ### - - -def downgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_users_email_address'), table_name='users') - op.create_index('ix_users_email_address', 'users', ['email_address'], unique=False) - ### end Alembic commands ### diff --git a/migrations/versions/80_remove_services.py b/migrations/versions/80_remove_services.py deleted file mode 100644 index ceb668cc8..000000000 --- a/migrations/versions/80_remove_services.py +++ /dev/null @@ -1,43 +0,0 @@ -"""empty message - -Revision ID: 80_remove_services -Revises: 70_unique_email -Create Date: 2016-01-14 17:48:42.426979 - -""" - -# revision identifiers, used by Alembic. -revision = '80_remove_services' -down_revision = '70_unique_email' - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -def upgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user_to_service') - op.drop_table('services') - ### end Alembic commands ### - - -def downgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.create_table('services', - sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('services_id_seq'::regclass)"), nullable=False), - sa.Column('name', sa.VARCHAR(length=255), autoincrement=False, nullable=False), - sa.Column('created_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=False), - sa.Column('active', sa.BOOLEAN(), autoincrement=False, nullable=False), - sa.Column('limit', sa.BIGINT(), autoincrement=False, nullable=False), - sa.Column('restricted', sa.BOOLEAN(), autoincrement=False, nullable=False), - sa.PrimaryKeyConstraint('id', name='services_pkey'), - sa.UniqueConstraint('name', name='services_name_key'), - postgresql_ignore_search_path=False - ) - op.create_table('user_to_service', - sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=True), - sa.Column('service_id', sa.INTEGER(), autoincrement=False, nullable=True), - sa.ForeignKeyConstraint(['service_id'], ['services.id'], name='user_to_service_service_id_fkey'), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='user_to_service_user_id_fkey') - ) - ### end Alembic commands ### diff --git a/migrations/versions/90_remove_user_fk_verify_codes.py b/migrations/versions/90_remove_user_fk_verify_codes.py deleted file mode 100644 index efa5a0d0c..000000000 --- a/migrations/versions/90_remove_user_fk_verify_codes.py +++ /dev/null @@ -1,26 +0,0 @@ -"""empty message - -Revision ID: 90_remove_user_fk_verify_codes -Revises: 80_remove_services -Create Date: 2016-01-19 20:57:41.986177 - -""" - -# revision identifiers, used by Alembic. -revision = '90_remove_user_fk_verify_codes' -down_revision = '80_remove_services' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint('verify_codes_user_id_fkey', 'verify_codes', type_='foreignkey') - ### end Alembic commands ### - - -def downgrade(): - ### commands auto generated by Alembic - please adjust! ### - op.create_foreign_key('verify_codes_user_id_fkey', 'verify_codes', 'users', ['user_id'], ['id']) - ### end Alembic commands ### diff --git a/requirements.txt b/requirements.txt index 28d1c6834..d65172e47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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.2.1#egg=notifications-python-client==0.2.1 +git+https://github.com/alphagov/notifications-python-client.git@0.2.2#egg=notifications-python-client==0.2.2 git+https://github.com/alphagov/notifications-utils.git@0.0.3#egg=notifications-utils==0.0.3 diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index f18c2d865..cd3ffb715 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -27,10 +27,3 @@ fi # Install Python development dependencies pip3 install -r requirements_for_test.txt - -# Create Postgres databases -createdb notifications_admin -createdb test_notifications_admin - -# Upgrade databases -python app.py db upgrade \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index c0806dcbb..2d325a5cd 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,15 +1,17 @@ from flask.testing import FlaskClient from flask import url_for -from app.models import User -from app.main.dao import (users_dao, verify_codes_dao) class TestClient(FlaskClient): def login(self, user): # Skipping authentication here and just log them in with self.session_transaction() as session: - session['user_email'] = user.email_address - verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') + session['user_details'] = { + "email": user.email_address, + "id": user.id} + # Include mock_login fixture in test for this to work. + # TODO would be better for it to be mocked in this + # function response = self.post( url_for('main.two_factor'), data={'sms_code': '12345'}) assert response.status_code == 302 @@ -49,12 +51,8 @@ TEST_USER_EMAIL = 'test@user.gov.uk' def create_test_user(state): - user = User(name='Test User', - password='somepassword', - email_address=TEST_USER_EMAIL, - mobile_number='+441234123412', - role_id=1, - state=state) + from app.main.dao import users_dao + user = None users_dao.insert_user(user) return user @@ -73,15 +71,12 @@ def create_test_api_user(state): def create_another_test_user(state): - user = User(name='Another Test User', - password='someOtherpassword', - email_address='another_test@user.gov.uk', - mobile_number='+442233123412', - role_id=1, - state=state) + from app.main.dao import users_dao + user = None users_dao.insert_user(user) return user def get_test_user(): + from app.main.dao import users_dao return users_dao.get_user_by_email(TEST_USER_EMAIL) diff --git a/tests/app/main/dao/__init__.py b/tests/app/main/dao/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/app/main/dao/test_roles_dao.py b/tests/app/main/dao/test_roles_dao.py deleted file mode 100644 index fbae56436..000000000 --- a/tests/app/main/dao/test_roles_dao.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest -import sqlalchemy - -from app.models import Roles -from app.main.dao import roles_dao - - -def test_insert_role_should_be_able_to_get_role(app_, db_, db_session): - role = Roles(id=1000, role='some role for test') - roles_dao.insert_role(role) - - saved_role = roles_dao.get_role_by_id(role.id) - assert saved_role == role - - -def test_insert_role_will_throw_error_if_role_already_exists(app_, - db_, - db_session): - role1 = roles_dao.get_role_by_id(1) - assert role1.id == 1 - - role = Roles(id=1, role='cannot create a duplicate') - - with pytest.raises(sqlalchemy.orm.exc.FlushError) as error: - roles_dao.insert_role(role) - assert 'conflicts with persistent instance' in str(error.value) diff --git a/tests/app/main/dao/test_service_dao.py b/tests/app/main/dao/test_service_dao.py deleted file mode 100644 index cccfaab4c..000000000 --- a/tests/app/main/dao/test_service_dao.py +++ /dev/null @@ -1,65 +0,0 @@ -from app.main.dao import services_dao -import pytest - - -def test_can_insert_new_service(db_, - db_session, - mock_active_user, - mock_create_service, - mock_get_by_email): - service_name = 'testing service' - id_ = services_dao.insert_new_service(service_name, mock_active_user.id) - mock_create_service.assert_called_once_with( - service_name, False, 1000, True, mock_active_user.id) - - -def test_unrestrict_service_updates_the_service(db_, - db_session, - mock_get_service, - mock_update_service, - mock_get_by_email): - service_one = mock_get_service.side_effect(123)['data'] - services_dao.unrestrict_service(service_one['id']) - mock_update_service.assert_called_once_with(service_one['id'], - service_one['name'], - service_one['active'], - service_one['limit'], - False, - service_one['users']) - - -@pytest.mark.xfail(reason='Incorrectly expects service to start off inactive') -def test_activate_service_update_service(db_, - db_session, - mock_active_user, - mock_get_service, - mock_update_service, - mock_get_by_email): - service_one = mock_get_service.side_effect(123)['data'] - services_dao.activate_service(service_one['id']) - mock_update_service.assert_called_once_with(service_one['id'], - service_one['name'], - False, - service_one['limit'], - service_one['restricted'], - service_one['users']) - - -def test_get_service_returns_none_if_service_does_not_exist(db_, db_session, mock_get_service): - mock_get_service.side_effect = lambda x: None - service = services_dao.get_service_by_id(1) - assert service is None - - -def test_find_by_service_name_returns_right_service(db_, db_session, mock_get_services): - service_name = "service_one" - service = services_dao.find_service_by_service_name(service_name) - assert mock_get_services.called - assert service['name'] == service_name - - -def test_should_return_list_of_service_names(db_, db_session, mock_api_user, mock_get_services): - expected = ['service_one', 'service_two'] - actual = services_dao.find_all_service_names(mock_api_user.id) - assert mock_get_services.called - assert actual == expected diff --git a/tests/app/main/dao/test_users_dao.py b/tests/app/main/dao/test_users_dao.py deleted file mode 100644 index 7cb994590..000000000 --- a/tests/app/main/dao/test_users_dao.py +++ /dev/null @@ -1,132 +0,0 @@ -from datetime import datetime -import pytest -import sqlalchemy -from app.main.encryption import check_hash -from app.models import User -from app.main.dao import users_dao - - -@pytest.mark.skipif(True, reason='Database tests to move to api and ineraction tests done here') -def test_insert_user_should_add_user(db_, db_session): - user = User(name='test insert', - password='somepassword', - email_address='test@insert.gov.uk', - mobile_number='+441234123412', - role_id=1) - - users_dao.insert_user(user) - saved_user = users_dao.get_user_by_id(user.id) - assert saved_user == user - - -@pytest.mark.skipif(True, reason='Database tests to move to api and ineraction tests done here') -def test_insert_user_with_role_that_does_not_exist_fails(db_, db_session): - user = User(name='role does not exist', - password='somepassword', - email_address='test@insert.gov.uk', - mobile_number='+441234123412', - role_id=100) - with pytest.raises(sqlalchemy.exc.IntegrityError) as error: - users_dao.insert_user(user) - assert 'insert or update on table "users" violates foreign key constraint "users_role_id_fkey"' in str(error.value) - - -def test_get_user_by_email_calls_api(db_, db_session, mock_active_user, mock_get_user_from_api): - users_dao.get_user_by_email(mock_active_user.email_address) - mock_get_user_from_api.assert_called_once_with(mock_active_user.email_address) - - -def test_get_all_users_calls_api(db_, db_session, mock_get_all_users_from_api): - users_dao.get_all_users() - assert mock_get_all_users_from_api.called - - -def test_user_is_active_is_false_if_state_is_inactive(db_, db_session, mock_active_user): - assert mock_active_user.is_active() is True - mock_active_user.state = 'inactive' - assert mock_active_user.is_active() is False - - -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.skipif(True, reason='Database tests to move to api and ineraction tests done here') -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) - - -def test_should_update_email_address(db_, db_session, mock_active_user, mock_get_user, mock_update_user_email_api): - assert mock_active_user.email_address == 'test@user.gov.uk' - users_dao.update_email_address(mock_active_user.id, 'new_email@testit.gov.uk') - - assert mock_active_user.email_address == 'new_email@testit.gov.uk' - mock_update_user_email_api.assert_called_once_with(mock_active_user) - - -@pytest.mark.skipif(True, reason='Database tests to move to api and ineraction tests done here') -def test_should_update_password(db_, db_session): - user = User(name='Update Email', - password='somepassword', - email_address='test@it.gov.uk', - mobile_number='+441234123412', - role_id=1, - state='active') - start = datetime.now() - users_dao.insert_user(user) - - saved = users_dao.get_user_by_id(user.id) - assert check_hash('somepassword', saved.password) - assert saved.password_changed_at is None - users_dao.update_password(saved, 'newpassword') - updated = users_dao.get_user_by_id(user.id) - assert check_hash('newpassword', updated.password) - assert updated.password_changed_at < datetime.now() - assert updated.password_changed_at > start - - -@pytest.mark.skipif(True, reason='Database tests to move to api and ineraction tests done here') -def test_should_return_list_of_all_email_addresses(db_, db_session): - first = User(name='First Person', - password='somepassword', - email_address='first@it.gov.uk', - mobile_number='+441234123412', - role_id=1, - state='active') - second = User(name='Second Person', - password='somepassword', - email_address='second@it.gov.uk', - mobile_number='+441234123412', - role_id=1, - state='active') - users_dao.insert_user(first) - users_dao.insert_user(second) - - email_addresses = users_dao.get_all_users() - expected = [first.email_address, second.email_address] - assert expected == [x.email_address for x in email_addresses] - - -@pytest.mark.skipif(True, reason='Database tests to move to api and ineraction tests done here') -def test_should_update_state_to_request_password_reset(db_, db_session): - user = User(name='Requesting Password Resest', - password='somepassword', - email_address='request@new_password.gov.uk', - mobile_number='+441234123412', - role_id=1, - state='active') - users_dao.insert_user(user) - users_dao.request_password_reset(user.email_address) - saved = users_dao.get_user_by_email(user.email_address) - assert saved.state == 'request_password_reset' diff --git a/tests/app/main/dao/test_verify_codes_dao.py b/tests/app/main/dao/test_verify_codes_dao.py deleted file mode 100644 index 0bd825d19..000000000 --- a/tests/app/main/dao/test_verify_codes_dao.py +++ /dev/null @@ -1,37 +0,0 @@ -import sqlalchemy -from pytest import fail - -from app.main.dao import verify_codes_dao -from app.main.encryption import check_hash - - -def test_insert_new_code_and_get_it_back(app_, db_, db_session): - - verify_codes_dao.add_code(user_id=1, code='12345', code_type='email') - saved_codes = verify_codes_dao.get_codes(user_id=1, code_type='email') - assert len(saved_codes) == 1 - saved_code = saved_codes[0] - assert saved_code.user_id == 1 - assert check_hash('12345', saved_code.code) - assert saved_code.code_type == 'email' - assert saved_code.code_used is False - - -def test_insert_new_code_should_thrw_exception_when_type_does_not_exist(app_, - db_, - db_session): - try: - verify_codes_dao.add_code(user_id=1, code='23545', code_type='not_real') - fail('Should have thrown an exception') - except sqlalchemy.exc.DataError as e: - assert 'invalid input value for enum verify_code_types: "not_real"' in e.orig.pgerror - - -def test_should_return_none_if_code_is_used(app_, - db_, - db_session): - - code = verify_codes_dao.add_code(user_id=1, code='12345', code_type='email') - verify_codes_dao.use_code(code.id) - saved_code = verify_codes_dao.get_code_by_code(user_id=1, code_type='email', code='12345') - assert not saved_code diff --git a/tests/app/main/notify_client/test_sender.py b/tests/app/main/notify_client/test_sender.py index e49ba5a9c..aef8d1f0c 100644 --- a/tests/app/main/notify_client/test_sender.py +++ b/tests/app/main/notify_client/test_sender.py @@ -4,17 +4,13 @@ from pytest import fail from app.notify_client.sender import generate_token, check_token -def test_should_return_email_from_signed_token(app_, - db_, - db_session): +def test_should_return_email_from_signed_token(app_): email = 'email@something.com' token = generate_token(email) assert email == check_token(token) -def test_should_throw_exception_when_token_is_tampered_with(app_, - db_, - db_session): +def test_should_throw_exception_when_token_is_tampered_with(app_): email = 'email@something.com' token = generate_token(email) try: @@ -24,9 +20,7 @@ def test_should_throw_exception_when_token_is_tampered_with(app_, pass -def test_return_none_when_token_is_expired(app_, - db_, - db_session): +def test_return_none_when_token_is_expired(app_): with app_.test_request_context(): app_.config['TOKEN_MAX_AGE_SECONDS'] = -1000 email = 'email@something.com' diff --git a/tests/app/main/test_add_service_form.py b/tests/app/main/test_add_service_form.py index 4bc866501..a89170c1e 100644 --- a/tests/app/main/test_add_service_form.py +++ b/tests/app/main/test_add_service_form.py @@ -2,9 +2,7 @@ from app.main.forms import AddServiceForm from werkzeug.datastructures import MultiDict -def test_form_should_have_errors_when_duplicate_service_is_added(app_, - db_, - db_session): +def test_form_should_have_errors_when_duplicate_service_is_added(app_): def _get_form_names(): return ['some service', 'more names'] with app_.test_request_context(): diff --git a/tests/app/main/test_create_api_key_form.py b/tests/app/main/test_create_api_key_form.py index c19114b58..c13fe9fe3 100644 --- a/tests/app/main/test_create_api_key_form.py +++ b/tests/app/main/test_create_api_key_form.py @@ -3,9 +3,7 @@ from werkzeug.datastructures import MultiDict from app.main.forms import CreateKeyForm -def test_return_validation_error_when_key_name_exists(app_, - db_, - db_session): +def test_return_validation_error_when_key_name_exists(app_): def _get_names(): return ['some key', 'another key'] diff --git a/tests/app/main/test_two_factor_form.py b/tests/app/main/test_two_factor_form.py index df4f32177..640e81e58 100644 --- a/tests/app/main/test_two_factor_form.py +++ b/tests/app/main/test_two_factor_form.py @@ -1,70 +1,61 @@ from datetime import datetime, timedelta -from app.main.dao import verify_codes_dao from app.main.forms import TwoFactorForm +from app.main.dao import users_dao from tests import create_test_user -def test_form_is_valid_returns_no_errors(app_, db_, db_session): +def test_form_is_valid_returns_no_errors(app_, mock_check_verify_code): with app_.test_request_context(method='POST', data={'sms_code': '12345'}) as req: - user = set_up_test_data() - codes = verify_codes_dao.get_codes(user.id) - form = TwoFactorForm(codes) + def _check_code(code): + return users_dao.check_verify_code('1', code, "sms") + form = TwoFactorForm(_check_code) assert form.validate() is True assert len(form.errors) == 0 -def test_returns_errors_when_code_is_too_short(app_, db_, db_session): +def test_returns_errors_when_code_is_too_short(app_, mock_check_verify_code): with app_.test_request_context(method='POST', data={'sms_code': '145'}) as req: - user = set_up_test_data() - codes = verify_codes_dao.get_codes(user.id) - form = TwoFactorForm(codes) + def _check_code(code): + return users_dao.check_verify_code('1', code, "sms") + form = TwoFactorForm(_check_code) assert form.validate() is False assert len(form.errors) == 1 assert set(form.errors) == set({'sms_code': ['Code must be 5 digits', 'Code does not match']}) -def test_returns_errors_when_code_is_missing(app_, db_, db_session): +def test_returns_errors_when_code_is_missing(app_, mock_check_verify_code): with app_.test_request_context(method='POST', data={}) as req: - user = set_up_test_data() - codes = verify_codes_dao.get_codes(user.id) - form = TwoFactorForm(codes) + def _check_code(code): + return users_dao.check_verify_code('1', code, "sms") + form = TwoFactorForm(_check_code) assert form.validate() is False assert len(form.errors) == 1 assert set(form.errors) == set({'sms_code': ['Code must not be empty']}) -def test_returns_errors_when_code_contains_letters(app_, db_, db_session): +def test_returns_errors_when_code_contains_letters(app_, mock_check_verify_code): with app_.test_request_context(method='POST', data={'sms_code': 'asdfg'}) as req: - user = set_up_test_data() - codes = verify_codes_dao.get_codes(user.id) - form = TwoFactorForm(codes) + def _check_code(code): + return users_dao.check_verify_code('1', code, "sms") + form = TwoFactorForm(_check_code) assert form.validate() is False assert len(form.errors) == 1 assert set(form.errors) == set({'sms_code': ['Code must be 5 digits', 'Code does not match']}) -def test_should_return_errors_when_code_is_expired(app_, db_, db_session): +def test_should_return_errors_when_code_is_expired(app_, + mock_check_verify_code_code_expired): with app_.test_request_context(method='POST', data={'sms_code': '23456'}) as req: - user = create_test_user('active') - verify_codes_dao.add_code_with_expiry(user_id=user.id, - code='23456', - code_type='sms', - expiry=datetime.now() + timedelta(hours=-2)) - codes = verify_codes_dao.get_codes(user.id) - form = TwoFactorForm(codes) + def _check_code(code): + return users_dao.check_verify_code('1', code, "sms") + form = TwoFactorForm(_check_code) assert form.validate() is False errors = form.errors assert len(errors) == 1 assert errors == {'sms_code': ['Code has expired']} - - -def set_up_test_data(): - user = create_test_user('active') - verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') - return user diff --git a/tests/app/main/test_validators.py b/tests/app/main/test_validators.py index f447b8436..551ef4f00 100644 --- a/tests/app/main/test_validators.py +++ b/tests/app/main/test_validators.py @@ -2,7 +2,7 @@ from app.main.dao import users_dao from app.main.forms import RegisterUserForm -def test_should_raise_validation_error_for_password(app_, mock_get_by_email): +def test_should_raise_validation_error_for_password(app_, mock_get_user_by_email): form = RegisterUserForm(users_dao.get_user_by_email) form.name.data = 'test' form.email_address.data = 'teset@example.gov.uk' diff --git a/tests/app/main/test_verify_form.py b/tests/app/main/test_verify_form.py index 2521a8a27..51bcf4b46 100644 --- a/tests/app/main/test_verify_form.py +++ b/tests/app/main/test_verify_form.py @@ -1,15 +1,18 @@ from datetime import datetime, timedelta -from app.main.dao import verify_codes_dao from app.main.forms import VerifyForm +from app.main.dao import users_dao from tests import create_test_user -def test_form_should_have_error_when_code_is_not_valid(app_, db_, db_session): +def test_form_should_have_error_when_code_is_not_valid(app_, + mock_check_verify_code): with app_.test_request_context(method='POST', data={'sms_code': '12345aa', 'email_code': 'abcde'}) as req: - user = set_up_test_data() - codes = verify_codes_dao.get_codes(user.id) - form = VerifyForm(codes) + + def _check_code(code, code_type): + return users_dao.check_verify_code('1', code, code_type) + + form = VerifyForm(_check_code) assert form.validate() is False errors = form.errors assert len(errors) == 2 @@ -19,12 +22,15 @@ def test_form_should_have_error_when_code_is_not_valid(app_, db_, db_session): assert set(errors) == set(expected) -def test_should_return_errors_when_code_missing(app_, db_, db_session): +def test_should_return_errors_when_code_missing(app_, + mock_check_verify_code): with app_.test_request_context(method='POST', data={}) as req: - user = set_up_test_data() - codes = verify_codes_dao.get_codes(user.id) - form = VerifyForm(codes) + + def _check_code(code, code_type): + return users_dao.check_verify_code('1', code, code_type) + + form = VerifyForm(_check_code) assert form.validate() is False errors = form.errors expected = {'sms_code': ['SMS code can not be empty'], @@ -33,12 +39,15 @@ def test_should_return_errors_when_code_missing(app_, db_, db_session): assert set(errors) == set(expected) -def test_should_return_errors_when_code_is_too_short(app_, db_, db_session): +def test_should_return_errors_when_code_is_too_short(app_, + mock_check_verify_code): with app_.test_request_context(method='POST', data={'sms_code': '123', 'email_code': '123'}) as req: - user = set_up_test_data() - codes = verify_codes_dao.get_codes(user.id) - form = VerifyForm(codes) + + def _check_code(code, code_type): + return users_dao.check_verify_code('1', code, code_type) + + form = VerifyForm(_check_code) assert form.validate() is False errors = form.errors expected = {'sms_code': ['Code must be 5 digits', 'Code does not match'], @@ -47,62 +56,36 @@ def test_should_return_errors_when_code_is_too_short(app_, db_, db_session): assert set(errors) == set(expected) -def test_should_return_errors_when_code_does_not_match(app_, db_, db_session): +def test_should_return_errors_when_code_does_not_match(app_, + mock_check_verify_code_code_not_found): with app_.test_request_context(method='POST', data={'sms_code': '34567', 'email_code': '34567'}) as req: - user = set_up_test_data() - codes = verify_codes_dao.get_codes(user.id) - form = VerifyForm(codes) + + def _check_code(code, code_type): + return users_dao.check_verify_code('1', code, code_type) + + form = VerifyForm(_check_code) assert form.validate() is False errors = form.errors - expected = {'sms_code': ['Code does not match'], - 'email_code': ['Code does not match']} + expected = {'sms_code': ['Code not found'], + 'email_code': ['Code not found']} assert len(errors) == 2 assert set(errors) == set(expected) -def test_should_return_errors_when_code_is_expired(app_, db_, db_session): +def test_should_return_errors_when_code_is_expired(app_, + mock_check_verify_code_code_expired): with app_.test_request_context(method='POST', data={'sms_code': '23456', 'email_code': '23456'}) as req: - user = create_test_user('pending') - verify_codes_dao.add_code_with_expiry(user_id=user.id, - code='23456', - code_type='sms', - expiry=datetime.now() + timedelta(hours=-2)) - verify_codes_dao.add_code_with_expiry(user_id=user.id, - code='23456', - code_type='email', - expiry=datetime.now() + timedelta(hours=-2)) - codes = verify_codes_dao.get_codes(user.id) - form = VerifyForm(codes) + def _check_code(code, code_type): + return users_dao.check_verify_code('1', code, code_type) + + form = VerifyForm(_check_code) assert form.validate() is False errors = form.errors expected = {'sms_code': ['Code has expired'], 'email_code': ['Code has expired']} assert len(errors) == 2 assert set(errors) == set(expected) - - -def test_should_return_valid_form_when_many_codes_exist(app_, - db_, - db_session): - with app_.test_request_context(method='POST', - data={'sms_code': '23456', - 'email_code': '23456'}) as req: - user = set_up_test_data() - verify_codes_dao.add_code(user_id=user.id, code='23456', code_type='email') - verify_codes_dao.add_code(user_id=user.id, code='23456', code_type='sms') - verify_codes_dao.add_code(user_id=user.id, code='60456', code_type='email') - verify_codes_dao.add_code(user_id=user.id, code='27856', code_type='sms') - codes = verify_codes_dao.get_codes(user.id) - form = VerifyForm(codes) - assert form.validate() is True - - -def set_up_test_data(): - user = create_test_user('pending') - verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='email') - verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') - return user diff --git a/tests/app/main/views/test_add_service.py b/tests/app/main/views/test_add_service.py index 952bd88d1..2ec2f4061 100644 --- a/tests/app/main/views/test_add_service.py +++ b/tests/app/main/views/test_add_service.py @@ -1,34 +1,32 @@ from flask import url_for -from app.main.dao import verify_codes_dao, services_dao -from tests import create_test_user -from app.models import User +from app.main.dao import services_dao def test_get_should_render_add_service_template(app_, - db_, - db_session, + api_user_active, mock_get_service, mock_get_services, - mock_active_user, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.add_service')) assert response.status_code == 200 assert 'Set up notifications for your service' in response.get_data(as_text=True) def test_should_add_service_and_redirect_to_next_page(app_, - db_, - db_session, mock_create_service, mock_get_services, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.post( url_for('main.add_service'), data={'name': 'testing the post'}) @@ -39,29 +37,29 @@ def test_should_add_service_and_redirect_to_next_page(app_, def test_should_return_form_errors_when_service_name_is_empty(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, mock_get_services, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.post(url_for('main.add_service'), data={}) assert response.status_code == 200 assert 'Service name can not be empty' in response.get_data(as_text=True) def test_should_return_form_errors_with_duplicate_service_name(app_, - db_, - db_session, mock_get_services, - mock_active_user, - mock_get_by_email): + mock_get_user, + api_user_active, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.post( url_for('main.add_service'), data={'name': 'service_one'}) assert response.status_code == 200 diff --git a/tests/app/main/views/test_api_keys.py b/tests/app/main/views/test_api_keys.py index 2d47fc20a..25fc0c283 100644 --- a/tests/app/main/views/test_api_keys.py +++ b/tests/app/main/views/test_api_keys.py @@ -3,27 +3,27 @@ from flask import url_for def test_should_show_api_keys_and_documentation_page(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.documentation', service_id=123)) assert response.status_code == 200 def test_should_show_empty_api_keys_page(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email, - mock_get_no_api_keys): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_get_no_api_keys, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.api_keys', service_id=123)) assert response.status_code == 200 @@ -33,14 +33,14 @@ def test_should_show_empty_api_keys_page(app_, def test_should_show_api_keys_page(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email, - mock_get_api_keys): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_get_api_keys, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.api_keys', service_id=123)) assert response.status_code == 200 @@ -51,29 +51,29 @@ def test_should_show_api_keys_page(app_, def test_should_show_name_api_key_page(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email, - mock_get_api_keys): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_get_api_keys, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.create_api_key', service_id=123)) assert response.status_code == 200 def test_should_render_show_api_key(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email, + api_user_active, + mock_get_user, + mock_get_user_by_email, mock_create_api_key, - mock_get_api_keys): + mock_get_api_keys, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.post(url_for('main.create_api_key', service_id=123), data={'key_name': 'some default key name'}) @@ -83,14 +83,14 @@ def test_should_render_show_api_key(app_, def test_should_show_confirm_revoke_api_key(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email, - mock_get_api_keys): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_get_api_keys, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.revoke_api_key', service_id=123, key_id=321)) assert response.status_code == 200 @@ -99,15 +99,15 @@ def test_should_show_confirm_revoke_api_key(app_, def test_should_redirect_after_revoking_api_key(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email, + api_user_active, + mock_get_user, + mock_get_user_by_email, mock_revoke_api_key, - mock_get_api_keys): + mock_get_api_keys, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.post(url_for('main.revoke_api_key', service_id=123, key_id=321)) assert response.status_code == 302 diff --git a/tests/app/main/views/test_choose_services.py b/tests/app/main/views/test_choose_services.py index 619beecec..7c2453198 100644 --- a/tests/app/main/views/test_choose_services.py +++ b/tests/app/main/views/test_choose_services.py @@ -1,20 +1,16 @@ 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, - active_user, - mock_get_services): + mock_get_user, + api_user_active, + mock_get_services, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - user = User.query.first() - client.login(user) + client.login(api_user_active) response = client.get(url_for('main.choose_service')) assert response.status_code == 200 diff --git a/tests/app/main/views/test_code_not_received.py b/tests/app/main/views/test_code_not_received.py index ed63fe34e..22297255c 100644 --- a/tests/app/main/views/test_code_not_received.py +++ b/tests/app/main/views/test_code_not_received.py @@ -1,18 +1,17 @@ -from app.main.dao import verify_codes_dao +import pytest from flask import url_for def test_should_render_email_code_not_received_template_and_populate_email_address(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user_by_email, + mock_send_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.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' \ @@ -21,18 +20,16 @@ def test_should_render_email_code_not_received_template_and_populate_email_addre def test_should_check_and_resend_email_code_redirect_to_verify(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, - mock_active_user, - mock_get_by_email, - mock_update_email): + api_user_active, + mock_get_user_by_email, + mock_update_user, + mock_send_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address - verify_codes_dao.add_code(mock_active_user.id, code='12345', code_type='email') + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} response = client.post(url_for('main.check_and_resend_email_code'), data={'email_address': 'test@user.gov.uk'}) assert response.status_code == 302 @@ -40,17 +37,15 @@ def test_should_check_and_resend_email_code_redirect_to_verify(app_, def test_should_render_text_code_not_received_template(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user_by_email, + mock_send_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address - verify_codes_dao.add_code(mock_active_user.id, code='12345', code_type='sms') + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} 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,18 +54,16 @@ def test_should_render_text_code_not_received_template(app_, def test_should_check_and_redirect_to_verify(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, - mock_active_user, - mock_get_by_email, - mock_update_mobile): + api_user_active, + mock_get_user_by_email, + mock_update_user, + mock_send_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address - verify_codes_dao.add_code(mock_active_user.id, code='12345', code_type='sms') + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} response = client.post(url_for('main.check_and_resend_text_code'), data={'mobile_number': '+447700900460'}) assert response.status_code == 302 @@ -78,53 +71,49 @@ def test_should_check_and_redirect_to_verify(app_, def test_should_update_email_address_resend_code(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, - mock_active_user, - mock_get_by_email, - mock_update_email): + api_user_active, + mock_get_user_by_email, + mock_update_user, + mock_send_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address - verify_codes_dao.add_code(user_id=mock_active_user.id, code='12345', code_type='email') + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} 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) - assert mock_active_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_active_user, - mock_get_by_email, - mock_update_mobile): + api_user_active, + mock_get_user_by_email, + mock_update_user, + mock_send_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address - verify_codes_dao.add_code(user_id=mock_active_user.id, code='12345', code_type='sms') + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} 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) - assert mock_active_user.mobile_number == '+447700900460' + api_user_active.mobile_number = '+447700900460' def test_should_render_verification_code_not_received(app_, - db_, - db_session, - mock_active_user): + api_user_active, + mock_send_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.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) @@ -133,37 +122,30 @@ def test_should_render_verification_code_not_received(app_, def test_check_and_redirect_to_two_factor(app_, - db_, - db_session, - mock_active_user, - mock_send_sms, - mock_send_email, - mock_get_by_email): + api_user_active, + mock_get_user_by_email, + mock_send_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} response = client.get(url_for('main.check_and_resend_verification_code')) assert response.status_code == 302 assert response.location == url_for('main.two_factor', _external=True) def test_should_create_new_code_for_user(app_, - db_, - db_session, - mock_active_user, - mock_send_sms, - mock_send_email, - mock_get_by_email): + api_user_active, + mock_get_user_by_email, + mock_send_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address - verify_codes_dao.add_code(user_id=mock_active_user.id, code='12345', code_type='sms') + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} response = client.get(url_for('main.check_and_resend_verification_code')) assert response.status_code == 302 assert response.location == url_for('main.two_factor', _external=True) - codes = verify_codes_dao.get_codes(user_id=mock_active_user.id, code_type='sms') - assert len(codes) == 2 - for x in ([used.code_used for used in codes]): - assert x is False diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index 6bbeccb11..a4036b103 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -1,16 +1,16 @@ -from app.models import User from flask import url_for def test_should_show_recent_jobs_on_dashboard(app_, - db_, - db_session, - mock_api_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): + with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_api_user) + client.login(api_user_active) response = client.get(url_for('main.service_dashboard', service_id=123)) assert response.status_code == 200 diff --git a/tests/app/main/views/test_forgot_password.py b/tests/app/main/views/test_forgot_password.py index 2c70eb12f..65b9f55ee 100644 --- a/tests/app/main/views/test_forgot_password.py +++ b/tests/app/main/views/test_forgot_password.py @@ -1,7 +1,7 @@ from flask import url_for -def test_should_render_forgot_password(app_, db_, db_session): +def test_should_render_forgot_password(app_): with app_.test_request_context(): response = app_.test_client().get(url_for('.forgot_password')) assert response.status_code == 200 @@ -10,18 +10,15 @@ def test_should_render_forgot_password(app_, db_, db_session): def test_should_redirect_to_password_reset_sent_and_state_updated(app_, - db_, - db_session, - mock_send_email, - mock_active_user, - mock_get_by_email, - mock_password_reset): + api_user_active, + mock_get_user_by_email, + mock_update_user, + mock_send_email): with app_.test_request_context(): response = app_.test_client().post( url_for('.forgot_password'), - data={'email_address': mock_active_user.email_address}) + data={'email_address': api_user_active.email_address}) assert response.status_code == 200 assert ( 'You have been sent an email containing a link' ' to reset your password.') in response.get_data(as_text=True) - assert mock_active_user.state == 'request_password_reset' diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index dc5e524c1..3f25ae0c9 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -2,13 +2,13 @@ from flask import url_for def test_logged_in_user_redirects_to_choose_service(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.index')) assert response.status_code == 302 diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index 3ac620fd0..ec084dc3b 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -2,14 +2,14 @@ from flask import url_for def test_should_return_list_of_all_jobs(app_, - db_, - db_session, service_one, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.view_jobs', service_id=101)) assert response.status_code == 200 @@ -17,17 +17,17 @@ def test_should_return_list_of_all_jobs(app_, def test_should_show_page_for_one_job(app_, - db_, - db_session, service_one, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_login, + mock_get_user, + mock_get_user_by_email): 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' - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.view_job', service_id=123, job_id=456)) assert response.status_code == 200 @@ -36,14 +36,14 @@ def test_should_show_page_for_one_job(app_, def test_should_show_page_for_one_notification(app_, - db_, - db_session, service_one, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for( 'main.view_notification', service_id=101, diff --git a/tests/app/main/views/test_new_password.py b/tests/app/main/views/test_new_password.py index 2a1b3d2e4..22c934cbc 100644 --- a/tests/app/main/views/test_new_password.py +++ b/tests/app/main/views/test_new_password.py @@ -8,24 +8,22 @@ import pytest def test_should_render_new_password_template(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_login, + mock_send_verify_code, + mock_get_user_by_email_request_password_reset): with app_.test_request_context(): with app_.test_client() as client: - mock_active_user.state = 'request_password_reset' - token = generate_token(mock_active_user.email_address) + token = generate_token(api_user_active.email_address) response = client.get(url_for('.new_password', token=token)) + print(response.location) assert response.status_code == 200 assert ' You can now create a new password for your account.' in response.get_data(as_text=True) @pytest.mark.skipif(True, reason='Password reset no implemented') def test_should_render_new_password_template_with_message_of_bad_token(app_, - db_, - db_session, - mock_get_by_email): + mock_get_user_by_email): with app_.test_request_context(): with app_.test_client() as client: token = generate_token('no_user@d.gov.uk') @@ -37,32 +35,25 @@ def test_should_render_new_password_template_with_message_of_bad_token(app_, @pytest.mark.skipif(True, reason='Password reset no implemented') def test_should_redirect_to_two_factor_when_password_reset_is_successful(app_, - db_, - db_session, - mock_send_sms, - mock_active_user, - mock_get_by_email): + mock_get_user_by_email_request_password_reset, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - mock_active_user.state = 'request_password_reset' - token = generate_token(mock_active_user.email_address) + user = mock_get_user_by_email_request_password_reset.return_value + token = generate_token(user.email_address) response = client.post(url_for('.new_password', token=token), data={'new_password': 'a-new_password'}) assert response.status_code == 302 assert response.location == url_for('.two_factor', _external=True) - saved_user = users_dao.get_user_by_id(user.id) - assert check_hash('a-new_password', saved_user.password) - assert saved_user.state == 'active' -def test_should_redirect_to_forgot_password_with_flash_message_when_token_is_expired(app_, - db_, - db_session, - mock_active_user): +def test_should_redirect_to_forgot_password_with_flash_message_when_token_is_expired( + app_, mock_get_user_by_email_request_password_reset, mock_login +): with app_.test_request_context(): with app_.test_client() as client: app_.config['TOKEN_MAX_AGE_SECONDS'] = -1000 - mock_active_user.state = 'request_password_reset' - token = generate_token(mock_active_user.email_address) + user = mock_get_user_by_email_request_password_reset.return_value + token = generate_token(user.email_address) response = client.post(url_for('.new_password', token=token), data={'new_password': 'a-new_password'}) assert response.status_code == 302 assert response.location == url_for('.forgot_password', _external=True) @@ -70,15 +61,13 @@ def test_should_redirect_to_forgot_password_with_flash_message_when_token_is_exp @pytest.mark.skipif(True, reason='Password reset no implemented') -def test_should_redirect_to_forgot_pass_when_user_active_should_be_request_passw_reset(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): +def test_should_redirect_to_forgot_pass_when_user_active_should_be_request_passw_reset( + app_, mock_get_user_by_email_request_password_reset, mock_login +): with app_.test_request_context(): with app_.test_client() as client: - mock_active_user.state = 'request_password_reset' - token = generate_token(mock_active_user.email_address) + user = mock_get_user_by_email_request_password_reset.return_value + token = generate_token(user.email_address) response = client.post(url_for('.new_password', token=token), data={'new_password': 'a-new_password'}) assert response.status_code == 302 assert response.location == url_for('.index', _external=True) diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index 4a7d55675..9f5b373dd 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -1,7 +1,7 @@ from flask import url_for -def test_render_register_returns_template_with_form(app_, db_, db_session): +def test_render_register_returns_template_with_form(app_): response = app_.test_client().get('/register') assert response.status_code == 200 @@ -9,13 +9,13 @@ def test_render_register_returns_template_with_form(app_, db_, db_session): def test_logged_in_user_redirects_to_choose_service(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user_by_email, + mock_send_verify_code, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.register')) assert response.status_code == 302 @@ -24,12 +24,10 @@ def test_logged_in_user_redirects_to_choose_service(app_, def test_process_register_creates_new_user(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, + mock_send_verify_code, mock_register_user, - mock_get_by_email): + mock_get_user_by_email_not_found, + mock_login): user_data = { 'name': 'Some One Valid', 'email_address': 'notfound@example.gov.uk', @@ -38,7 +36,7 @@ def test_process_register_creates_new_user(app_, } with app_.test_request_context(): - response = app_.test_client().post('/register', + response = app_.test_client().post(url_for('main.register'), data=user_data) assert response.status_code == 302 assert response.location == url_for('main.verify', _external=True) @@ -46,44 +44,41 @@ def test_process_register_creates_new_user(app_, def test_process_register_returns_400_when_mobile_number_is_invalid(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, - mock_get_by_email): - response = app_.test_client().post('/register', - data={'name': 'Bad Mobile', - 'email_address': 'bad_mobile@example.gov.uk', - 'mobile_number': 'not good', - 'password': 'validPassword!'}) + mock_send_verify_code, + mock_get_user_by_email_not_found, + mock_login): + with app_.test_request_context(): + response = app_.test_client().post(url_for('main.register'), + data={'name': 'Bad Mobile', + 'email_address': 'bad_mobile@example.gov.uk', + 'mobile_number': 'not good', + 'password': 'validPassword!'}) assert response.status_code == 200 assert 'Must be a UK mobile number (eg 07700 900460)' in response.get_data(as_text=True) def test_should_return_400_when_email_is_not_gov_uk(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, - mock_get_by_email): - response = app_.test_client().post('/register', - data={'name': 'Bad Mobile', - 'email_address': 'bad_mobile@example.not.right', - 'mobile_number': '+44123412345', - 'password': 'validPassword!'}) + mock_send_verify_code, + mock_get_user_by_email, + mock_login): + with app_.test_request_context(): + response = app_.test_client().post(url_for('main.register'), + data={'name': 'Bad Mobile', + 'email_address': 'bad_mobile@example.not.right', + 'mobile_number': '+44123412345', + 'password': 'validPassword!'}) assert response.status_code == 200 assert 'Enter a gov.uk email address' in response.get_data(as_text=True) def test_should_add_verify_codes_on_session(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, + mock_send_verify_code, mock_register_user, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email_not_found, + mock_login): user_data = { 'name': 'Test Codes', 'email_address': 'notfound@example.gov.uk', @@ -91,22 +86,23 @@ def test_should_add_verify_codes_on_session(app_, 'password': 'validPassword!' } - with app_.test_client() as client: - response = client.post('/register', - data=user_data) - assert response.status_code == 302 - assert 'notify_admin_session' in response.headers.get('Set-Cookie') + with app_.test_request_context(): + with app_.test_client() as client: + response = client.post(url_for('main.register'), + data=user_data) + assert response.status_code == 302 + assert 'notify_admin_session' in response.headers.get('Set-Cookie') def test_should_return_400_if_password_is_blacklisted(app_, - db_, - db_session, - mock_get_by_email): - response = app_.test_client().post('/register', - data={'name': 'Bad Mobile', - 'email_address': 'bad_mobile@example.not.right', - 'mobile_number': '+44123412345', - 'password': 'password1234'}) + mock_get_user_by_email, + mock_login): + with app_.test_request_context(): + response = app_.test_client().post(url_for('main.register'), + data={'name': 'Bad Mobile', + 'email_address': 'bad_mobile@example.not.right', + 'mobile_number': '+44123412345', + 'password': 'password1234'}) response.status_code == 200 assert 'That password is blacklisted, too common' in response.get_data(as_text=True) diff --git a/tests/app/main/views/test_service_settings.py b/tests/app/main/views/test_service_settings.py index 77e7a0f89..0f87134fd 100644 --- a/tests/app/main/views/test_service_settings.py +++ b/tests/app/main/views/test_service_settings.py @@ -2,14 +2,14 @@ from flask import (url_for, session) def test_should_show_overview(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.get(url_for( 'main.service_settings', service_id=service_id)) @@ -21,14 +21,14 @@ def test_should_show_overview(app_, def test_should_show_service_name(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.get(url_for( 'main.service_name_change', service_id=service_id)) @@ -40,14 +40,14 @@ def test_should_show_service_name(app_, def test_should_redirect_after_change_service_name(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.post(url_for( 'main.service_name_change', service_id=service_id)) @@ -60,14 +60,14 @@ def test_should_redirect_after_change_service_name(app_, def test_should_show_service_name_confirmation(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.get(url_for( 'main.service_name_change_confirm', service_id=service_id)) @@ -79,15 +79,16 @@ def test_should_show_service_name_confirmation(app_, def test_should_redirect_after_service_name_confirmation(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, mock_update_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login, + mock_verify_password): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 service_new_name = 'New Name' with client.session_transaction() as session: @@ -105,14 +106,14 @@ def test_should_redirect_after_service_name_confirmation(app_, def test_should_show_request_to_go_live(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.get( url_for('main.service_request_to_go_live', service_id=service_id)) @@ -124,15 +125,15 @@ def test_should_show_request_to_go_live(app_, def test_should_redirect_after_request_to_go_live(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, mock_update_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.post(url_for( 'main.service_request_to_go_live', service_id=service_id)) @@ -146,14 +147,14 @@ def test_should_redirect_after_request_to_go_live(app_, def test_should_show_status_page(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.get(url_for( 'main.service_status_change', service_id=service_id)) @@ -165,14 +166,14 @@ def test_should_show_status_page(app_, def test_should_show_redirect_after_status_change(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.post(url_for( 'main.service_status_change', service_id=service_id)) @@ -185,14 +186,14 @@ def test_should_show_redirect_after_status_change(app_, def test_should_show_status_confirmation(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.get(url_for( 'main.service_status_change_confirm', service_id=service_id)) @@ -204,15 +205,16 @@ def test_should_show_status_confirmation(app_, def test_should_redirect_after_status_confirmation(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, mock_update_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login, + mock_verify_password): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.post(url_for( 'main.service_status_change_confirm', service_id=service_id)) @@ -226,14 +228,14 @@ def test_should_redirect_after_status_confirmation(app_, def test_should_show_delete_page(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.get(url_for( 'main.service_delete', service_id=service_id)) @@ -244,14 +246,14 @@ def test_should_show_delete_page(app_, def test_should_show_redirect_after_deleting_service(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.post(url_for( 'main.service_delete', service_id=service_id)) @@ -263,14 +265,14 @@ def test_should_show_redirect_after_deleting_service(app_, def test_should_show_delete_confirmation(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.get(url_for( 'main.service_delete_confirm', service_id=service_id)) @@ -281,15 +283,16 @@ def test_should_show_delete_confirmation(app_, def test_should_redirect_delete_confirmation(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service, mock_delete_service, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login, + mock_verify_password): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.post(url_for( 'main.service_delete_confirm', service_id=service_id)) diff --git a/tests/app/main/views/test_sign_in.py b/tests/app/main/views/test_sign_in.py index 613cd62de..a1eeb3f1f 100644 --- a/tests/app/main/views/test_sign_in.py +++ b/tests/app/main/views/test_sign_in.py @@ -1,3 +1,8 @@ + +from datetime import datetime + +from app.main.dao import users_dao + from flask import url_for @@ -12,13 +17,12 @@ def test_render_sign_in_returns_sign_in_template(app_): def test_logged_in_user_redirects_to_choose_service(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.get(url_for('main.sign_in')) assert response.status_code == 302 @@ -27,13 +31,11 @@ def test_logged_in_user_redirects_to_choose_service(app_, def test_process_sign_in_return_2fa_template(app_, - db_, - db_session, - mock_send_sms, - mock_send_email, + mock_send_verify_code, mock_get_user, - mock_get_by_email, - mock_user_checkpassword): + mock_get_user_by_email, + mock_verify_password): + with app_.test_request_context(): response = app_.test_client().post( url_for('main.sign_in'), data={ @@ -43,31 +45,18 @@ def test_process_sign_in_return_2fa_template(app_, assert response.location == 'http://localhost/two-factor' -def test_should_return_locked_out_true_when_user_is_locked(app_, mock_get_by_email): +def test_should_return_locked_out_true_when_user_is_locked(app_, + mock_get_user_by_email_locked): with app_.test_request_context(): - for _ in range(10): - app_.test_client().post( - url_for('main.sign_in'), data={ - 'email_address': 'locked_user@example.gov.uk', - 'password': 'whatIsMyPassword!'}) - - response = app_.test_client().post( - url_for('main.sign_in'), data={ - 'email_address': 'valid@example.gov.uk', - 'password': 'val1dPassw0rd!'}) - - assert response.status_code == 200 - assert 'Username or password is incorrect' in response.get_data(as_text=True) - - another_bad_attempt = app_.test_client().post( + resp = app_.test_client().post( url_for('main.sign_in'), data={ 'email_address': 'valid@example.gov.uk', 'password': 'whatIsMyPassword!'}) - assert another_bad_attempt.status_code == 200 - assert 'Username or password is incorrect' in response.get_data(as_text=True) + assert resp.status_code == 200 + assert 'Username or password is incorrect' in resp.get_data(as_text=True) -def test_should_return_active_user_is_false_if_user_is_inactive(app_, mock_get_by_email): +def test_should_return_active_user_is_false_if_user_is_inactive(app_, mock_get_user_by_email_inactive): with app_.test_request_context(): response = app_.test_client().post( @@ -79,7 +68,7 @@ def test_should_return_active_user_is_false_if_user_is_inactive(app_, mock_get_b assert 'Username or password is incorrect' in response.get_data(as_text=True) -def test_should_return_200_when_user_does_not_exist(app_, mock_get_by_email): +def test_should_return_200_when_user_does_not_exist(app_, mock_get_user_by_email_not_found): with app_.test_request_context(): response = app_.test_client().post( url_for('main.sign_in'), data={ @@ -89,7 +78,7 @@ def test_should_return_200_when_user_does_not_exist(app_, mock_get_by_email): assert 'Username or password is incorrect' in response.get_data(as_text=True) -def test_should_return_200_when_user_is_not_active(app_, mock_get_by_email): +def test_should_return_200_when_user_is_pending(app_, mock_get_user_by_email_pending): with app_.test_request_context(): response = app_.test_client().post( url_for('main.sign_in'), data={ diff --git a/tests/app/main/views/test_sign_out.py b/tests/app/main/views/test_sign_out.py index a433b28ac..aecc472c5 100644 --- a/tests/app/main/views/test_sign_out.py +++ b/tests/app/main/views/test_sign_out.py @@ -1,7 +1,6 @@ from datetime import datetime from flask import url_for from app.main.dao import users_dao -from app.models import User def test_render_sign_out_redirects_to_sign_in(app_): @@ -14,16 +13,16 @@ def test_render_sign_out_redirects_to_sign_in(app_): def test_sign_out_user(app_, - mock_send_sms, - mock_send_email, mock_get_service, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): email = 'valid@example.gov.uk' password = 'val1dPassw0rd!' with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) # Check we are logged in response = client.get( url_for('main.service_dashboard', service_id="123")) diff --git a/tests/app/main/views/test_sms.py b/tests/app/main/views/test_sms.py index 7285b4b0d..ef90289bf 100644 --- a/tests/app/main/views/test_sms.py +++ b/tests/app/main/views/test_sms.py @@ -4,35 +4,14 @@ from flask import url_for import moto -def test_choose_template_page(app_, - db_, - db_session, - mock_send_sms, - mock_active_user, - mock_get_by_email, - mock_get_service_templates): - with app_.test_request_context(): - with app_.test_client() as client: - client.login(mock_active_user) - upload_data = {'file': (BytesIO(''.encode('utf-8')), 'emtpy.csv')} - response = client.get(url_for('main.send_sms', service_id=123)) - - assert response.status_code == 200 - content = response.get_data(as_text=True) - assert 'template_one' in content - assert 'template one content' in content - - def test_upload_empty_csvfile_returns_to_upload_page(app_, - db_, - db_session, - mock_send_sms, - mock_active_user, - mock_get_by_email, - mock_get_service_templates): + api_user_active, + mock_get_user, + mock_get_service_templates, + mock_check_verify_code): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) upload_data = {'file': (BytesIO(''.encode('utf-8')), 'emtpy.csv')} response = client.post(url_for('main.send_sms', service_id=123), data=upload_data, follow_redirects=True) @@ -44,16 +23,18 @@ def test_upload_empty_csvfile_returns_to_upload_page(app_, @moto.mock_s3 def test_upload_csvfile_with_invalid_phone_shows_check_page_with_errors(app_, - mock_active_user, - mock_get_by_email, - mock_get_service_template): + mocker, + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): contents = 'phone\n+44 123\n+44 456' file_data = (BytesIO(contents.encode('utf-8')), 'invalid.csv') with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) upload_data = {'file': file_data} response = client.post(url_for('main.send_sms', service_id=123), data=upload_data, @@ -69,16 +50,17 @@ def test_upload_csvfile_with_invalid_phone_shows_check_page_with_errors(app_, @moto.mock_s3 def test_upload_csvfile_with_valid_phone_shows_first3_and_last3_numbers(app_, mocker, - mock_active_user, - mock_get_by_email): - + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): 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 file_data = (BytesIO(contents.encode('utf-8')), 'valid.csv') with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) upload_data = {'file': file_data} response = client.post(url_for('main.send_sms', service_id=123), data=upload_data, @@ -104,8 +86,10 @@ def test_upload_csvfile_with_valid_phone_shows_first3_and_last3_numbers(app_, @moto.mock_s3 def test_upload_csvfile_with_valid_phone_shows_all_if_6_or_less_numbers(app_, mocker, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): 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 @@ -113,7 +97,7 @@ def test_upload_csvfile_with_valid_phone_shows_all_if_6_or_less_numbers(app_, with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) upload_data = {'file': file_data} response = client.post(url_for('main.send_sms', service_id=123), data=upload_data, @@ -134,11 +118,13 @@ 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_, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) response = client.post(url_for('main.check_sms', service_id=123, upload_id='someid')) diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index 023d5f851..8e4ff9d2a 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -3,14 +3,14 @@ from flask import url_for def test_should_return_list_of_all_templates(app_, - db_, - db_session, - mock_active_user, + api_user_active, mock_get_service_templates, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_active_user) + client.login(api_user_active) service_id = 123 response = client.get(url_for( '.manage_service_templates', service_id=service_id)) @@ -20,14 +20,14 @@ def test_should_return_list_of_all_templates(app_, def test_should_show_page_for_one_templates(app_, - db_, - db_session, - mock_api_user, + api_user_active, mock_get_service_template, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_api_user) + client.login(api_user_active) service_id = 123 template_id = 456 response = client.get(url_for( @@ -43,15 +43,15 @@ def test_should_show_page_for_one_templates(app_, def test_should_redirect_when_saving_a_template(app_, - db_, - db_session, - mock_api_user, + api_user_active, mock_get_service_template, mock_update_service_template, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_api_user) + client.login(api_user_active) service_id = 123 template_id = 456 name = "new name" @@ -77,14 +77,14 @@ def test_should_redirect_when_saving_a_template(app_, def test_should_show_delete_template_page(app_, - db_, - db_session, - mock_api_user, + api_user_active, mock_get_service_template, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_api_user) + client.login(api_user_active) service_id = 123 template_id = 456 response = client.get(url_for( @@ -99,15 +99,15 @@ def test_should_show_delete_template_page(app_, def test_should_redirect_when_deleting_a_template(app_, - db_, - db_session, - mock_api_user, + api_user_active, mock_get_service_template, mock_delete_service_template, - mock_get_by_email): + mock_get_user, + mock_get_user_by_email, + mock_login): with app_.test_request_context(): with app_.test_client() as client: - client.login(mock_api_user) + client.login(api_user_active) service_id = 123 template_id = 456 name = "new name" diff --git a/tests/app/main/views/test_two_factor.py b/tests/app/main/views/test_two_factor.py index 513e74330..274306923 100644 --- a/tests/app/main/views/test_two_factor.py +++ b/tests/app/main/views/test_two_factor.py @@ -1,34 +1,35 @@ from flask import url_for -from app.main.dao import verify_codes_dao +from tests import create_test_user def test_should_render_two_factor_page(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user_by_email): 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: - session['user_email'] = mock_active_user.email_address + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} response = client.get(url_for('main.two_factor')) assert response.status_code == 200 assert '''We've sent you a text message with a verification code.''' in response.get_data(as_text=True) def test_should_login_user_and_redirect_to_dashboard(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_check_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address - verify_codes_dao.add_code(user_id=mock_active_user.id, code='12345', code_type='sms') + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} response = client.post(url_for('main.two_factor'), data={'sms_code': '12345'}) @@ -37,37 +38,32 @@ def test_should_login_user_and_redirect_to_dashboard(app_, def test_should_return_200_with_sms_code_error_when_sms_code_is_wrong(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user_by_email, + mock_check_verify_code_code_not_found): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address - verify_codes_dao.add_code(user_id=mock_active_user.id, code='12345', code_type='sms') + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} response = client.post(url_for('main.two_factor'), data={'sms_code': '23456'}) assert response.status_code == 200 - assert 'Code does not match' in response.get_data(as_text=True) + assert 'Code not found' in response.get_data(as_text=True) def test_should_login_user_when_multiple_valid_codes_exist(app_, - db_, - db_session, - mock_active_user, - mock_get_by_email): + api_user_active, + mock_get_user, + mock_get_user_by_email, + mock_check_verify_code): with app_.test_request_context(): with app_.test_client() as client: with client.session_transaction() as session: - session['user_email'] = mock_active_user.email_address - verify_codes_dao.add_code(user_id=mock_active_user.id, code='23456', code_type='sms') - verify_codes_dao.add_code(user_id=mock_active_user.id, code='12345', code_type='sms') - verify_codes_dao.add_code(user_id=mock_active_user.id, code='34567', code_type='sms') - assert len(verify_codes_dao.get_codes(user_id=mock_active_user.id, code_type='sms')) == 3 + session['user_details'] = { + 'id': api_user_active.id, + 'email': api_user_active.email_address} response = client.post(url_for('main.two_factor'), data={'sms_code': '23456'}) assert response.status_code == 302 - codes = verify_codes_dao.get_codes(user_id=mock_active_user.id, code_type='sms') - # query will only return codes where code_used == False - assert len(codes) == 0 diff --git a/tests/app/main/views/test_user_profile.py b/tests/app/main/views/test_user_profile.py index b2a8f278b..58ce2bbed 100644 --- a/tests/app/main/views/test_user_profile.py +++ b/tests/app/main/views/test_user_profile.py @@ -1,121 +1,293 @@ -def test_should_show_overview_page(app_): - response = app_.test_client().get('/user-profile') - - assert 'Your profile' in response.get_data(as_text=True) - assert response.status_code == 200 +import json +from flask import url_for -def test_should_show_name_page(app_): - response = app_.test_client().get('/user-profile/name') +def test_should_show_overview_page(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.get(url_for('main.user_profile')) - assert 'Change your name' in response.get_data(as_text=True) - assert response.status_code == 200 + assert 'Your profile' in response.get_data(as_text=True) + assert response.status_code == 200 -def test_should_redirect_after_name_change(app_): - response = app_.test_client().post('/user-profile/name') +def test_should_show_name_page(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.get(url_for('main.user_profile_name')) - assert response.status_code == 302 - assert response.location == 'http://localhost/user-profile' + assert 'Change your name' in response.get_data(as_text=True) + assert response.status_code == 200 -def test_should_show_email_page(app_): - response = app_.test_client().get('/user-profile/email') +def test_should_redirect_after_name_change(app_, + api_user_active, + mock_login, + mock_update_user, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + new_name = 'New Name' + data = {'new_name': new_name} + response = client.post(url_for( + 'main.user_profile_name'), data=data) - assert 'Change your email address' in response.get_data(as_text=True) - assert response.status_code == 200 + assert response.status_code == 302 + assert response.location == url_for( + 'main.user_profile', _external=True) + api_user_active.name = new_name + assert mock_update_user.called -def test_should_redirect_after_email_change(app_): - response = app_.test_client().post('/user-profile/email') +def test_should_show_email_page(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.get(url_for( + 'main.user_profile_email')) - assert response.status_code == 302 - assert response.location == 'http://localhost/user-profile/email/authenticate' + assert 'Change your email address' in response.get_data(as_text=True) + assert response.status_code == 200 -def test_should_show_authenticate_after_email_change(app_): - response = app_.test_client().get('/user-profile/email/authenticate') +def test_should_redirect_after_email_change(app_, + api_user_active, + mock_login, + mock_get_user, + mock_get_user_by_email_not_found): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + data = {'email_address': 'new_notify@notify.gov.uk'} + response = client.post( + url_for('main.user_profile_email'), + data=data) - assert 'Change your email address' in response.get_data(as_text=True) - assert 'Confirm' in response.get_data(as_text=True) - assert response.status_code == 200 + assert response.status_code == 302 + assert response.location == url_for( + 'main.user_profile_email_authenticate', _external=True) -def test_should_redirect_after_email_change_confirm(app_): - response = app_.test_client().post('/user-profile/email/authenticate') +def test_should_show_authenticate_after_email_change(app_, + api_user_active, + mock_login, + mock_get_user, + mock_verify_password): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + with client.session_transaction() as session: + session['new-email'] = 'new_notify@notify.gov.uk' + response = client.get(url_for('main.user_profile_email_authenticate')) - assert response.status_code == 302 - assert response.location == 'http://localhost/user-profile/email/confirm' + assert 'Change your email address' in response.get_data(as_text=True) + assert 'Confirm' in response.get_data(as_text=True) + assert response.status_code == 200 -def test_should_show_confirm_after_email_change(app_): - response = app_.test_client().get('/user-profile/email/confirm') +def test_should_redirect_after_email_change_confirm(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + data = {'email-code': '12345'} + with client.session_transaction() as session: + session['new-email'] = 'new_notify@notify.gov.uk' + response = client.post( + url_for('main.user_profile_email_authenticate'), + data=data) - assert 'Change your email address' in response.get_data(as_text=True) - assert 'Confirm' in response.get_data(as_text=True) - assert response.status_code == 200 + assert response.status_code == 302 + assert response.location == url_for( + 'main.user_profile_email_confirm', _external=True) -def test_should_redirect_after_email_change_confirm(app_): - response = app_.test_client().post('/user-profile/email/confirm') +def test_should_show_confirm_after_email_change(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + with client.session_transaction() as session: + session['new-email-password-confirmed'] = True + response = client.get(url_for('main.user_profile_email_confirm')) - assert response.status_code == 302 - assert response.location == 'http://localhost/user-profile' + assert 'Change your email address' in response.get_data(as_text=True) + assert 'Confirm' in response.get_data(as_text=True) + assert response.status_code == 200 -def test_should_show_mobile_number_page(app_): - response = app_.test_client().get('/user-profile/mobile-number') +def test_should_redirect_after_email_change_confirm(app_, + api_user_active, + mock_login, + mock_get_user, + mock_check_verify_code): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + with client.session_transaction() as session: + session['new-email-password-confirmed'] = True + session['new-email'] = 'new_notify@notify.gov.uk' + data = {'email_code': '12345'} + response = client.post( + url_for('main.user_profile_email_confirm'), + data=data) - assert 'Change your mobile number' in response.get_data(as_text=True) - assert response.status_code == 200 + assert response.status_code == 302 + assert response.location == url_for( + 'main.user_profile', _external=True) -def test_should_redirect_after_mobile_number_change(app_): - response = app_.test_client().post('/user-profile/email') +def test_should_show_mobile_number_page(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.get(url_for('main.user_profile_mobile_number')) - assert response.status_code == 302 - assert response.location == 'http://localhost/user-profile/email/authenticate' + assert 'Change your mobile number' in response.get_data(as_text=True) + assert response.status_code == 200 -def test_should_show_authenticate_after_mobile_number_change(app_): - response = app_.test_client().get('/user-profile/mobile-number/authenticate') - - assert 'Change your mobile number' in response.get_data(as_text=True) - assert 'Confirm' in response.get_data(as_text=True) - assert response.status_code == 200 +def test_should_redirect_after_mobile_number_change(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + data = {'mobile_number': '07121231234'} + response = client.post( + url_for('main.user_profile_mobile_number'), + data=data) + assert response.status_code == 302 + assert response.location == url_for( + 'main.user_profile_mobile_number_authenticate', _external=True) -def test_should_redirect_after_mobile_number_authenticate(app_): - response = app_.test_client().post('/user-profile/mobile-number/authenticate') +def test_should_show_authenticate_after_mobile_number_change(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + with client.session_transaction() as session: + session['new-mob'] = '+441234123123' + response = client.get( + url_for('main.user_profile_mobile_number_authenticate')) - assert response.status_code == 302 - assert response.location == 'http://localhost/user-profile/mobile-number/confirm' + assert 'Change your mobile number' in response.get_data(as_text=True) + assert 'Confirm' in response.get_data(as_text=True) + assert response.status_code == 200 -def test_should_show_confirm_after_mobile_number_change(app_): - response = app_.test_client().get('/user-profile/mobile-number/confirm') +def test_should_redirect_after_mobile_number_authenticate(app_, + api_user_active, + mock_login, + mock_get_user, + mock_verify_password, + mock_send_verify_code): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + with client.session_transaction() as session: + session['new-mob'] = '+441234123123' + data = {'password': '12345667'} + response = client.post( + url_for('main.user_profile_mobile_number_authenticate'), + data=data) - assert 'Change your mobile number' in response.get_data(as_text=True) - assert 'Confirm' in response.get_data(as_text=True) - assert response.status_code == 200 + assert response.status_code == 302 + assert response.location == url_for( + 'main.user_profile_mobile_number_confirm', _external=True) -def test_should_redirect_after_mobile_number_confirm(app_): - response = app_.test_client().post('/user-profile/mobile-number/confirm') +def test_should_show_confirm_after_mobile_number_change(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + with client.session_transaction() as session: + session['new-mob-password-confirmed'] = True + response = client.get( + url_for('main.user_profile_mobile_number_confirm')) - assert response.status_code == 302 - assert response.location == 'http://localhost/user-profile' + assert 'Change your mobile number' in response.get_data(as_text=True) + assert 'Confirm' in response.get_data(as_text=True) + assert response.status_code == 200 -def test_should_show_password_page(app_): - response = app_.test_client().get('/user-profile/password') - - assert 'Change your password' in response.get_data(as_text=True) - assert response.status_code == 200 +def test_should_redirect_after_mobile_number_confirm(app_, + api_user_active, + mock_login, + mock_get_user, + mock_check_verify_code): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + with client.session_transaction() as session: + session['new-mob-password-confirmed'] = True + session['new-mob'] = '+441234123123' + data = {'sms_code': '12345'} + response = client.post( + url_for('main.user_profile_mobile_number_confirm'), + data=data) + print(response.get_data(as_text=True)) + assert response.status_code == 302 + assert response.location == url_for( + 'main.user_profile', _external=True) -def test_should_redirect_after_password_change(app_): - response = app_.test_client().post('/user-profile/password') +def test_should_show_password_page(app_, + api_user_active, + mock_login, + mock_get_user): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.get(url_for('main.user_profile_password')) - assert response.status_code == 302 - assert response.location == 'http://localhost/user-profile' + assert 'Change your password' in response.get_data(as_text=True) + assert response.status_code == 200 + + +def test_should_redirect_after_password_change(app_, + api_user_active, + mock_login, + mock_get_user, + mock_update_user, + mock_verify_password): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + data = { + 'new_password': '1234567890', + 'old_password': '4567676328'} + response = client.post( + url_for('main.user_profile_password'), + data=data) + + print(response.get_data(as_text=True)) + assert response.status_code == 302 + assert response.location == url_for( + 'main.user_profile', _external=True) diff --git a/tests/app/main/views/test_verify.py b/tests/app/main/views/test_verify.py index 902ddc33d..11a9e6adf 100644 --- a/tests/app/main/views/test_verify.py +++ b/tests/app/main/views/test_verify.py @@ -1,14 +1,19 @@ -from flask import url_for -from app.main.dao import verify_codes_dao +from flask import json, url_for +from app.main.dao import users_dao +from tests import create_test_api_user + +import pytest -def test_should_return_verify_template(app_, db_, db_session, mock_api_user): +def test_should_return_verify_template(app_, + api_user_active, + mock_send_verify_code): 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: - session['user_details'] = {'email_address': mock_api_user.email_address, 'id': mock_api_user.id} + session['user_details'] = {'email_address': api_user_active.email_address, 'id': api_user_active.id} response = client.get(url_for('main.verify')) assert response.status_code == 200 assert ( @@ -17,17 +22,14 @@ def test_should_return_verify_template(app_, db_, db_session, mock_api_user): def test_should_redirect_to_add_service_when_code_are_correct(app_, - db_, - db_session, - mock_api_user, + api_user_active, mock_get_user, - mock_activate_user): + mock_update_user, + mock_check_verify_code): 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') + session['user_details'] = {'email_address': api_user_active.email_address, 'id': api_user_active.id} response = client.post(url_for('main.verify'), data={'sms_code': '12345', 'email_code': '23456'}) @@ -35,54 +37,33 @@ def test_should_redirect_to_add_service_when_code_are_correct(app_, assert response.location == url_for('main.add_service', first='first', _external=True) -def test_should_activate_user_after_verify(app_, db_, db_session, mock_api_user, mock_activate_user, mock_get_user): +def test_should_activate_user_after_verify(app_, + api_user_active, + mock_get_user, + mock_send_verify_code, + mock_check_verify_code, + mock_update_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') + session['user_details'] = {'email_address': api_user_active.email_address, 'id': api_user_active.id} client.post(url_for('main.verify'), data={'sms_code': '12345', 'email_code': '23456'}) - assert mock_api_user.state == 'active' + assert mock_update_user.called -def test_should_return_200_when_codes_are_wrong(app_, db_, db_session, mock_api_user, mock_get_user): +def test_should_return_200_when_codes_are_wrong(app_, + api_user_active, + mock_get_user, + mock_check_verify_code_code_not_found): 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='23345', code_type='sms') - verify_codes_dao.add_code(user_id=mock_api_user.id, code='98456', code_type='email') + session['user_details'] = {'email_address': api_user_active.email_address, 'id': api_user_active.id} response = client.post(url_for('main.verify'), data={'sms_code': '12345', 'email_code': '23456'}) - print(response.location) assert response.status_code == 200 resp_data = response.get_data(as_text=True) - assert resp_data.count('Code does not match') == 2 - - -def test_should_mark_all_codes_as_used_when_many_codes_exist(app_, - db_, - db_session, - mock_api_user, - mock_get_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} - 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': '12345', - 'email_code': '23412'}) - assert response.status_code == 302 - assert verify_codes_dao.get_code_by_id(code1.id).code_used is True - assert verify_codes_dao.get_code_by_id(code2.id).code_used is True - assert verify_codes_dao.get_code_by_id(code3.id).code_used is True - assert verify_codes_dao.get_code_by_id(code4.id).code_used is True + assert resp_data.count('Code not found') == 2 diff --git a/tests/conftest.py b/tests/conftest.py index e93c6103b..f036a6585 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,8 @@ from alembic.config import Config from flask.ext.migrate import Migrate, MigrateCommand from flask.ext.script import Manager from sqlalchemy.schema import MetaData -from app import create_app, db + +from app import create_app from . import ( create_test_user, service_json, TestClient, @@ -28,42 +29,9 @@ def app_(request): return app -@pytest.fixture(scope='session') -def db_(app_, request): - Migrate(app_, db) - Manager(db, MigrateCommand) - BASE_DIR = os.path.dirname(os.path.dirname(__file__)) - ALEMBIC_CONFIG = os.path.join(BASE_DIR, 'migrations') - config = Config(ALEMBIC_CONFIG + '/alembic.ini') - config.set_main_option("script_location", ALEMBIC_CONFIG) - - with app_.app_context(): - upgrade(config, 'head') - - def teardown(): - db.session.remove() - db.drop_all() - db.engine.execute("drop table alembic_version") - db.get_engine(app_).dispose() - - request.addfinalizer(teardown) - - @pytest.fixture(scope='function') -def db_session(request): - def teardown(): - db.session.remove() - for tbl in reversed(meta.sorted_tables): - if tbl.fullname not in ['roles']: - db.engine.execute(tbl.delete()) - - meta = MetaData(bind=db.engine, reflect=True) - request.addfinalizer(teardown) - - -@pytest.fixture(scope='function') -def service_one(request, mock_api_user): - return service_json(1, 'service one', [mock_api_user.id]) +def service_one(request, api_user_active): + return service_json(1, 'service one', [api_user_active.id]) @pytest.fixture(scope='function') @@ -77,12 +45,12 @@ def mock_send_email(request, mocker): @pytest.fixture(scope='function') -def mock_get_service(mocker, mock_api_user): +def mock_get_service(mocker, api_user_active): def _get(service_id): service = service_json( - service_id, "Test Service", [mock_api_user.id], limit=1000, - active=True, restricted=True) - return {'data': service, 'token': 1} + service_id, "Test Service", [api_user_active.id], limit=1000, + active=False, restricted=True) + return {'data': service} return mocker.patch('app.notifications_api_client.get_service', side_effect=_get) @@ -95,9 +63,8 @@ def mock_create_service(mocker): active=active, restricted=restricted) return {'data': service} - mock_class = mocker.patch( + return mocker.patch( 'app.notifications_api_client.create_service', side_effect=_create) - return mock_class @pytest.fixture(scope='function') @@ -113,23 +80,24 @@ def mock_update_service(mocker): active=active, restricted=restricted) return {'data': service} - mock_class = mocker.patch( + return mocker.patch( 'app.notifications_api_client.update_service', side_effect=_update) - return mock_class @pytest.fixture(scope='function') -def mock_get_services(mocker, mock_active_user): - def _create(user_id): +def mock_get_services(mocker, user=None): + if user is None: + user = api_user_active() + + def _create(user_id=None): service_one = service_json( - 1, "service_one", [mock_active_user.id], 1000, True, False) + 1, "service_one", [user.id], 1000, True, False) service_two = service_json( - 2, "service_two", [mock_active_user.id], 1000, True, False) + 2, "service_two", [user.id], 1000, True, False) return {'data': [service_one, service_two]} - mock_class = mocker.patch( + return mocker.patch( 'app.notifications_api_client.get_services', side_effect=_create) - return mock_class @pytest.fixture(scope='function') @@ -137,9 +105,8 @@ def mock_delete_service(mocker, mock_get_service): def _delete(service_id): return mock_get_service.side_effect(service_id) - mock_class = mocker.patch( + return mocker.patch( 'app.notifications_api_client.delete_service', side_effect=_delete) - return mock_class @pytest.fixture(scope='function') @@ -160,10 +127,9 @@ def mock_create_service_template(mocker): 101, name, type_, content, service) return {'data': template} - mock_class = mocker.patch( + return mocker.patch( 'app.notifications_api_client.create_service_template', side_effect=_create) - return mock_class @pytest.fixture(scope='function') @@ -173,10 +139,9 @@ def mock_update_service_template(mocker): id_, name, type_, content, service) return {'data': template} - mock_class = mocker.patch( + return mocker.patch( 'app.notifications_api_client.update_service_template', side_effect=_update) - return mock_class @pytest.fixture(scope='function') @@ -188,10 +153,9 @@ def mock_get_service_templates(mocker): 2, "template_two", "sms", "template two content", service_id) return {'data': [template_one, template_two]} - mock_class = mocker.patch( + return mocker.patch( 'app.notifications_api_client.get_service_templates', side_effect=_create) - return mock_class @pytest.fixture(scope='function') @@ -207,9 +171,9 @@ def mock_delete_service_template(mocker): @pytest.fixture(scope='function') -def mock_api_user(mocker): +def api_user_pending(): from app.notify_client.user_api_client import User - user_data = {'id': 1, + user_data = {'id': 111, 'name': 'Test User', 'password': 'somepassword', 'email_address': 'test@user.gov.uk', @@ -222,98 +186,154 @@ def mock_api_user(mocker): @pytest.fixture(scope='function') -def mock_register_user(mocker, mock_api_user): +def api_user_active(): + from app.notify_client.user_api_client import User + user_data = {'id': 222, + 'name': 'Test User', + 'password': 'somepassword', + 'email_address': 'test@user.gov.uk', + 'mobile_number': '+4412341234', + 'state': 'active', + 'failed_login_count': 0 + } + user = User(user_data) + return user + + +@pytest.fixture(scope='function') +def api_user_locked(): + from app.notify_client.user_api_client import User + user_data = {'id': 333, + 'name': 'Test User', + 'password': 'somepassword', + 'email_address': 'test@user.gov.uk', + 'mobile_number': '+4412341234', + 'state': 'active', + 'failed_login_count': 5 + } + user = User(user_data) + return user + + +@pytest.fixture(scope='function') +def api_user_request_password_reset(): + from app.notify_client.user_api_client import User + user_data = {'id': 555, + 'name': 'Test User', + 'password': 'somepassword', + 'email_address': 'test@user.gov.uk', + 'mobile_number': '+4412341234', + 'state': 'request_password_reset', + 'failed_login_count': 5 + } + user = User(user_data) + return user + + +@pytest.fixture(scope='function') +def mock_register_user(mocker, api_user_pending): def _register(name, email_address, mobile_number, password): - mock_api_user.name = name - mock_api_user.email_address = email_address - mock_api_user.mobile_number = mobile_number - mock_api_user.password = password - return mock_api_user + api_user_pending.name = name + api_user_pending.email_address = email_address + api_user_pending.mobile_number = mobile_number + api_user_pending.password = password + return api_user_pending return mocker.patch('app.user_api_client.register_user', side_effect=_register) @pytest.fixture(scope='function') -def mock_active_user(mocker, mock_api_user): - mock_api_user.state = 'active' - return mock_api_user - - -@pytest.fixture(scope='function') -def mock_activate_user(mocker): - def _activate(user): - user.state = 'active' - return user - - return mocker.patch('app.user_api_client.update_user', side_effect=_activate) - - -@pytest.fixture(scope='function') -def mock_get_user(mocker, mock_active_user): +def mock_get_user(mocker, api_user_active): def _get_user(id): - return mock_active_user - - return mocker.patch('app.main.dao.users_dao.get_user_by_id', side_effect=_get_user) + return api_user_active + return mocker.patch( + 'app.user_api_client.get_user', side_effect=_get_user) @pytest.fixture(scope='function') -def mock_get_by_email(mocker, mock_api_user, mock_active_user, mock_get_user): +def mock_get_user_locked(mocker, api_user_locked): + return mocker.patch( + 'app.user_api_client.get_user', return_value=api_user_locked) + + +@pytest.fixture(scope='function') +def mock_get_user_pending(mocker, api_user_pending): + return mocker.patch( + 'app.user_api_client.get_user', return_value=api_user_pending) + + +@pytest.fixture(scope='function') +def mock_get_user_by_email(mocker, api_user_active): + def _get_user(email_address): - if email_address == 'notfound@example.gov.uk': - return None - if email_address == 'locked_user@example.gov.uk': - mock_active_user.failed_login_count = 5 - return mock_active_user - if email_address == 'inactive_user@example.gov.uk': - mock_active_user.state = 'inactive' - if email_address == 'pending_user@example.gov.uk': - return mock_api_user - - else: - mock_active_user.email_address = email_address - return mock_active_user - return mocker.patch('app.main.dao.users_dao.get_user_by_email', side_effect=_get_user) + api_user_active._email_address = email_address + return api_user_active + return mocker.patch('app.user_api_client.get_user_by_email', side_effect=_get_user) @pytest.fixture(scope='function') -def mock_user_checkpassword(mocker, mock_active_user): - def _check(mock_active_user, password): +def mock_get_user_by_email_request_password_reset(mocker, api_user_request_password_reset): + return mocker.patch( + 'app.user_api_client.get_user_by_email', + return_value=api_user_request_password_reset) + + +@pytest.fixture(scope='function') +def mock_get_user_by_email_locked(mocker, api_user_locked): + return mocker.patch( + 'app.user_api_client.get_user_by_email', return_value=api_user_locked) + + +@pytest.fixture(scope='function') +def mock_get_user_by_email_inactive(mocker, api_user_pending): + + def _get_user(email_address): + api_user_pending._email_address = email_address + api_user_pending._is_locked = True + return api_user_pending + return mocker.patch('app.user_api_client.get_user_by_email', side_effect=_get_user) + + +@pytest.fixture(scope='function') +def mock_get_user_by_email_pending(mocker, api_user_pending): + return mocker.patch( + 'app.user_api_client.get_user_by_email', + return_value=api_user_pending) + + +@pytest.fixture(scope='function') +def mock_get_user_by_email_not_found(mocker): + return mocker.patch('app.user_api_client.get_user_by_email', return_value=None) + + +@pytest.fixture(scope='function') +def mock_verify_password(mocker): + def _verify_password(user, password): return True - - return mocker.patch('app.main.dao.users_dao.verify_password', side_effect=_check) + return mocker.patch( + 'app.user_api_client.verify_password', + side_effect=_verify_password) @pytest.fixture(scope='function') -def mock_update_email(mocker, mock_active_user): - def _update(id, email_address): - mock_active_user.email_address = email_address - - return mocker.patch('app.main.dao.users_dao.update_email_address', side_effect=_update) - - -@pytest.fixture(scope='function') -def mock_update_mobile(mocker, mock_active_user): - - def _update(id, mobile_number): - mock_active_user.mobile_number = mobile_number - - return mocker.patch('app.main.dao.users_dao.update_mobile_number', side_effect=_update) - - -@pytest.fixture(scope='function') -def mock_password_reset(mocker, mock_active_user): +def mock_password_reset(mocker, api_user_active): def _reset(email): - mock_active_user.state = 'request_password_reset' - + api_user_active.state = 'request_password_reset' return mocker.patch('app.main.dao.users_dao.request_password_reset', side_effect=_reset) @pytest.fixture(scope='function') -def mock_get_user_from_api(mocker, mock_active_user): - def _get_user(email_address): - return mock_active_user - return mocker.patch('app.main.dao.users_dao.user_api_client.get_user_by_email', side_effect=_get_user) +def mock_update_user(mocker): + + def _update(user): + return user + return mocker.patch('app.user_api_client.update_user', side_effect=_update) + + +@pytest.fixture(scope='function') +def mock_is_email_unique(mocker): + return mocker.patch('app.user_api_client.get_user_by_email', return_value=None) @pytest.fixture(scope='function') @@ -321,21 +341,14 @@ def mock_get_all_users_from_api(mocker): return mocker.patch('app.main.dao.users_dao.user_api_client.get_users') -@pytest.fixture(scope='function') -def mock_update_user_email_api(mocker): - def _update(user): - return user - return mocker.patch('app.main.dao.users_dao.user_api_client.update_user', side_effect=_update) - - @pytest.fixture(scope='function') def mock_create_api_key(mocker): + def _create(service_id, key_name): import uuid return {'data': str(uuid.uuid4())} - mock_class = mocker.patch('app.api_key_api_client.create_api_key', side_effect=_create) - return mock_class + return mocker.patch('app.api_key_api_client.create_api_key', side_effect=_create) @pytest.fixture(scope='function') @@ -343,8 +356,9 @@ def mock_revoke_api_key(mocker): def _revoke(service_id, key_id): return {} - mock_class = mocker.patch('app.api_key_api_client.revoke_api_key', side_effect=_revoke) - return mock_class + return mocker.patch( + 'app.api_key_api_client.revoke_api_key', + side_effect=_revoke) @pytest.fixture(scope='function') @@ -354,8 +368,7 @@ def mock_get_api_keys(mocker): api_key_json(2, 'another key name', expiry_date=str(date.fromtimestamp(0)))]} return keys - mock_class = mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys) - return mock_class + return mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys) @pytest.fixture(scope='function') @@ -364,5 +377,51 @@ def mock_get_no_api_keys(mocker): keys = {'apiKeys': []} return keys - mock_class = mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys) - return mock_class + return mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys) + + +@pytest.fixture(scope='function') +def mock_login(mocker, mock_get_user, mock_update_user): + def _verify_code(user_id, code, code_type): + return True, '' + return mocker.patch( + 'app.user_api_client.check_verify_code', + side_effect=_verify_code) + + +@pytest.fixture(scope='function') +def mock_send_verify_code(mocker): + return mocker.patch('app.user_api_client.send_verify_code') + + +@pytest.fixture(scope='function') +def mock_check_verify_code(mocker): + def _verify(user_id, code, code_type): + return True, '' + return mocker.patch( + 'app.user_api_client.check_verify_code', + side_effect=_verify) + + +@pytest.fixture(scope='function') +def mock_check_verify_code_code_not_found(mocker): + def _verify(user_id, code, code_type): + return False, 'Code not found' + return mocker.patch( + 'app.user_api_client.check_verify_code', + side_effect=_verify) + + +@pytest.fixture(scope='function') +def mock_check_verify_code_code_expired(mocker): + def _verify(user_id, code, code_type): + return False, 'Code has expired' + return mocker.patch( + 'app.user_api_client.check_verify_code', + side_effect=_verify) + + +@pytest.fixture(scope='function') +def mock_send_email(mocker): + return mocker.patch( + 'app.notifications_api_client.send_email', return_value=None)