Add moar tests!

This commit is contained in:
Matt Wright
2014-03-14 15:26:20 -04:00
parent 44b91a351a
commit e5111dbb0c
17 changed files with 316 additions and 64 deletions
+18 -46
View File
@@ -12,7 +12,7 @@ import time
import pytest
from flask import Flask, render_template, current_app
from flask import Flask, render_template
from flask_mail import Mail
from flask_security import Security, MongoEngineUserDatastore, SQLAlchemyUserDatastore, \
@@ -59,7 +59,7 @@ def app():
def http_custom_realm():
return render_template('index.html', content='HTTP Authentication')
@app.route('/token')
@app.route('/token', methods=['GET', 'POST'])
@auth_token_required
def token():
return render_template('index.html', content='Token Authentication')
@@ -96,40 +96,6 @@ def app():
def unauthorized():
return render_template('unauthorized.html')
@app.route('/coverage/add_role_to_user')
def add_role_to_user():
ds = current_app.security.datastore
u = ds.find_user(email='joe@lp.com')
r = ds.find_role('admin')
ds.add_role_to_user(u, r)
return 'success'
@app.route('/coverage/remove_role_from_user')
def remove_role_from_user():
ds = current_app.security.datastore
u = ds.find_user(email='matt@lp.com')
ds.remove_role_from_user(u, 'admin')
return 'success'
@app.route('/coverage/deactivate_user')
def deactivate_user():
ds = current_app.security.datastore
u = ds.find_user(email='matt@lp.com')
ds.deactivate_user(u)
return 'success'
@app.route('/coverage/activate_user')
def activate_user():
ds = current_app.security.datastore
u = ds.find_user(email='tiya@lp.com')
ds.activate_user(u)
return 'success'
@app.route('/coverage/invalid_role')
def invalid_role():
ds = current_app.security.datastore
return 'success' if ds.find_role('bogus') is None else 'failure'
@app.route('/page1')
def page_1():
return 'Page 1'
@@ -158,7 +124,7 @@ def mongoengine_datastore(request, app):
class User(db.Document, UserMixin):
email = db.StringField(unique=True, max_length=255)
username = db.StringField(max_length=255)
password = db.StringField(required=True, max_length=255)
password = db.StringField(required=False, max_length=255)
last_login_at = db.DateTimeField()
current_login_at = db.DateTimeField()
last_login_ip = db.StringField(max_length=100)
@@ -237,7 +203,7 @@ def peewee_datastore(request, app, tmpdir):
class User(db.Model, UserMixin):
email = TextField()
username = TextField()
password = TextField()
password = TextField(null=True)
last_login_at = DateTimeField(null=True)
current_login_at = DateTimeField(null=True)
last_login_ip = TextField(null=True)
@@ -287,14 +253,9 @@ def mongoengine_app(app, mongoengine_datastore):
return create
@pytest.fixture(params=['sqlalchemy', 'mongoengine', 'peewee'])
def client(request, sqlalchemy_app, mongoengine_app, peewee_app):
if request.param == 'sqlalchemy':
app = sqlalchemy_app()
elif request.param == 'mongoengine':
app = mongoengine_app()
elif request.param == 'peewee':
app = peewee_app()
@pytest.fixture()
def client(request, sqlalchemy_app):
app = sqlalchemy_app()
populate_data(app)
return app.test_client()
@@ -305,3 +266,14 @@ def get_message(app):
rv = app.config['SECURITY_MSG_' + key][0] % kwargs
return rv.encode('utf-8')
return fn
@pytest.fixture(params=['sqlalchemy', 'mongoengine', 'peewee'])
def datastore(request, sqlalchemy_datastore, mongoengine_datastore, peewee_datastore):
if request.param == 'sqlalchemy':
rv = sqlalchemy_datastore
elif request.param == 'mongoengine':
rv = mongoengine_datastore
elif request.param == 'peewee':
rv = peewee_datastore
return rv
@@ -1 +1,3 @@
CUSTOM CHANGE PASSWORD
{{ foo }}
@@ -1 +1,3 @@
CUSTOM FORGOT PASSWORD
{{ foo }}
@@ -1 +1,3 @@
CUSTOM LOGIN USER
{{ foo }}
@@ -1 +1,3 @@
CUSTOM REGISTER USER
{{ foo }}
@@ -1 +1,3 @@
CUSTOM RESET PASSWORD
{{ foo }}
@@ -1 +1,3 @@
CUSTOM SEND CONFIRMATION
{{ foo }}
@@ -1 +1,3 @@
CUSTOM SEND LOGIN
{{ foo }}
@@ -0,0 +1,3 @@
CUSTOM RESET INSTRUCTIONS
{{ foo }}
+15 -3
View File
@@ -49,16 +49,22 @@ def test_recoverable_flag(app, sqlalchemy_datastore, get_message):
'new_password': 'newpassword',
'new_password_confirm': 'notnewpassword'
}, follow_redirects=True)
assert get_message('PASSWORD_CHANGE') not in response.data
assert get_message('RETYPE_PASSWORD_MISMATCH') in response.data
# Test missing password
response = client.post('/change', data={
'password': ' ',
'new_password': '',
'new_password_confirm': ''
}, follow_redirects=True)
assert get_message('PASSWORD_NOT_PROVIDED') in response.data
# Test bad password
response = client.post('/change', data={
'password': 'password',
'new_password': 'a',
'new_password_confirm': 'a'
}, follow_redirects=True)
assert get_message('PASSWORD_CHANGE') not in response.data
assert get_message('PASSWORD_INVALID_LENGTH') in response.data
# Test same as previous
@@ -67,7 +73,6 @@ def test_recoverable_flag(app, sqlalchemy_datastore, get_message):
'new_password': 'password',
'new_password_confirm': 'password'
}, follow_redirects=True)
assert get_message('PASSWORD_CHANGE') not in response.data
assert get_message('PASSWORD_IS_THE_SAME') in response.data
# Test successful submit sends email notification
@@ -84,6 +89,13 @@ def test_recoverable_flag(app, sqlalchemy_datastore, get_message):
assert len(outbox) == 1
assert "Your password has been changed" in outbox[0].html
# Test JSON
data = ('{"password": "newpassword", "new_password": "newpassword2", '
'"new_password_confirm": "newpassword2"}')
response = client.post('/change', data=data, headers={'Content-Type': 'application/json'})
assert response.status_code == 200
assert response.headers['Content-Type'] == 'application/json'
def test_custom_change_url(app, sqlalchemy_datastore, get_message):
client = _get_client(app, sqlalchemy_datastore, **{
+29 -9
View File
@@ -28,6 +28,18 @@ def test_authenticate(client):
assert b'Hello matt@lp.com' in response.data
def test_authenticate_with_next(client):
data = dict(email='matt@lp.com', password='password')
response = client.post('/login?next=/page1', data=data, follow_redirects=True)
assert b'Page 1' in response.data
def test_authenticate_with_invalid_next(client, get_message):
data = dict(email='matt@lp.com', password='password')
response = client.post('/login?next=http://google.com', data=data)
assert get_message('INVALID_REDIRECT') in response.data
def test_authenticate_case_insensitive_email(client):
response = authenticate(client, email='MATT@lp.com', follow_redirects=True)
assert b'Hello matt@lp.com' in response.data
@@ -58,6 +70,11 @@ def test_inactive_user(client, get_message):
assert get_message('DISABLED_ACCOUNT') in response.data
def test_unset_password(client, get_message):
response = authenticate(client, "jess@lp.com", "password")
assert get_message('PASSWORD_NOT_SET') in response.data
def test_logout(client):
authenticate(client)
response = logout(client, follow_redirects=True)
@@ -199,6 +216,9 @@ def test_multi_auth_basic(client):
})
assert b'Basic' in response.data
response = client.get('/multi_auth')
assert response.status_code == 401
def test_multi_auth_token(client):
response = json_authenticate(client)
@@ -245,12 +265,12 @@ def test_token_loader_does_not_fail_with_invalid_token(client):
assert b'BadSignature' not in response.data
def test_coverage_endpoints(client):
for endpoint in [
'/coverage/add_role_to_user',
'/coverage/remove_role_from_user',
'/coverage/activate_user',
'/coverage/deactivate_user'
]:
response = client.get(endpoint)
assert b'success' in response.data
def test_sending_auth_token_with_json(client):
response = json_authenticate(client)
token = response.jdata['response']['user']['authentication_token']
data = '{"auth_token": "%s"}' % token
response = client.post('/token', data=data, headers={'Content-Type': 'application/json'})
assert b'Token Authentication' in response.data
assert b'Token Authentication' in response.data
+106
View File
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
"""
test_context_processors
~~~~~~~~~~~~~~~~~~~~~~~
Context processor tests
"""
from utils import authenticate, init_app_with_options
def test_context_processors(app, sqlalchemy_datastore):
init_app_with_options(app, sqlalchemy_datastore, **{
'SECURITY_RECOVERABLE': True,
# 'SECURITY_PASSWORDLESS': True,
'SECURITY_REGISTERABLE': True,
'SECURITY_CONFIRMABLE': True,
'SECURITY_CHANGEABLE': True,
'SECURITY_LOGIN_WITHOUT_CONFIRMATION': True,
'SECURITY_CHANGE_PASSWORD_TEMPLATE': 'custom_security/change_password.html',
'SECURITY_LOGIN_USER_TEMPLATE': 'custom_security/login_user.html',
# 'SECURITY_SEND_LOGIN_TEMPLATE': 'custom_security/send_login.html',
'SECURITY_RESET_PASSWORD_TEMPLATE': 'custom_security/reset_password.html',
'SECURITY_FORGOT_PASSWORD_TEMPLATE': 'custom_security/forgot_password.html',
'SECURITY_SEND_CONFIRMATION_TEMPLATE': 'custom_security/send_confirmation.html',
'SECURITY_REGISTER_USER_TEMPLATE': 'custom_security/register_user.html'
})
client = app.test_client()
@app.security.forgot_password_context_processor
def forgot_password():
return {'foo': 'bar'}
response = client.get('/reset')
assert b'bar' in response.data
@app.security.login_context_processor
def login():
return {'foo': 'bar'}
response = client.get('/login')
assert b'bar' in response.data
@app.security.register_context_processor
def register():
return {'foo': 'bar'}
response = client.get('/register')
assert b'bar' in response.data
@app.security.reset_password_context_processor
def reset_password():
return {'foo': 'bar'}
response = client.get('/reset')
assert b'bar' in response.data
@app.security.change_password_context_processor
def change_password():
return {'foo': 'bar'}
authenticate(client)
response = client.get('/change')
assert b'bar' in response.data
@app.security.send_confirmation_context_processor
def send_confirmation():
return {'foo': 'bar'}
response = client.get('/confirm')
assert b'bar' in response.data
@app.security.mail_context_processor
def mail():
return {'foo': 'bar'}
with app.mail.record_messages() as outbox:
client.post('/reset', data=dict(email='matt@lp.com'))
email = outbox[0]
assert b'bar' in email.html
def test_passwordless_login_context_processor(app, sqlalchemy_datastore):
init_app_with_options(app, sqlalchemy_datastore, **{
'SECURITY_PASSWORDLESS': True,
'SECURITY_SEND_LOGIN_TEMPLATE': 'custom_security/send_login.html',
})
client = app.test_client()
@app.security.send_login_context_processor
def send_login():
return {'foo': 'bar'}
response = client.get('/login')
assert b'bar' in response.data
# @app.security.mail_context_processor
# def mail():
# return {'foo': 'bar'}
+79 -1
View File
@@ -8,20 +8,28 @@
from pytest import raises
from flask_security import UserMixin
from flask_security import UserMixin, RoleMixin
from flask_security.datastore import Datastore, UserDatastore
from utils import init_app_with_options
class User(UserMixin):
pass
class Role(RoleMixin):
pass
def test_unimplemented_datastore_methods():
datastore = Datastore(None)
assert datastore.db is None
with raises(NotImplementedError):
datastore.put(None)
with raises(NotImplementedError):
datastore.delete(None)
assert not datastore.commit()
def test_unimplemented_user_datastore_methods():
@@ -30,6 +38,8 @@ def test_unimplemented_user_datastore_methods():
datastore.find_user(None)
with raises(NotImplementedError):
datastore.find_role(None)
with raises(NotImplementedError):
datastore.get_user(None)
def test_toggle_active():
@@ -70,3 +80,71 @@ def test_activate_returns_false_if_already_true():
user = User()
user.active = True
assert not datastore.activate_user(user)
def test_get_user(app, datastore):
init_app_with_options(app, datastore, **{
'SECURITY_USER_IDENTITY_ATTRIBUTES': ('email', 'username')
})
with app.app_context():
user_id = datastore.find_user(email='matt@lp.com').id
user = datastore.get_user(user_id)
assert user is not None
user = datastore.get_user('matt@lp.com')
assert user is not None
user = datastore.get_user('matt')
assert user is not None
def test_find_role(app, datastore):
init_app_with_options(app, datastore)
role = datastore.find_role('admin')
assert role is not None
role = datastore.find_role('bogus')
assert role is None
def test_add_role_to_user(app, datastore):
init_app_with_options(app, datastore)
# Test with user object
user = datastore.find_user(email='matt@lp.com')
assert user.has_role('editor') is False
assert datastore.add_role_to_user(user, 'editor') is True
assert datastore.add_role_to_user(user, 'editor') is False
assert user.has_role('editor') is True
# Test with email
assert datastore.add_role_to_user('jill@lp.com', 'editor') is True
user = datastore.find_user(email='jill@lp.com')
assert user.has_role('editor') is True
# Test remove role
assert datastore.remove_role_from_user(user, 'editor') is True
assert datastore.remove_role_from_user(user, 'editor') is False
def test_create_user_with_roles(app, datastore):
init_app_with_options(app, datastore)
user = datastore.create_user(email='dude@lp.com', username='dude',
password='password', roles=['admin'])
datastore.commit()
user = datastore.find_user(email='dude@lp.com')
assert user.has_role('admin') is True
def test_delete_user(app, datastore):
init_app_with_options(app, datastore)
user = datastore.find_user(email='matt@lp.com')
datastore.delete_user(user)
datastore.commit()
user = datastore.find_user(email='matt@lp.com')
assert user is None
+34
View File
@@ -6,6 +6,8 @@
Email functionality tests
"""
from pytest import raises
from flask_security import Security
from flask_security.forms import LoginForm, RegisterForm, ConfirmRegisterForm, \
SendConfirmationForm, PasswordlessLoginForm, ForgotPasswordForm, ResetPasswordForm, \
@@ -140,3 +142,35 @@ def test_addition_identity_attributes(app, sqlalchemy_datastore):
client = app.test_client()
response = authenticate(client, email='matt', follow_redirects=True)
assert b'Hello matt@lp.com' in response.data
def test_flash_messages_off(app, sqlalchemy_datastore, get_message):
init_app_with_options(app, sqlalchemy_datastore, **{
'SECURITY_FLASH_MESSAGES': False
})
client = app.test_client()
response = client.get('/profile')
assert get_message('LOGIN') not in response.data
def test_invalid_hash_scheme(app, sqlalchemy_datastore, get_message):
with raises(ValueError):
init_app_with_options(app, sqlalchemy_datastore, **{
'SECURITY_PASSWORD_HASH': 'bogus'
})
def test_change_hash_type(app, sqlalchemy_datastore):
init_app_with_options(app, sqlalchemy_datastore, **{
'SECURITY_PASSWORD_SCHEMES': ['bcrypt', 'plaintext']
})
app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt'
app.config['SECURITY_PASSWORD_SALT'] = 'salty'
app.security = Security(app, datastore=sqlalchemy_datastore, register_blueprint=False)
client = app.test_client()
response = client.post('/login', data=dict(email='matt@lp.com', password='password'))
assert response.status_code == 302
+1 -1
View File
@@ -40,7 +40,7 @@ def test_trackable_flag(app, sqlalchemy_datastore, get_message):
# Test login with json and invalid email
data = '{"email": "nobody@lp.com", "password": "password"}'
response = client.post('/login', data=data, headers={'Content-Type': 'application/json'})
assert 'errors' in response.data
assert b'errors' in response.data
# Test sends email and shows appropriate response
with capture_passwordless_login_requests() as requests:
+10 -1
View File
@@ -57,12 +57,21 @@ def test_registerable_flag(app, sqlalchemy_datastore, get_message):
# Test registering with JSON
data = '{ "email": "dude2@lp.com", "password": "password"}'
response = client.post('/register', data=data, content_type='application/json')
response = client.post('/register', data=data, headers={'Content-Type': 'application/json'})
assert response.headers['content-type'] == 'application/json'
assert response.jdata['meta']['code'] == 200
logout(client)
# Test registering with invalid JSON
data = '{ "email": "bogus", "password": "password"}'
response = client.post('/register', data=data, headers={'Content-Type': 'application/json'})
print response.data
assert response.headers['content-type'] == 'application/json'
assert response.jdata['meta']['code'] == 400
logout(client)
# Test ?next param
data = dict(email='dude3@lp.com',
password='password',
+7 -3
View File
@@ -39,11 +39,14 @@ def create_users(ds, count=None):
('joe@lp.com', 'joe', 'password', ['editor'], True),
('dave@lp.com', 'dave', 'password', ['admin', 'editor'], True),
('jill@lp.com', 'jill', 'password', ['author'], True),
('tiya@lp.com', 'tiya', 'password', [], False)]
('tiya@lp.com', 'tiya', 'password', [], False),
('jess@lp.com', 'jess', None, [], True)]
count = count or len(users)
for u in users[:count]:
pw = encrypt_password(u[2])
pw = u[2]
if pw is not None:
pw = encrypt_password(pw)
roles = [ds.find_or_create_role(rn) for rn in u[3]]
ds.commit()
user = ds.create_user(email=u[0], username=u[1], password=pw, active=u[4])
@@ -75,6 +78,7 @@ class Response(BaseResponse): # pragma: no cover
def init_app_with_options(app, datastore, **options):
security_args = options.pop('security_args', {})
app.config.update(**options)
app.security = Security(app, datastore=datastore)
app.security = Security(app, datastore=datastore, **security_args)
populate_data(app)