diff --git a/ordr2/templates/account/login.jinja2 b/ordr2/templates/account/login.jinja2 new file mode 100644 index 0000000..060ab83 --- /dev/null +++ b/ordr2/templates/account/login.jinja2 @@ -0,0 +1,39 @@ +{% extends "ordr2:templates/layout.jinja2" %} + +{% block title %} Ordr | Login Failed {% endblock title %} + +{% block content %} +
+
+ +

Try again…

+

You entered the wrong username or password.

+ +
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+ +

+ If you forgot your password you can reset it. +

+
+
+{% endblock content %} diff --git a/ordr2/templates/layout.jinja2 b/ordr2/templates/layout.jinja2 index f65df01..351f5eb 100644 --- a/ordr2/templates/layout.jinja2 +++ b/ordr2/templates/layout.jinja2 @@ -33,7 +33,9 @@ @@ -60,7 +62,7 @@ -
+ diff --git a/ordr2/views/account.py b/ordr2/views/account.py index f0d6dfc..d9ba83c 100644 --- a/ordr2/views/account.py +++ b/ordr2/views/account.py @@ -1,7 +1,11 @@ ''' account registration, login, logout and settings ''' +from pyramid.httpexceptions import HTTPFound +from pyramid.security import remember, forget from pyramid.view import view_config +from ordr2.models import User + @view_config( context='ordr2:resources.account.AccountResource', @@ -12,3 +16,39 @@ from pyramid.view import view_config def register(context, request): ''' the new user registraion page ''' return {} + + +@view_config( + context='ordr2:resources.account.AccountResource', + name='login', + permission='login', + request_method='POST', + renderer='ordr2:templates/account/login.jinja2' + ) +def login(context, request): + ''' loging in a user ''' + username = request.POST.get('username') + password = request.POST.get('password') + + # Form validation is not done for login forms, + # either the data represents a user or not. + user = request.dbsession.query(User).filter_by(username=username).first() + if user is not None: + if user.is_active and user.check_password(password): + headers = remember(request, user.id) + return HTTPFound( + request.resource_path(request.root, 'orders'), + headers=headers + ) + return {} + + +@view_config( + context='ordr2:resources.account.AccountResource', + name='logout', + permission='logout' + ) +def logout(context, request): + ''' log out an user ''' + headers = forget(request) + return HTTPFound(request.resource_url(request.root), headers=headers) diff --git a/tests/_functional/__init__.py b/tests/_functional/__init__.py index 92bb8a0..c38ef86 100644 --- a/tests/_functional/__init__.py +++ b/tests/_functional/__init__.py @@ -5,19 +5,41 @@ import pytest import transaction import webtest -from .. import APP_SETTINGS, create_users - - -# some path mangling to get the path to passlib.ini -currrent_dir = os.path.dirname(__file__) -ordr2_dir = os.path.dirname(os.path.dirname(currrent_dir)) -passlib_config_path = os.path.join(ordr2_dir, 'passlib.ini') +from .. import APP_SETTINGS, create_users, get_user WEBTEST_SETTINGS = APP_SETTINGS.copy() # WEBTEST_SETTINGS.update({ }) +class CustomTestApp(webtest.TestApp): + ''' adds login and logout methods to webtest.TestApp ''' + + def reset(self): + ''' logs out any user and resets the state of the application ''' + self.logout() + super().reset() + + + def logout(self): + ''' perfomes a logout of a user ''' + self.get('/account/logout') + + + def login(self, role_name): + ''' perfomes a logs in of a user ''' + # do a logout first, testapp fixture scope is on module level + self.logout() + user = get_user(role_name) + + response = self.get('/') + login_form = response.forms['login-form'] + login_form.set('username', user.username) + login_form.set('password', user.first_name) + login_form.submit() + + + @pytest.fixture(scope='module') def testapp(): ''' fixture for using webtest ''' @@ -26,7 +48,7 @@ def testapp(): from ordr2 import main app = main({}, **WEBTEST_SETTINGS) - testapp = webtest.TestApp(app) + testapp = CustomTestApp(app) session_factory = app.registry['dbsession_factory'] engine = session_factory.kw['bind'] @@ -37,6 +59,6 @@ def testapp(): dbsession = get_tm_session(session_factory, transaction.manager) create_users(dbsession) - yield testapp + yield testapp Base.metadata.drop_all(engine) diff --git a/tests/_functional/account.py b/tests/_functional/account.py index c0b6ded..6e8d092 100644 --- a/tests/_functional/account.py +++ b/tests/_functional/account.py @@ -1,6 +1,9 @@ ''' tests for the common layout and simple (static)''' +import pytest + from . import testapp +from .. import get_user def test_account_register_unauthenticated(testapp): @@ -18,3 +21,81 @@ def test_account_register_unauthenticated(testapp): assert 'active' in li_two['class'] assert li_two.find('a').text == 'Register' + +@pytest.mark.parametrize('role_name', ['user', 'purchaser', 'admin', ]) +def test_account_login_for_active_users(testapp, role_name): + ''' check if user login works ''' + testapp.reset() + + user = get_user(role_name) + root = testapp.get('/') + login_form = root.forms['login-form'] + login_form.set('username', user.username) + login_form.set('password', user.first_name) + response = login_form.submit() + + # a login leads to a redirect + assert response.status == '302 Found' + assert response.location.endswith('/orders') + + # the layout should reflect the login + response = testapp.get('/faq') + assert '' in response + assert 'id="login-form"' not in response + assert 'Logged in as {}'.format(user.username) in response + + +@pytest.mark.parametrize('role_name', ['unvalidated', 'new', 'inactive']) +def test_account_login_for_inactive_users(testapp, role_name): + ''' check if user login works ''' + testapp.reset() + + user = get_user(role_name) + root = testapp.get('/') + login_form = root.forms['login-form'] + login_form.set('username', user.username) + login_form.set('password', user.first_name) + response = login_form.submit() + + assert '' in response + assert 'id="login-form' in response + assert 'Logged in as {}'.format(user.username) not in response + assert 'You entered the wrong username or password' in response + + +@pytest.mark.parametrize( + 'username, password', [ + ('EricIdle', 'wrong password'), + ('unknown user', 'Eric'), + ('unknown user', 'unknown password') + ] + ) +def test_account_login_fails(testapp, username, password): + ''' check if user login works ''' + testapp.reset() + + root = testapp.get('/') + login_form = root.forms['login-form'] + login_form.set('username', username) + login_form.set('password', password) + response = login_form.submit() + + assert '' in response + assert 'id="login-form' in response + assert 'Logged in as {}'.format(username) not in response + assert 'You entered the wrong username or password' in response + + +def test_account_login_only_by_post(testapp): + testapp.reset() + + +def test_account_logout_works(testapp): + ''' check if a user can log out ''' + testapp.reset() + + testapp.login('user') + response = testapp.get('/account/logout').follow() + + assert '' in response + assert 'id="login-form' in response diff --git a/tests/_functional/layout_and_pages.py b/tests/_functional/layout_and_pages.py index a01db4d..128c510 100644 --- a/tests/_functional/layout_and_pages.py +++ b/tests/_functional/layout_and_pages.py @@ -3,6 +3,8 @@ from . import testapp +# tests for layout template + def test_layout_unauthenticated_user(testapp): ''' test the layout template elements for unauthenticated users @@ -13,8 +15,42 @@ def test_layout_unauthenticated_user(testapp): response = testapp.get('/faq') assert '' in response - assert 'class="login-form' in response + assert 'id="login-form' in response + + +def test_layout_authenticated_user(testapp): + ''' test the layout template elements for unauthenticated users + + The '/faq' path is used; '/' will redirect on authenticated users + ''' + testapp.reset() + testapp.login('user') + + response = testapp.get('/faq') + + assert '' in response + assert 'id="login-form"' not in response + assert 'Admin' not in response + assert 'Logged in as TerryGilliam' in response + + +def test_layout_authenticated_admin(testapp): + ''' test the layout template elements for unauthenticated users + + The '/faq' path is used; '/' will redirect on authenticated users + ''' + testapp.reset() + testapp.login('admin') + + response = testapp.get('/faq') + + assert '' in response + assert 'id="login-form"' not in response + assert 'Admin' in response + assert 'Logged in as TerryJones' in response + +# tests for simple pages def test_page_welcome_unauthenticated(testapp): ''' test the welcome page for a unauthenticated user '''