|
|
@@ -1,7 +1,10 @@ |
|
|
# pylint: disable=no-self-use
|
|
|
from collections import namedtuple
|
|
|
|
|
|
from mock import patch, Mock, MagicMock
|
|
|
import pytest
|
|
|
|
|
|
import deform
|
|
|
from pyramid import httpexceptions
|
|
|
from pyramid.testing import DummyRequest
|
|
|
from horus.interfaces import (
|
|
|
@@ -16,11 +19,10 @@ |
|
|
from horus.schemas import ProfileSchema
|
|
|
from horus.forms import SubmitForm
|
|
|
from horus.strings import UIStringsBase
|
|
|
import deform
|
|
|
import colander
|
|
|
|
|
|
from h.accounts import schemas
|
|
|
from h.accounts import views
|
|
|
from h.accounts.views import validate_form
|
|
|
from h.accounts.views import RegisterController
|
|
|
from h.accounts.views import ProfileController
|
|
|
from h.accounts.views import AsyncFormViewMapper
|
|
|
@@ -48,44 +50,53 @@ def configure(config): |
|
|
config.registry.feature.return_value = None
|
|
|
|
|
|
|
|
|
def _get_fake_request(username, password, with_subscriptions=False, active=True):
|
|
|
def _get_fake_request(username, password):
|
|
|
fake_request = DummyRequest()
|
|
|
|
|
|
def get_fake_token():
|
|
|
return 'fake_token'
|
|
|
|
|
|
fake_request.method = 'POST'
|
|
|
fake_request.params['csrf_token'] = 'fake_token'
|
|
|
fake_request.session.get_csrf_token = get_fake_token
|
|
|
fake_request.POST['username'] = username
|
|
|
fake_request.POST['pwd'] = password
|
|
|
|
|
|
if with_subscriptions:
|
|
|
subs = '{"active": activestate, "uri": "username", "id": 1}'
|
|
|
subs = subs.replace('activestate', str(active).lower()).replace('username', username)
|
|
|
fake_request.POST['subscriptions'] = subs
|
|
|
return fake_request
|
|
|
|
|
|
|
|
|
class TestEmailsMustMatchValidator(object):
|
|
|
# A fake version of colander.Invalid for use when testing validate_form
|
|
|
FakeInvalid = namedtuple('FakeInvalid', 'children')
|
|
|
|
|
|
|
|
|
def test_validate_form_passes_data_to_validate():
|
|
|
idata = {}
|
|
|
form = MagicMock()
|
|
|
|
|
|
err, data = validate_form(form, idata)
|
|
|
|
|
|
form.validate.assert_called_with(idata)
|
|
|
|
|
|
|
|
|
def test_validate_form_failure():
|
|
|
invalid = FakeInvalid(children=object())
|
|
|
form = MagicMock()
|
|
|
form.validate.side_effect = deform.ValidationFailure(None, None, invalid)
|
|
|
|
|
|
err, data = validate_form(form, {})
|
|
|
|
|
|
assert err == {'errors': invalid.children}
|
|
|
assert data is None
|
|
|
|
|
|
|
|
|
"""Unit tests for _emails_must_match_validator()."""
|
|
|
def test_validate_form_ok():
|
|
|
form = MagicMock()
|
|
|
form.validate.return_value = {'foo': 'bar'}
|
|
|
|
|
|
def test_it_raises_invalid_if_the_emails_do_not_match(self):
|
|
|
form = deform.Form(schemas.EditProfileSchema())
|
|
|
value = {
|
|
|
"email": "foo",
|
|
|
"emailAgain": "bar"
|
|
|
}
|
|
|
with pytest.raises(colander.Invalid):
|
|
|
views._emails_must_match_validator(form, value)
|
|
|
err, odata = validate_form(form, {})
|
|
|
|
|
|
def test_it_returns_None_if_the_emails_match(self):
|
|
|
form = deform.Form(schemas.EditProfileSchema())
|
|
|
value = {
|
|
|
"email": "foo",
|
|
|
"emailAgain": "foo"
|
|
|
}
|
|
|
assert views._emails_must_match_validator(form, value) is None
|
|
|
assert err is None
|
|
|
assert odata == {'foo': 'bar'}
|
|
|
|
|
|
|
|
|
class TestProfile(object):
|
|
|
@@ -110,125 +121,70 @@ class TestEditProfile(object): |
|
|
|
|
|
"""Unit tests for ProfileController's edit_profile() method."""
|
|
|
|
|
|
@pytest.mark.usefixtures('activation_model', 'dummy_db_session')
|
|
|
def test_profile_invalid_password(self, config, user_model):
|
|
|
def test_edit_profile_invalid_password(self, form_validator, user_model):
|
|
|
"""Make sure our edit_profile call validates the user password."""
|
|
|
request = _get_fake_request('john', 'doe')
|
|
|
configure(config)
|
|
|
form_validator.return_value = (None, {
|
|
|
"username": "john",
|
|
|
"pwd": "blah",
|
|
|
"subscriptions": "",
|
|
|
})
|
|
|
|
|
|
# With an invalid password, get_user returns None
|
|
|
user_model.get_user.return_value = None
|
|
|
# Mock an invalid password
|
|
|
user_model.validate_user.return_value = False
|
|
|
|
|
|
request = DummyRequest(method='POST')
|
|
|
profile = ProfileController(request)
|
|
|
result = profile.edit_profile()
|
|
|
|
|
|
assert result['code'] == 401
|
|
|
assert any('pwd' in err for err in result['errors'])
|
|
|
|
|
|
@pytest.mark.usefixtures('activation_model', 'dummy_db_session')
|
|
|
def test_edit_profile_with_validation_failure(self, config, user_model):
|
|
|
"""If validation raises edit_profile() should return an error.
|
|
|
|
|
|
If _validate_edit_profile_request() raises an exception then
|
|
|
edit_profile() should return a dict with an "errors" list containing a
|
|
|
list of the error(s) from the exception's .errors property.
|
|
|
def test_edit_profile_with_validation_failure(self, form_validator):
|
|
|
"""If form validation fails, return the error object."""
|
|
|
form_validator.return_value = ({"errors": "BOOM!"}, None)
|
|
|
|
|
|
"""
|
|
|
configure(config)
|
|
|
profile = ProfileController(DummyRequest())
|
|
|
errors = [
|
|
|
("email", ["That email is invalid", "That email is taken"]),
|
|
|
("emailAgain", "The emails must match."),
|
|
|
("password", ["That password is wrong"])
|
|
|
]
|
|
|
|
|
|
with patch(
|
|
|
"h.accounts.views._validate_edit_profile_request") as validate:
|
|
|
validate.side_effect = (
|
|
|
views._InvalidEditProfileRequestError(errors=errors))
|
|
|
result = profile.edit_profile()
|
|
|
request = DummyRequest(method='POST')
|
|
|
profile = ProfileController(request)
|
|
|
result = profile.edit_profile()
|
|
|
|
|
|
assert result["errors"] == errors
|
|
|
assert result == {"errors": "BOOM!"}
|
|
|
|
|
|
@pytest.mark.usefixtures('activation_model', 'dummy_db_session')
|
|
|
def test_edit_profile_successfully(self, config, user_model):
|
|
|
def test_edit_profile_successfully(self, authn_policy, form_validator, user_model):
|
|
|
"""edit_profile() returns a dict with key "form" when successful."""
|
|
|
configure(config)
|
|
|
profile = ProfileController(DummyRequest())
|
|
|
|
|
|
with patch(
|
|
|
"h.accounts.views._validate_edit_profile_request") as validate:
|
|
|
validate.return_value = {
|
|
|
"username": "johndoe",
|
|
|
"pwd": "password",
|
|
|
"subscriptions": []
|
|
|
}
|
|
|
result = profile.edit_profile()
|
|
|
|
|
|
assert "form" in result
|
|
|
assert "errors" not in result
|
|
|
|
|
|
@pytest.mark.usefixtures('activation_model', 'dummy_db_session')
|
|
|
def test_edit_profile_returns_email(self, config, user_model,
|
|
|
authn_policy):
|
|
|
"""edit_profile()'s response should contain the user's current email.
|
|
|
|
|
|
For a valid edit_profile() request
|
|
|
horus.views.ProfileController.edit_profile() returns an HTTPRedirection
|
|
|
object. h.accounts.views.ProfileController.edit_profile() should
|
|
|
add a JSON body to this response containing a "model" dict with the
|
|
|
user's current email address.
|
|
|
|
|
|
AsyncFormViewMapper will pick up this JSON body and preserve it in the
|
|
|
body of the 200 OK response that is finally sent back to the browser.
|
|
|
authn_policy.authenticated_userid.return_value = "johndoe"
|
|
|
form_validator.return_value = (None, {
|
|
|
"username": "johndoe",
|
|
|
"pwd": "password",
|
|
|
"subscriptions": "",
|
|
|
})
|
|
|
user_model.validate_user.return_value = True
|
|
|
user_model.get_by_id.return_value = FakeUser(email="john@doe.com")
|
|
|
|
|
|
request = DummyRequest(method='POST')
|
|
|
profile = ProfileController(request)
|
|
|
result = profile.edit_profile()
|
|
|
|
|
|
The frontend uses this email field to show the user's current email
|
|
|
address in the form.
|
|
|
assert result == {"model": {"email": "john@doe.com"}}
|
|
|
|
|
|
"""
|
|
|
configure(config)
|
|
|
validate_patcher = patch(
|
|
|
"h.accounts.views._validate_edit_profile_request")
|
|
|
edit_profile_patcher = patch(
|
|
|
"horus.views.ProfileController.edit_profile")
|
|
|
get_by_id_patcher = patch("h.accounts.models.User.get_by_id")
|
|
|
|
|
|
result = None
|
|
|
try:
|
|
|
validate = validate_patcher.start()
|
|
|
validate.return_value = {
|
|
|
"username": "fake user name",
|
|
|
"pwd": "fake password",
|
|
|
"subscriptions": []
|
|
|
}
|
|
|
|
|
|
edit_profile = edit_profile_patcher.start()
|
|
|
edit_profile.return_value = httpexceptions.HTTPFound("fake url")
|
|
|
|
|
|
get_by_id = get_by_id_patcher.start()
|
|
|
get_by_id.return_value = FakeUser(email="fake email")
|
|
|
|
|
|
result = ProfileController(DummyRequest()).edit_profile()
|
|
|
|
|
|
assert result.json["model"]["email"] == "fake email"
|
|
|
|
|
|
finally:
|
|
|
validate = validate_patcher.stop()
|
|
|
edit_profile = edit_profile_patcher.stop()
|
|
|
get_by_id = get_by_id_patcher.stop()
|
|
|
|
|
|
@pytest.mark.usefixtures('activation_model', 'user_model')
|
|
|
def test_subscription_update(self, config, dummy_db_session):
|
|
|
def test_subscription_update(self, authn_policy, form_validator,
|
|
|
subscriptions_model, user_model):
|
|
|
"""Make sure that the new status is written into the DB."""
|
|
|
request = _get_fake_request('acct:john@doe', 'smith', True, True)
|
|
|
configure(config)
|
|
|
authn_policy.authenticated_userid.return_value = "acct:john@doe"
|
|
|
form_validator.return_value = (None, {
|
|
|
"username": "acct:john@doe",
|
|
|
"pwd": "smith",
|
|
|
"subscriptions": '{"active":true,"uri":"acct:john@doe","id":1}',
|
|
|
})
|
|
|
mock_sub = Mock(active=False)
|
|
|
subscriptions_model.get_by_id.return_value = mock_sub
|
|
|
|
|
|
request = DummyRequest(method='POST')
|
|
|
profile = ProfileController(request)
|
|
|
result = profile.edit_profile()
|
|
|
|
|
|
assert mock_sub.active == True
|
|
|
assert result == {}
|
|
|
|
|
|
with patch('h.accounts.views.Subscriptions') as mock_subs:
|
|
|
mock_subs.get_by_id = MagicMock()
|
|
|
mock_subs.get_by_id.return_value = Mock(active=True)
|
|
|
profile = ProfileController(request)
|
|
|
profile.edit_profile()
|
|
|
assert dummy_db_session.added
|
|
|
|
|
|
|
|
|
class TestAsyncFormViewMapper(object):
|
|
|
@@ -264,11 +220,12 @@ def edit_profile(self): |
|
|
|
|
|
@pytest.mark.usefixtures('activation_model',
|
|
|
'dummy_db_session')
|
|
|
def test_disable_invalid_password(config, user_model):
|
|
|
def test_disable_invalid_password(config, form_validator, user_model):
|
|
|
"""
|
|
|
Make sure our disable_user call validates the user password
|
|
|
"""
|
|
|
request = _get_fake_request('john', 'doe')
|
|
|
form_validator.return_value = (None, {"username": "john", "pwd": "doe"})
|
|
|
configure(config)
|
|
|
|
|
|
# With an invalid password, get_user returns None
|
|
|
@@ -283,11 +240,12 @@ def test_disable_invalid_password(config, user_model): |
|
|
|
|
|
@pytest.mark.usefixtures('activation_model',
|
|
|
'dummy_db_session')
|
|
|
def test_user_disabled(config, user_model):
|
|
|
def test_user_disabled(config, form_validator, user_model):
|
|
|
"""
|
|
|
Check if the user is disabled
|
|
|
"""
|
|
|
request = _get_fake_request('john', 'doe')
|
|
|
form_validator.return_value = (None, {"username": "john", "pwd": "doe"})
|
|
|
configure(config)
|
|
|
|
|
|
user = FakeUser(password='abc')
|
|
|
@@ -320,14 +278,30 @@ def test_registration_does_not_autologin(config, authn_policy): |
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def user_model(config):
|
|
|
mock = MagicMock()
|
|
|
config.registry.registerUtility(mock, IUserClass)
|
|
|
return mock
|
|
|
def subscriptions_model(request):
|
|
|
patcher = patch('h.accounts.views.Subscriptions', autospec=True)
|
|
|
request.addfinalizer(patcher.stop)
|
|
|
return patcher.start()
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def user_model(config, request):
|
|
|
patcher = patch('h.accounts.views.User', autospec=True)
|
|
|
request.addfinalizer(patcher.stop)
|
|
|
user = patcher.start()
|
|
|
config.registry.registerUtility(user, IUserClass)
|
|
|
return user
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def activation_model(config):
|
|
|
mock = MagicMock()
|
|
|
config.registry.registerUtility(mock, IActivationClass)
|
|
|
return mock
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def form_validator(request):
|
|
|
patcher = patch('h.accounts.views.validate_form', autospec=True)
|
|
|
request.addfinalizer(patcher.stop)
|
|
|
return patcher.start()
|