diff --git a/ordr/scripts/initializedb.py b/ordr/scripts/initializedb.py index 6539938..24bb492 100644 --- a/ordr/scripts/initializedb.py +++ b/ordr/scripts/initializedb.py @@ -40,5 +40,5 @@ def main(argv=sys.argv): with transaction.manager: dbsession = get_tm_session(session_factory, transaction.manager) - - dbsession.add() + + # dbsession.add() diff --git a/ordr/templates/pages/welcome.jinja2 b/ordr/templates/pages/login.jinja2 similarity index 63% rename from ordr/templates/pages/welcome.jinja2 rename to ordr/templates/pages/login.jinja2 index a9fab94..5e88ce6 100644 --- a/ordr/templates/pages/welcome.jinja2 +++ b/ordr/templates/pages/login.jinja2 @@ -14,10 +14,16 @@

Login

- + +
- + + {% if loginerror %} +
+ Username and password do not match, or account is not activated. +
+ {% endif %}
Forgot your password? diff --git a/ordr/views/pages.py b/ordr/views/pages.py index e20b733..c9163a6 100644 --- a/ordr/views/pages.py +++ b/ordr/views/pages.py @@ -1,11 +1,13 @@ from pyramid.httpexceptions import HTTPFound +from pyramid.security import remember, forget from pyramid.view import view_config +from ordr.models import User + @view_config( context='ordr.resources.RootResource', permission='view', - renderer='ordr:templates/pages/welcome.jinja2', ) def welcome(context, request): next = 'orders' if request.user else 'login' @@ -13,21 +15,55 @@ def welcome(context, request): return HTTPFound(redirect_to) +@view_config( + context='ordr.resources.RootResource', + name='faq', + permission='view', + renderer='ordr:templates/pages/faq.jinja2' + ) +def faq(context, request): + return {} + + @view_config( context='ordr.resources.RootResource', name='login', + request_method='GET', permission='view', - renderer='ordr:templates/pages/welcome.jinja2', + renderer='ordr:templates/pages/login.jinja2', ) def login(context, request): - return {} + return { 'loginerror': False } @view_config( context='ordr.resources.RootResource', - name='faq', + name='login', + request_method='POST', permission='view', - renderer='ordr:templates/pages/faq.jinja2' + renderer='ordr:templates/pages/login.jinja2', ) -def faq(context, request): - return {} +def check_login(context, request): + username = request.POST.get('username') + password = request.POST.get('password') + user = ( + request.dbsession + .query(User) + .filter_by(username=username) + .first() + ) + if user and user.is_active and user.check_password(password): + headers = remember(request, user.id) + return HTTPFound(request.resource_url(request.root), headers=headers) + return { 'loginerror': True } + + +@view_config( + context='ordr.resources.RootResource', + name='logout', + permission='view' + ) +def logout(context, request): + ''' log out of an user ''' + headers = forget(request) + return HTTPFound(request.resource_url(request.root), headers=headers) diff --git a/tests/__init__.py b/tests/__init__.py index e253c7b..167d996 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,6 +6,11 @@ from pyramid import testing APP_SETTINGS = { 'sqlalchemy.url': 'sqlite:///:memory:', + 'session.secret': 'something', + 'session.auto_csrf': True, + 'passlib.schemes': 'argon2 bcrypt', + 'passlib.default': 'argon2', + 'passlib.deprecated': 'auto' } EXAMPLE_USER_DATA = { diff --git a/tests/_functional/__init__.py b/tests/_functional/__init__.py index 3b52226..e66230f 100644 --- a/tests/_functional/__init__.py +++ b/tests/_functional/__init__.py @@ -4,7 +4,7 @@ import pytest import transaction import webtest -from .. import APP_SETTINGS +from .. import APP_SETTINGS, get_example_user WEBTEST_SETTINGS = APP_SETTINGS.copy() # WEBTEST_SETTINGS['pyramid.includes'].append('pyramid_mailer.testing') @@ -17,20 +17,32 @@ class CustomTestApp(webtest.TestApp): def login(self, username, password): ''' stub for user login ''' self.logout() - pass + result = self.get('/login') + login_form = result.forms[0] + login_form['username'] = username + login_form['password'] = password + login_form.submit() def logout(self): ''' stub for user logout ''' - pass + self.get('/logout') + + def reset(self): + ''' reset the webapp ''' + self.logout() + super().reset() def create_users(dbsession): ''' create example users ''' - pass + from ordr.models.account import Role + for role in Role: + user = get_example_user(role) + dbsession.add(user) @pytest.fixture(scope='module') -def testapp(): +def testappsetup(): ''' fixture for using webtest ''' from ordr.models.meta import Base from ordr.models import get_tm_session @@ -51,3 +63,9 @@ def testapp(): yield testapp Base.metadata.drop_all(engine) + + +@pytest.fixture(scope='function') +def testapp(testappsetup): + testappsetup.reset() + yield testappsetup diff --git a/tests/_functional/errors.py b/tests/_functional/errors.py index ec4ca19..2e61036 100644 --- a/tests/_functional/errors.py +++ b/tests/_functional/errors.py @@ -1,6 +1,6 @@ ''' functional tests for ordr2.views.errors ''' -from . import testapp # noqa: F401 +from . import testappsetup, testapp # noqa: F401 def test_404(testapp): # noqa: F811 diff --git a/tests/_functional/layout.py b/tests/_functional/layout.py index 7cc077f..a761712 100644 --- a/tests/_functional/layout.py +++ b/tests/_functional/layout.py @@ -6,7 +6,7 @@ two urls are accessible by either everyone or all active users import pytest -from . import testapp # noqa: F401 +from . import testappsetup, testapp # noqa: F401 def test_navbar_no_user(testapp): # noqa: F811 diff --git a/tests/_functional/pages.py b/tests/_functional/pages.py index 24558f5..3ca29da 100644 --- a/tests/_functional/pages.py +++ b/tests/_functional/pages.py @@ -2,18 +2,23 @@ import pytest -from . import testapp # noqa: F401 +from . import testappsetup, testapp # noqa: F401 -@pytest.mark.xfail def test_welcome(testapp): # noqa: F811 - testapp.logout() result = testapp.get('/') assert result.location == 'http://localhost/login' - testapp.login('user', 'password') + testapp.login('TerryGilliam', 'Terry') + result = testapp.get('/') assert result.location == 'http://localhost/orders' +def test_faq(testapp): # noqa: F811 + result = testapp.get('/faq') + active = result.html.find('li', class_='active') + assert active.a['href'] == '/faq' + + def test_login_get(testapp): # noqa: F811 result = testapp.get('/login') active = result.html.find('li', class_='active') @@ -26,10 +31,26 @@ def test_login_get(testapp): # noqa: F811 login_form = forms[0] assert login_form['action'] == '/login' assert login_form['method'] == 'POST' - assert 'wrong username' not in result + assert 'account is not activated' not in result -def test_faq(testapp): # noqa: F811 - result = testapp.get('/faq') - active = result.html.find('li', class_='active') - assert active.a['href'] == '/faq' +def test_login_ok(testapp): # noqa: F811 + result = testapp.get('/login') + login_form = result.forms[0] + login_form['username'] = 'TerryGilliam' + login_form['password'] = 'Terry' + result = login_form.submit() + assert result.location == 'http://localhost/' + + +@pytest.mark.parametrize( + 'username,password', + [('John', 'Cleese'), ('unknown user', 'wrong password')] + ) +def test_login_denied(testapp, username, password): # noqa: F811 + result = testapp.get('/login') + login_form = result.forms[0] + login_form['username'] = 'John' + login_form['password'] = 'Cleese' + result = login_form.submit() + assert 'account is not activated' in result diff --git a/tests/views/pages.py b/tests/views/pages.py index 69b9e7c..d7a49fd 100644 --- a/tests/views/pages.py +++ b/tests/views/pages.py @@ -3,6 +3,10 @@ import pytest from pyramid.httpexceptions import HTTPFound from pyramid.testing import DummyRequest +from ordr.models.account import Role + +from .. import app_config, dbsession, get_example_user # noqa: F401 + @pytest.mark.parametrize( 'user,location', @@ -20,3 +24,59 @@ def test_faq(): from ordr.views.pages import faq result = faq(None, None) assert result == {} + + +def test_login(): + from ordr.views.pages import login + result = login(None, None) + assert result == { 'loginerror': False } + + +@pytest.mark.parametrize('role', [Role.USER, Role.PURCHASER, Role.ADMIN]) +def test_check_login_ok(dbsession, role): + from ordr.views.pages import check_login + user = get_example_user(role) + dbsession.add(user) + post_data = {'username': user.username, 'password': user.first_name} + request = DummyRequest(dbsession=dbsession, POST=post_data) + result = check_login(None, request) + assert isinstance(result, HTTPFound) + assert result.location == 'http://example.com//' + + +@pytest.mark.parametrize('role', [Role.UNVALIDATED, Role.NEW, Role.INACTIVE]) +def test_check_login_not_activated(dbsession, role): + from ordr.views.pages import check_login + user = get_example_user(role) + dbsession.add(user) + post_data = {'username': user.username, 'password': user.first_name} + request = DummyRequest(dbsession=dbsession, POST=post_data) + result = check_login(None, request) + assert result == { 'loginerror': True } + + +@pytest.mark.parametrize( + 'username,password', [ + ('', ''), + ('TerryGilliam', ''), + ('', 'Terry'), + ('TerryGilliam', 'wrong password'), + ('wrong username', 'Terry'), + ] + ) +def test_check_login_invalid_credentials(dbsession, username, password): + from ordr.views.pages import check_login + user = get_example_user(Role.USER) + dbsession.add(user) + post_data = {'username': username, 'password': password} + request = DummyRequest(dbsession=dbsession, POST=post_data) + result = check_login(None, request) + assert result == { 'loginerror': True } + + +def test_logout(): + from ordr.views.pages import logout + request = DummyRequest() + result = logout(None, request) + assert isinstance(result, HTTPFound) + assert result.location == 'http://example.com//'