Browse Source

refactored account resource and view

login/logout, registration of a new account and resetting a forgotten
password are now child resources of the account resource. The views for
these resources are also grouped together. This should make a reusage of
the code easier
rework
Holger Frey 7 years ago
parent
commit
f8d6d475d1
  1. 8
      ordr/resources/__init__.py
  2. 30
      ordr/resources/account.py
  3. 2
      ordr/templates/account/login.jinja2
  4. 20
      ordr/templates/layout.jinja2
  5. 268
      ordr/views/account.py
  6. 120
      ordr/views/forgotten_password.py
  7. 57
      ordr/views/pages.py
  8. 86
      ordr/views/registration.py
  9. 4
      tests/_functional/__init__.py
  10. 10
      tests/_functional/account/__init__.py
  11. 7
      tests/_functional/account/forgotten_password.py
  12. 35
      tests/_functional/account/login_logout.py
  13. 16
      tests/_functional/account/registration.py
  14. 9
      tests/_functional/account/settings.py
  15. 22
      tests/_functional/layout.py
  16. 4
      tests/_functional/pages.py
  17. 49
      tests/resources/account.py
  18. 8
      tests/resources/root.py
  19. 22
      tests/views/account/__init__.py
  20. 30
      tests/views/account/forgotten_password.py
  21. 95
      tests/views/account/login_logout.py
  22. 20
      tests/views/account/registration.py
  23. 19
      tests/views/account/settings.py
  24. 76
      tests/views/pages.py

8
ordr/resources/__init__.py

@ -2,11 +2,7 @@
from pyramid.security import Allow, Everyone, DENY_ALL from pyramid.security import Allow, Everyone, DENY_ALL
from .account import ( from .account import AccountResource
RegistrationResource,
PasswordResetResource,
AccountResource
)
class RootResource: class RootResource:
@ -38,8 +34,6 @@ class RootResource:
:raises: KeyError if child resource is not found :raises: KeyError if child resource is not found
''' '''
map = { map = {
'register': RegistrationResource,
'forgot': PasswordResetResource,
'account': AccountResource, 'account': AccountResource,
} }
child_class = map[key] child_class = map[key]

30
ordr/resources/account.py

@ -28,7 +28,7 @@ class RegistrationTokenResource(BaseChildResource):
def __acl__(self): def __acl__(self):
''' access controll list for the resource ''' ''' access controll list for the resource '''
return [(Allow, Everyone, 'view'), DENY_ALL] return [(Allow, Everyone, 'register'), DENY_ALL]
class RegistrationResource(BaseChildResource): class RegistrationResource(BaseChildResource):
@ -43,7 +43,7 @@ class RegistrationResource(BaseChildResource):
def __acl__(self): def __acl__(self):
''' access controll list for the resource ''' ''' access controll list for the resource '''
return [(Allow, Everyone, 'view'), DENY_ALL] return [(Allow, Everyone, 'register'), DENY_ALL]
def __getitem__(self, key): def __getitem__(self, key):
''' returns a resource for a valid registration token ''' ''' returns a resource for a valid registration token '''
@ -81,7 +81,7 @@ class PasswordResetTokenResource(BaseChildResource):
def __acl__(self): def __acl__(self):
''' access controll list for the resource ''' ''' access controll list for the resource '''
return [(Allow, Everyone, 'view'), DENY_ALL] return [(Allow, Everyone, 'reset'), DENY_ALL]
def get_reset_form(self, **kwargs): def get_reset_form(self, **kwargs):
''' returns password reset form ''' ''' returns password reset form '''
@ -107,7 +107,7 @@ class PasswordResetResource(BaseChildResource):
def __acl__(self): def __acl__(self):
''' access controll list for the resource ''' ''' access controll list for the resource '''
return [(Allow, Everyone, 'view'), DENY_ALL] return [(Allow, Everyone, 'reset'), DENY_ALL]
def __getitem__(self, key): def __getitem__(self, key):
''' returns a resource for a valid reset password token ''' ''' returns a resource for a valid reset password token '''
@ -156,10 +156,28 @@ class AccountResource(BaseChildResource):
def __acl__(self): def __acl__(self):
''' access controll list for the resource ''' ''' access controll list for the resource '''
return [(Allow, Authenticated, 'edit'), DENY_ALL] return [
(Allow, Everyone, 'view'),
(Allow, Everyone, 'login'),
(Allow, Everyone, 'logout'),
(Allow, Everyone, 'register'),
(Allow, Everyone, 'reset'),
(Allow, Authenticated, 'edit'),
DENY_ALL
]
def __getitem__(self, key): def __getitem__(self, key):
''' returns a resource for a valid change email token ''' ''' returns a resource for child resource '''
# static child resources
map = {
'register': RegistrationResource,
'forgot': PasswordResetResource,
}
if key in map:
child_class = map[key]
return child_class(name=key, parent=self)
# change email verification
token = Token.retrieve(self.request, key, TokenSubject.CHANGE_EMAIL) token = Token.retrieve(self.request, key, TokenSubject.CHANGE_EMAIL)
if token is None: if token is None:
raise KeyError(f'Token {key} not found') raise KeyError(f'Token {key} not found')

2
ordr/templates/pages/login.jinja2 → ordr/templates/account/login.jinja2

@ -12,7 +12,7 @@
<div class="row"> <div class="row">
<div class="col-4 offset-2"> <div class="col-4 offset-2">
<h4 class="mb-4">Login</h4> <h4 class="mb-4">Login</h4>
<form action="/login" method="POST"> <form action="{{ request.resource_url(context, 'login') }}" method="POST">
<div class="form-group"> <div class="form-group">
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> <input type="hidden" name="csrf_token" value="{{get_csrf_token()}}">
<input type="text" class="form-control {% if loginerror %}is-invalid{% endif %}" id="input-username" placeholder="Username" name="username" autofocus="autofocus"> <input type="text" class="form-control {% if loginerror %}is-invalid{% endif %}" id="input-username" placeholder="Username" name="username" autofocus="autofocus">

20
ordr/templates/layout.jinja2

@ -22,30 +22,30 @@
<body> <body>
<nav class="navbar navbar-dark bg-dark navbar-expand-sm"> <nav class="navbar navbar-dark bg-dark navbar-expand-sm">
<a class="navbar-brand text-primary" href="/"><strong>ordr</strong></a> <a class="navbar-brand text-primary" href="{{ request.resource_url(request.root) }}"><strong>ordr</strong></a>
{% if not request.user %} {% if not request.user %}
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item {% if context.nav_active=='welcome' and request.view_name=='login' %}active{% endif %}"> <li class="nav-item {% if context.nav_active=='welcome' and request.view_name=='login' %}active{% endif %}">
<a href="/" class="nav-link">Welcome</a> <a href="{{ request.resource_url(request.root) }}" class="nav-link">Welcome</a>
</li> </li>
<li class="nav-item {% if context.nav_active=='welcome' and request.view_name=='faq' %}active{% endif %}"> <li class="nav-item {% if context.nav_active=='welcome' and request.view_name=='faq' %}active{% endif %}">
<a href="/faq" class="nav-link">FAQs</a> <a href="{{ request.resource_url(request.root, 'faq') }}" class="nav-link">FAQs</a>
</li> </li>
<li class="nav-item {% if context.nav_active=='registration' %}active{% endif %}"> <li class="nav-item {% if context.nav_active=='registration' %}active{% endif %}">
<a href="/register" class="nav-link">Register</a> <a href="{{ request.resource_url(request.root, 'account', 'register') }}" class="nav-link">Register</a>
</li> </li>
</ul> </ul>
{% else %} {% else %}
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item {% if context.nav_active=='orders' %}active{% endif %}"> <li class="nav-item {% if context.nav_active=='orders' %}active{% endif %}">
<a href="/orders" class="nav-link">Orders</a> <a href="{{ request.resource_url(request.root, 'orders') }}" class="nav-link">Orders</a>
</li> </li>
<li class="nav-item {% if context.nav_active=='welcome' and request.view_name=='faq' %}active{% endif %}"> <li class="nav-item {% if context.nav_active=='welcome' and request.view_name=='faq' %}active{% endif %}">
<a href="/faq" class="nav-link">FAQs</a> <a href="{{ request.resource_url(request.root, 'faq') }}" class="nav-link">FAQs</a>
</li> </li>
{% if 'role:admin' in request.user.principals %} {% if 'role:admin' in request.user.principals %}
<li class="nav-item {% if context.nav_active=='admin' %}active{% endif %}"> <li class="nav-item {% if context.nav_active=='admin' %}active{% endif %}">
<a href="/admin" class="nav-link">Admin</a> <a href="{{ request.resource_url(request.root, 'admin') }}" class="nav-link">Admin</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
@ -55,10 +55,10 @@
{{ request.user }} {{ request.user }}
</a> </a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="userDropdown"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="userDropdown">
<a class="dropdown-item" href="/logout">Logout</a> <a class="dropdown-item" href="{{ request.resource_url(request.root, 'account', 'logout') }}">Logout</a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item small" href="/account/settings">Settings</a> <a class="dropdown-item small" href="{{ request.resource_url(request.root, 'account', 'settings') }}">Settings</a>
<a class="dropdown-item small" href="/account/password">Change Password</a> <a class="dropdown-item small" href="{{ request.resource_url(request.root, 'account', 'password') }}">Change Password</a>
</div> </div>
</li> </li>
</ul> </ul>

268
ordr/views/account.py

@ -1,21 +1,279 @@
''' views for user accounts
This includes login, logout, registration, forgotten passwords, changing
settings and passwords
'''
import deform import deform
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget
from pyramid.view import view_config from pyramid.view import view_config
from sqlalchemy import func, or_
from ordr.events import (
ChangeEmailNotification,
PasswordResetNotification,
RegistrationNotification
)
from ordr.models.account import Role, TokenSubject, User
from ordr.events import ChangeEmailNotification
from ordr.models.account import TokenSubject
# account resource root
@view_config( @view_config(
context='ordr.resources.account.AccountResource', context='ordr.resources.account.AccountResource',
permission='edit' permission='view'
) )
def account(context, request): def account(context, request):
''' redirect if '/account' was requested directly ''' ''' redirect if '/account' was requested directly '''
return HTTPFound(request.resource_url(request.root)) return HTTPFound(request.resource_url(request.root))
# login and logout
@view_config(
context='ordr.resources.account.AccountResource',
name='login',
request_method='GET',
permission='login',
renderer='ordr:templates/account/login.jinja2',
)
def login(context, request):
''' shows the login page '''
context.nav_active = 'welcome'
return {'loginerror': False}
@view_config(
context='ordr.resources.account.AccountResource',
name='login',
request_method='POST',
permission='login',
renderer='ordr:templates/account/login.jinja2',
)
def check_login(context, request):
''' check user credentials '''
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)
context.nav_active = 'welcome'
return {'loginerror': True}
@view_config(
context='ordr.resources.account.AccountResource',
name='logout',
permission='logout'
)
def logout(context, request):
''' log out of an user '''
headers = forget(request)
return HTTPFound(request.resource_url(request.root), headers=headers)
# registration process
@view_config(
context='ordr.resources.account.RegistrationResource',
permission='register',
request_method='GET',
renderer='ordr:templates/account/registration_form.jinja2'
)
def registration_form(context, request):
''' show registration form '''
form = context.get_registration_form()
return {'form': form}
@view_config(
context='ordr.resources.account.RegistrationResource',
permission='register',
request_method='POST',
renderer='ordr:templates/account/registration_form.jinja2'
)
def registration_form_processing(context, request):
''' process registration form '''
if 'create' not in request.POST:
return HTTPFound(request.resource_url(request.root))
form = context.get_registration_form()
data = request.POST.items()
try:
appstruct = form.validate(data)
except deform.ValidationFailure as e:
return {'form': form}
# form validation successfull, create user
account = User(
username=appstruct['username'],
first_name=appstruct['first_name'],
last_name=appstruct['last_name'],
email=appstruct['email'],
role=Role.UNVALIDATED
)
account.set_password(appstruct['password'])
request.dbsession.add(account)
# create a verify-new-account token and send email
token = account.issue_token(request, TokenSubject.REGISTRATION)
notification = RegistrationNotification(request, account, {'token': token})
request.registry.notify(notification)
return HTTPFound(request.resource_url(context, 'verify'))
@view_config(
context='ordr.resources.account.RegistrationResource',
name='verify',
permission='register',
request_method='GET',
renderer='ordr:templates/account/registration_verify.jinja2'
)
def registration_verify_email(context, request):
''' show email verification text '''
return {}
@view_config(
context='ordr.resources.account.RegistrationTokenResource',
permission='register',
request_method='GET',
renderer='ordr:templates/account/registration_completed.jinja2'
)
def registration_completed(context, request):
''' registration is completed, awaiting activation by admin '''
token = context.model
account = token.owner
account.role = Role.NEW
request.dbsession.delete(token)
return {}
# forgotten password process
@view_config(
context='ordr.resources.account.PasswordResetResource',
permission='reset',
request_method='GET',
renderer='ordr:templates/account/forgotten_password_form.jinja2'
)
def forgotten_password_form(context, request):
''' show forgotten password form '''
return {'formerror': False}
@view_config(
context='ordr.resources.account.PasswordResetResource',
permission='reset',
request_method='POST',
renderer='ordr:templates/account/forgotten_password_form.jinja2'
)
def forgotten_password_form_processing(context, request):
''' process forgotten password form '''
if 'cancel' in request.POST:
return HTTPFound(request.resource_url(request.root))
identifier = request.POST.get('identifier', '')
account = (
request.dbsession
.query(User)
.filter(or_(
func.lower(User.username) == identifier.lower(),
func.lower(User.email) == identifier.lower()
))
.first()
)
if account is None or not account.is_active:
return {'formerror': True}
# create a verify-new-account token and send email
token = account.issue_token(request, TokenSubject.RESET_PASSWORD)
notification = PasswordResetNotification(
request,
account,
{'token': token}
)
request.registry.notify(notification)
return HTTPFound(request.resource_url(context, 'verify'))
@view_config(
context='ordr.resources.account.PasswordResetResource',
name='verify',
permission='reset',
request_method='GET',
renderer='ordr:templates/account/forgotten_password_verify.jinja2'
)
def forgotten_password_verify_email(context, request):
''' show email verification text '''
return {}
@view_config(
context='ordr.resources.account.PasswordResetResource',
name='completed',
permission='reset',
request_method='GET',
renderer='ordr:templates/account/forgotten_password_completed.jinja2'
)
def forgotten_password_completed(context, request):
''' user is verified, process reset password form '''
return {}
@view_config(
context='ordr.resources.account.PasswordResetTokenResource',
permission='reset',
request_method='GET',
renderer='ordr:templates/account/forgotten_password_reset.jinja2'
)
def reset_password_form(context, request):
''' user is verified, show reset password form '''
form = context.get_reset_form()
return {'form': form}
@view_config(
context='ordr.resources.account.PasswordResetTokenResource',
permission='reset',
request_method='POST',
renderer='ordr:templates/account/forgotten_password_reset.jinja2'
)
def reset_password_form_processing(context, request):
''' process the password reset form '''
if 'change' not in request.POST:
return HTTPFound(request.resource_url(request.root))
form = context.get_reset_form()
data = request.POST.items()
try:
appstruct = form.validate(data)
except deform.ValidationFailure as e:
return {'form': form}
# set new password
token = context.model
account = token.owner
account.set_password(appstruct['password'])
request.dbsession.delete(token)
return HTTPFound(request.resource_url(context.__parent__, 'completed'))
# account settings
@view_config( @view_config(
context='ordr.resources.account.AccountResource', context='ordr.resources.account.AccountResource',
permission='edit', permission='edit',
@ -85,7 +343,7 @@ def settings_form_processing(context, request):
request_method='GET', request_method='GET',
renderer='ordr:templates/account/settings_mail_changed.jinja2' renderer='ordr:templates/account/settings_mail_changed.jinja2'
) )
def verify_email(context, request): def verify_email_change(context, request):
''' show email verification text ''' ''' show email verification text '''
payload = context.model.payload payload = context.model.payload
request.user.email = payload['email'] request.user.email = payload['email']
@ -93,6 +351,8 @@ def verify_email(context, request):
return {} return {}
# change password
@view_config( @view_config(
context='ordr.resources.account.AccountResource', context='ordr.resources.account.AccountResource',
permission='edit', permission='edit',

120
ordr/views/forgotten_password.py

@ -1,120 +0,0 @@
import deform
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
from sqlalchemy import func, or_
from ordr.models.account import User, TokenSubject
from ordr.events import PasswordResetNotification
# below this password length a warning is displayed
MIN_PW_LENGTH = 12
@view_config(
context='ordr.resources.account.PasswordResetResource',
permission='view',
request_method='GET',
renderer='ordr:templates/account/forgotten_password_form.jinja2'
)
def forgotten_password_form(context, request):
''' show forgotten password form '''
return {'formerror': False}
@view_config(
context='ordr.resources.account.PasswordResetResource',
permission='view',
request_method='POST',
renderer='ordr:templates/account/forgotten_password_form.jinja2'
)
def forgotten_password_form_processing(context, request):
''' process forgotten password form '''
if 'cancel' in request.POST:
return HTTPFound(request.resource_url(request.root))
identifier = request.POST.get('identifier', '')
account = (
request.dbsession
.query(User)
.filter(or_(
func.lower(User.username) == identifier.lower(),
func.lower(User.email) == identifier.lower()
))
.first()
)
if account is None or not account.is_active:
return {'formerror': True}
# create a verify-new-account token and send email
token = account.issue_token(request, TokenSubject.RESET_PASSWORD)
notification = PasswordResetNotification(
request,
account,
{'token': token}
)
request.registry.notify(notification)
return HTTPFound(request.resource_url(context, 'verify'))
@view_config(
context='ordr.resources.account.PasswordResetResource',
name='verify',
permission='view',
request_method='GET',
renderer='ordr:templates/account/forgotten_password_verify.jinja2'
)
def verify(context, request):
''' show email verification text '''
return {}
@view_config(
context='ordr.resources.account.PasswordResetResource',
name='completed',
permission='view',
request_method='GET',
renderer='ordr:templates/account/forgotten_password_completed.jinja2'
)
def completed(context, request):
''' user is verified, process reset password form '''
return {}
@view_config(
context='ordr.resources.account.PasswordResetTokenResource',
permission='view',
request_method='GET',
renderer='ordr:templates/account/forgotten_password_reset.jinja2'
)
def reset_password_form(context, request):
''' user is verified, show reset password form '''
form = context.get_reset_form()
return {'form': form}
@view_config(
context='ordr.resources.account.PasswordResetTokenResource',
permission='view',
request_method='POST',
renderer='ordr:templates/account/forgotten_password_reset.jinja2'
)
def reset_password_form_processing(context, request):
''' process the password reset form '''
if 'change' not in request.POST:
return HTTPFound(request.resource_url(request.root))
form = context.get_reset_form()
data = request.POST.items()
try:
appstruct = form.validate(data)
except deform.ValidationFailure as e:
return {'form': form}
# set new password
token = context.model
account = token.owner
account.set_password(appstruct['password'])
request.dbsession.delete(token)
return HTTPFound(request.resource_url(context.__parent__, 'completed'))

57
ordr/views/pages.py

@ -1,9 +1,6 @@
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget
from pyramid.view import view_config from pyramid.view import view_config
from ordr.models import User
@view_config( @view_config(
context='ordr.resources.RootResource', context='ordr.resources.RootResource',
@ -11,8 +8,10 @@ from ordr.models import User
) )
def welcome(context, request): def welcome(context, request):
''' web root redirects ''' ''' web root redirects '''
next = 'orders' if request.user else 'login' if request.user:
redirect_to = request.resource_url(context, next) redirect_to = request.resource_url(context, 'orders')
else:
redirect_to = request.resource_url(context, 'account', 'login')
return HTTPFound(redirect_to) return HTTPFound(redirect_to)
@ -25,51 +24,3 @@ def welcome(context, request):
def faq(context, request): def faq(context, request):
''' displays the FAQ page ''' ''' displays the FAQ page '''
return {} return {}
@view_config(
context='ordr.resources.RootResource',
name='login',
request_method='GET',
permission='view',
renderer='ordr:templates/pages/login.jinja2',
)
def login(context, request):
''' shows the login page '''
return {'loginerror': False}
@view_config(
context='ordr.resources.RootResource',
name='login',
request_method='POST',
permission='view',
renderer='ordr:templates/pages/login.jinja2',
)
def check_login(context, request):
''' check user credentials '''
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)

86
ordr/views/registration.py

@ -1,86 +0,0 @@
import deform
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
from ordr.models.account import User, Role, TokenSubject
from ordr.events import RegistrationNotification
# below this password length a warning is displayed
MIN_PW_LENGTH = 12
@view_config(
context='ordr.resources.account.RegistrationResource',
permission='view',
request_method='GET',
renderer='ordr:templates/account/registration_form.jinja2'
)
def registration_form(context, request):
''' show registration form '''
form = context.get_registration_form()
return {'form': form}
@view_config(
context='ordr.resources.account.RegistrationResource',
permission='view',
request_method='POST',
renderer='ordr:templates/account/registration_form.jinja2'
)
def registration_form_processing(context, request):
''' process registration form '''
if 'create' not in request.POST:
return HTTPFound(request.resource_url(request.root))
form = context.get_registration_form()
data = request.POST.items()
try:
appstruct = form.validate(data)
except deform.ValidationFailure as e:
return {'form': form}
# form validation successfull, create user
account = User(
username=appstruct['username'],
first_name=appstruct['first_name'],
last_name=appstruct['last_name'],
email=appstruct['email'],
role=Role.UNVALIDATED
)
account.set_password(appstruct['password'])
request.dbsession.add(account)
# create a verify-new-account token and send email
token = account.issue_token(request, TokenSubject.REGISTRATION)
notification = RegistrationNotification(request, account, {'token': token})
request.registry.notify(notification)
return HTTPFound(request.resource_url(context, 'verify'))
@view_config(
context='ordr.resources.account.RegistrationResource',
name='verify',
permission='view',
request_method='GET',
renderer='ordr:templates/account/registration_verify.jinja2'
)
def verify(context, request):
''' show email verification text '''
return {}
@view_config(
context='ordr.resources.account.RegistrationTokenResource',
permission='view',
request_method='GET',
renderer='ordr:templates/account/registration_completed.jinja2'
)
def completed(context, request):
''' registration is completed, awaiting activation by admin '''
token = context.model
account = token.owner
account.role = Role.NEW
request.dbsession.delete(token)
return {}

4
tests/_functional/__init__.py

@ -23,7 +23,7 @@ class CustomTestApp(webtest.TestApp):
def login(self, username, password): def login(self, username, password):
''' login ''' ''' login '''
self.logout() self.logout()
result = self.get('/login') result = self.get('/account/login')
login_form = result.forms[0] login_form = result.forms[0]
login_form['username'] = username login_form['username'] = username
login_form['password'] = password login_form['password'] = password
@ -33,7 +33,7 @@ class CustomTestApp(webtest.TestApp):
def logout(self): def logout(self):
''' logout ''' ''' logout '''
self.get('/logout') self.get('/account/logout')
def reset(self): def reset(self):
''' reset the webapp ''' ''' reset the webapp '''

10
tests/_functional/account/__init__.py

@ -0,0 +1,10 @@
''' functional tests for ordr accounts '''
from .. import testappsetup, testapp, get_token_url # noqa: F401
def test_account_root(testapp): # noqa: F811
''' check the redirect if '/account' is requested '''
testapp.login('TerryGilliam', 'Terry')
response = testapp.get('/account')
assert response.location == 'http://localhost/'

7
tests/_functional/forgotten_password.py → tests/_functional/account/forgotten_password.py

@ -7,7 +7,7 @@ from . import testappsetup, testapp, get_token_url # noqa: F401
def test_forgot_password_process(testapp): # noqa: F811 def test_forgot_password_process(testapp): # noqa: F811
''' test the forgot password form ''' ''' test the forgot password form '''
response = testapp.get('/forgot') response = testapp.get('/account/forgot')
active_nav = response.html.find('li', class_='active') active_nav = response.html.find('li', class_='active')
active_step = response.html.find('p', class_='text-primary') active_step = response.html.find('p', class_='text-primary')
assert active_nav is None assert active_nav is None
@ -27,10 +27,11 @@ def test_forgot_password_process(testapp): # noqa: F811
assert 'Username or email address unknown' in response assert 'Username or email address unknown' in response
# fill out this form with valid data # fill out this form with valid data
response = testapp.get('/account/forgot')
form = response.form form = response.form
form['identifier'] = 'TerryGilliam' form['identifier'] = 'TerryGilliam'
response = form.submit(name='send_mail') response = form.submit(name='send_mail')
assert response.location == 'http://localhost/forgot/verify' assert response.location == 'http://localhost/account/forgot/verify'
response = response.follow() response = response.follow()
active_nav = response.html.find('li', class_='active') active_nav = response.html.find('li', class_='active')
@ -70,7 +71,7 @@ def test_forgot_password_process(testapp): # noqa: F811
form['password'] = 'Lost in La Mancha' form['password'] = 'Lost in La Mancha'
form['password-confirm'] = 'Lost in La Mancha' form['password-confirm'] = 'Lost in La Mancha'
response = form.submit(name='change') response = form.submit(name='change')
assert response.location == 'http://localhost/forgot/completed' assert response.location == 'http://localhost/account/forgot/completed'
response = response.follow() response = response.follow()
active_nav = response.html.find('li', class_='active') active_nav = response.html.find('li', class_='active')

35
tests/_functional/login_logout.py → tests/_functional/account/login_logout.py

@ -7,18 +7,17 @@ from . import testappsetup, testapp # noqa: F401
def test_login_get(testapp): # noqa: F811 def test_login_get(testapp): # noqa: F811
''' test the login form ''' ''' test the login form '''
response = testapp.get('/login') response = testapp.get('/account/login')
active = response.html.find('li', class_='active') active = response.html.find('li', class_='active')
assert active.a['href'] == '/' form = response.form
expected = {'/', '/faq', '/register', '/forgot', '/register'} assert active.a['href'] == 'http://localhost/'
hrefs = {a['href'] for a in response.html.find_all('a')} assert form.action == 'http://localhost/account/login'
assert expected == hrefs
def test_login_ok(testapp): # noqa: F811 def test_login_ok(testapp): # noqa: F811
''' test login form with valid credentials ''' ''' test login form with valid credentials '''
response = testapp.get('/login') response = testapp.get('/account/login')
login_form = response.forms[0] login_form = response.forms[0]
login_form['username'] = 'TerryGilliam' login_form['username'] = 'TerryGilliam'
@ -27,6 +26,9 @@ def test_login_ok(testapp): # noqa: F811
assert response.location == 'http://localhost/' assert response.location == 'http://localhost/'
response = testapp.get('/faq')
assert 'TerryGilliam' in response
@pytest.mark.parametrize( # noqa: F811 @pytest.mark.parametrize( # noqa: F811
'username,password', 'username,password',
@ -34,7 +36,7 @@ def test_login_ok(testapp): # noqa: F811
) )
def test_login_denied(testapp, username, password): def test_login_denied(testapp, username, password):
''' test login form with invalid credentials ''' ''' test login form with invalid credentials '''
response = testapp.get('/login') response = testapp.get('/account/login')
login_form = response.forms[0] login_form = response.forms[0]
login_form['username'] = 'John' login_form['username'] = 'John'
@ -42,3 +44,22 @@ def test_login_denied(testapp, username, password):
response = login_form.submit() response = login_form.submit()
assert 'account is not activated' in response assert 'account is not activated' in response
def test_logout(testapp): # noqa: F811
''' test login form with valid credentials '''
response = testapp.get('/account/login')
login_form = response.forms[0]
login_form['username'] = 'TerryGilliam'
login_form['password'] = 'Terry'
login_form.submit()
response = testapp.get('/faq')
assert 'TerryGilliam' in response
response = testapp.get('/account/logout')
assert response.location == 'http://localhost/'
response = testapp.get('/faq')
assert 'TerryGilliam' not in response

16
tests/_functional/registration.py → tests/_functional/account/registration.py

@ -7,15 +7,15 @@ from . import testappsetup, testapp, get_token_url # noqa: F401
def test_registration_form(testapp): # noqa: F811 def test_registration_form(testapp): # noqa: F811
''' test the registration form ''' ''' test the registration form '''
response = testapp.get('/register') response = testapp.get('/account/register')
active = response.html.find('li', class_='active') active = response.html.find('li', class_='active')
assert active.a['href'] == '/register' assert active.a['href'] == 'http://localhost/account/register'
assert 'Registration' in response.html.title.text assert 'Registration' in response.html.title.text
def test_registration_form_invalid(testapp): # noqa: F811 def test_registration_form_invalid(testapp): # noqa: F811
''' test the registration form with invalid data ''' ''' test the registration form with invalid data '''
response = testapp.get('/register') response = testapp.get('/account/register')
form = response.form form = response.form
form['email'] = 'not an email address' form['email'] = 'not an email address'
@ -27,7 +27,7 @@ def test_registration_form_invalid(testapp): # noqa: F811
def test_registration_process(testapp): # noqa: F811 def test_registration_process(testapp): # noqa: F811
''' test the registration process with valid data ''' ''' test the registration process with valid data '''
response = testapp.get('/register') response = testapp.get('/account/register')
form = response.form form = response.form
form['username'] = 'AmyMcDonald', form['username'] = 'AmyMcDonald',
@ -37,11 +37,11 @@ def test_registration_process(testapp): # noqa: F811
form['password'] = 'Make Amy McDonald A Rich Girl Fund', form['password'] = 'Make Amy McDonald A Rich Girl Fund',
form['password-confirm'] = 'Make Amy McDonald A Rich Girl Fund', form['password-confirm'] = 'Make Amy McDonald A Rich Girl Fund',
response = form.submit(name='create') response = form.submit(name='create')
assert response.location == 'http://localhost/register/verify' assert response.location == 'http://localhost/account/register/verify'
response = response.follow() response = response.follow()
active = response.html.find('li', class_='active') active = response.html.find('li', class_='active')
assert active.a['href'] == '/register' assert active.a['href'] == 'http://localhost/account/register'
assert 'Please follow the link in the email' in response assert 'Please follow the link in the email' in response
assert 'Registration' in response.html.title.text assert 'Registration' in response.html.title.text
@ -50,9 +50,9 @@ def test_registration_process(testapp): # noqa: F811
email = mailer.outbox[-1] email = mailer.outbox[-1]
assert email.subject == '[ordr] Please verify your email address' assert email.subject == '[ordr] Please verify your email address'
token_link = get_token_url(email, prefix='/register/') token_link = get_token_url(email, prefix='/account/register/')
response = testapp.get(token_link) response = testapp.get(token_link)
active = response.html.find('li', class_='active') active = response.html.find('li', class_='active')
assert active.a['href'] == '/register' assert active.a['href'] == 'http://localhost/account/register'
assert 'Registration Completed' in response assert 'Registration Completed' in response
assert 'Registration' in response.html.title.text assert 'Registration' in response.html.title.text

9
tests/_functional/account.py → tests/_functional/account/settings.py

@ -2,14 +2,7 @@
from pyramid_mailer import get_mailer from pyramid_mailer import get_mailer
from . import testappsetup, testapp, get_token_url # noqa: F401 from .. import testappsetup, testapp, get_token_url # noqa: F401
def test_account_root(testapp): # noqa: F811
''' check the redirect if '/account' is requested '''
testapp.login('TerryGilliam', 'Terry')
response = testapp.get('/account')
assert response.location == 'http://localhost/'
def test_account_change_settings(testapp): # noqa: F811 def test_account_change_settings(testapp): # noqa: F811

22
tests/_functional/layout.py

@ -13,7 +13,12 @@ def test_navbar_no_user(testapp): # noqa: F811
''' test the navigation on top of the page for an unauthenticated user ''' ''' test the navigation on top of the page for an unauthenticated user '''
response = testapp.get('/faq') response = testapp.get('/faq')
navbar = response.html.find('nav', class_='navbar-dark') navbar = response.html.find('nav', class_='navbar-dark')
expected = ['/', '/', '/faq', '/register'] expected = [
'http://localhost/',
'http://localhost/',
'http://localhost/faq',
'http://localhost/account/register'
]
hrefs = [a['href'] for a in navbar.find_all('a')] hrefs = [a['href'] for a in navbar.find_all('a')]
assert expected == hrefs assert expected == hrefs
@ -25,7 +30,7 @@ def test_navbar_no_user(testapp): # noqa: F811
'username,password,extras', [ 'username,password,extras', [
('TerryGilliam', 'Terry', []), ('TerryGilliam', 'Terry', []),
('EricIdle', 'Eric', []), ('EricIdle', 'Eric', []),
('TerryJones', 'Terry', ['/admin']), ('TerryJones', 'Terry', ['http://localhost/admin']),
] ]
) )
def test_navbar_with_user(testapp, username, password, extras): def test_navbar_with_user(testapp, username, password, extras):
@ -34,9 +39,18 @@ def test_navbar_with_user(testapp, username, password, extras):
response = testapp.get('/faq') response = testapp.get('/faq')
navbar = response.html.find('nav', class_='navbar-dark') navbar = response.html.find('nav', class_='navbar-dark')
hrefs = [a['href'] for a in navbar.find_all('a')] hrefs = [a['href'] for a in navbar.find_all('a')]
expected = ['/', '/orders', '/faq'] expected = [
'http://localhost/',
'http://localhost/orders',
'http://localhost/faq'
]
expected.extend(extras) expected.extend(extras)
expected.extend(['#', '/logout', '/account/settings', '/account/password']) expected.extend([
'#',
'http://localhost/account/logout',
'http://localhost/account/settings',
'http://localhost/account/password'
])
assert expected == hrefs assert expected == hrefs
assert 'nav-item dropdown' in response assert 'nav-item dropdown' in response

4
tests/_functional/pages.py

@ -6,7 +6,7 @@ from . import testappsetup, testapp # noqa: F401
def test_welcome(testapp): # noqa: F811 def test_welcome(testapp): # noqa: F811
''' test the redirects on web root ''' ''' test the redirects on web root '''
response = testapp.get('/') response = testapp.get('/')
assert response.location == 'http://localhost/login' assert response.location == 'http://localhost/account/login'
testapp.login('TerryGilliam', 'Terry') testapp.login('TerryGilliam', 'Terry')
@ -18,4 +18,4 @@ def test_faq(testapp): # noqa: F811
''' test the faq page ''' ''' test the faq page '''
response = testapp.get('/faq') response = testapp.get('/faq')
active = response.html.find('li', class_='active') active = response.html.find('li', class_='active')
assert active.a['href'] == '/faq' assert active.a['href'] == 'http://localhost/faq'

49
tests/resources/account.py

@ -15,7 +15,7 @@ def test_registration_token_acl():
parent = DummyResource(request='request') parent = DummyResource(request='request')
resource = RegistrationTokenResource('name', parent) resource = RegistrationTokenResource('name', parent)
assert resource.__acl__() == [(Allow, Everyone, 'view'), DENY_ALL] assert resource.__acl__() == [(Allow, Everyone, 'register'), DENY_ALL]
def test_registration_acl(): def test_registration_acl():
@ -26,7 +26,7 @@ def test_registration_acl():
parent = DummyResource(request='request') parent = DummyResource(request='request')
resource = RegistrationResource('a name', parent) resource = RegistrationResource('a name', parent)
assert resource.__acl__() == [(Allow, Everyone, 'view'), DENY_ALL] assert resource.__acl__() == [(Allow, Everyone, 'register'), DENY_ALL]
def test_registration_get_registration_form(): def test_registration_get_registration_form():
@ -97,7 +97,7 @@ def test_password_reset_token_acl():
parent = DummyResource(request='request') parent = DummyResource(request='request')
resource = PasswordResetTokenResource('name', parent) resource = PasswordResetTokenResource('name', parent)
assert resource.__acl__() == [(Allow, Everyone, 'view'), DENY_ALL] assert resource.__acl__() == [(Allow, Everyone, 'reset'), DENY_ALL]
def test_password_reset_token_get_reset_form(): def test_password_reset_token_get_reset_form():
@ -124,7 +124,7 @@ def test_password_reset_acl():
parent = DummyResource(request='request') parent = DummyResource(request='request')
resource = PasswordResetResource('a name', parent) resource = PasswordResetResource('a name', parent)
assert resource.__acl__() == [(Allow, Everyone, 'view'), DENY_ALL] assert resource.__acl__() == [(Allow, Everyone, 'reset'), DENY_ALL]
def test_password_reset_getitem_found(dbsession): # noqa: F811 def test_password_reset_getitem_found(dbsession): # noqa: F811
@ -171,7 +171,7 @@ def test_password_reset_getitem_not_found(dbsession): # noqa: F811
resource['unknown hash'] resource['unknown hash']
def test_change_email_reset_token_acl(dbsession): # noqa: F811 def test_change_email_token_acl(dbsession): # noqa: F811
''' test access controll list for PasswordResetTokenResource ''' ''' test access controll list for PasswordResetTokenResource '''
from pyramid.security import Allow, DENY_ALL from pyramid.security import Allow, DENY_ALL
from ordr.models.account import Role, Token, TokenSubject from ordr.models.account import Role, Token, TokenSubject
@ -204,17 +204,50 @@ def test_account_resource_set_model_from_request():
def test_account_resource_acl(): def test_account_resource_acl():
''' test access controll list for PasswordResetResource ''' ''' test access controll list for PasswordResetResource '''
from pyramid.security import Allow, Authenticated, DENY_ALL from pyramid.security import (
Allow,
Everyone,
Authenticated,
DENY_ALL
)
from ordr.resources.account import AccountResource from ordr.resources.account import AccountResource
request = DummyRequest() request = DummyRequest()
parent = DummyResource(request=request) parent = DummyResource(request=request)
resource = AccountResource('a name', parent) resource = AccountResource('a name', parent)
assert resource.__acl__() == [(Allow, Authenticated, 'edit'), DENY_ALL] assert resource.__acl__() == [
(Allow, Everyone, 'view'),
(Allow, Everyone, 'login'),
(Allow, Everyone, 'logout'),
(Allow, Everyone, 'register'),
(Allow, Everyone, 'reset'),
(Allow, Authenticated, 'edit'),
DENY_ALL
]
@pytest.mark.parametrize('key', ['register', 'forgot']) # noqa: F811
def test_account_resource_getitem_static(dbsession, key):
''' test '__getitem__()' method returns static resources '''
from ordr.resources.account import (
AccountResource,
PasswordResetResource,
RegistrationResource
)
request = DummyRequest(dbsession=dbsession)
parent = DummyResource(request=request)
resource = AccountResource('some name', parent)
result = resource[key]
if key == 'register':
assert isinstance(result, RegistrationResource)
elif key == 'forgot':
assert isinstance(result, PasswordResetResource)
def test_account_resource_getitem_found(dbsession): # noqa: F811 def test_account_resource_getitem_token(dbsession): # noqa: F811
''' test '__getitem__()' method returns child resource ''' ''' test '__getitem__()' method returns child resource '''
from ordr.models.account import Role, TokenSubject from ordr.models.account import Role, TokenSubject
from ordr.resources.account import ( from ordr.resources.account import (

8
tests/resources/root.py

@ -2,11 +2,7 @@
import pytest import pytest
from ordr.resources.account import ( from ordr.resources.account import AccountResource
RegistrationResource,
PasswordResetResource,
AccountResource
)
def test_root_init(): def test_root_init():
@ -28,8 +24,6 @@ def test_root_acl():
@pytest.mark.parametrize( @pytest.mark.parametrize(
'key,resource_class', [ 'key,resource_class', [
('register', RegistrationResource),
('forgot', PasswordResetResource),
('account', AccountResource) ('account', AccountResource)
] ]
) )

22
tests/views/account/__init__.py

@ -0,0 +1,22 @@
from pyramid.httpexceptions import HTTPFound
from pyramid.testing import DummyRequest
from ... import ( # noqa: F401
app_config,
dbsession,
get_example_user,
get_post_request
)
# test for account resource root
def test_account_redirect(dbsession): # noqa: F811
''' redirect on root of account resource '''
from ordr.views.account import account
request = DummyRequest(dbsession=dbsession)
result = account(None, request)
assert isinstance(result, HTTPFound)
assert result.location == 'http://example.com//'

30
tests/views/forgotten_password.py → tests/views/account/forgotten_password.py

@ -4,7 +4,7 @@ import pytest
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from pyramid.testing import DummyRequest, DummyResource from pyramid.testing import DummyRequest, DummyResource
from .. import ( # noqa: F401 from ... import ( # noqa: F401
app_config, app_config,
dbsession, dbsession,
get_example_user, get_example_user,
@ -15,7 +15,7 @@ from .. import ( # noqa: F401
def test_forgotten_password_form(): def test_forgotten_password_form():
''' test the view for the forgotten password form ''' ''' test the view for the forgotten password form '''
from ordr.resources.account import PasswordResetResource from ordr.resources.account import PasswordResetResource
from ordr.views.forgotten_password import forgotten_password_form from ordr.views.account import forgotten_password_form
request = DummyRequest() request = DummyRequest()
parent = DummyResource(request=request) parent = DummyResource(request=request)
@ -33,7 +33,7 @@ def test_forgotten_password_processing_ok(dbsession, identifier):
''' test the processing of the forgotten password form ''' ''' test the processing of the forgotten password form '''
from ordr.models.account import Role, TokenSubject from ordr.models.account import Role, TokenSubject
from ordr.resources.account import PasswordResetResource from ordr.resources.account import PasswordResetResource
from ordr.views.forgotten_password import ( from ordr.views.account import (
forgotten_password_form_processing forgotten_password_form_processing
) )
@ -70,7 +70,7 @@ def test_forgotten_password_processing_not_ok(dbsession, identifier):
''' test error processing of the forgotten password form ''' ''' test error processing of the forgotten password form '''
from ordr.models.account import Role, Token from ordr.models.account import Role, Token
from ordr.resources.account import PasswordResetResource from ordr.resources.account import PasswordResetResource
from ordr.views.forgotten_password import ( from ordr.views.account import (
forgotten_password_form_processing forgotten_password_form_processing
) )
@ -95,7 +95,7 @@ def test_forgotten_password_processing_cancel(dbsession): # noqa: F811
''' test the canceling of the forgotten password form ''' ''' test the canceling of the forgotten password form '''
from ordr.models.account import Token from ordr.models.account import Token
from ordr.resources.account import PasswordResetResource from ordr.resources.account import PasswordResetResource
from ordr.views.forgotten_password import ( from ordr.views.account import (
forgotten_password_form_processing forgotten_password_form_processing
) )
@ -113,17 +113,17 @@ def test_forgotten_password_processing_cancel(dbsession): # noqa: F811
assert dbsession.query(Token).count() == 0 assert dbsession.query(Token).count() == 0
def test_verify(): def test_forgotten_password_verify_email():
''' test the message view for check your email ''' ''' test the message view for check your email '''
from ordr.views.forgotten_password import verify from ordr.views.account import forgotten_password_verify_email
result = verify(None, None) result = forgotten_password_verify_email(None, None)
assert result == {} assert result == {}
def test_completed(): def test_forgotten_password_completed():
''' test the view for a completed reset process ''' ''' test the view for a completed reset process '''
from ordr.views.forgotten_password import completed from ordr.views.account import forgotten_password_completed
result = completed(None, None) result = forgotten_password_completed(None, None)
assert result == {} assert result == {}
@ -131,7 +131,7 @@ def test_reset_password_form():
''' test reset password form view ''' ''' test reset password form view '''
from ordr.resources.account import PasswordResetTokenResource from ordr.resources.account import PasswordResetTokenResource
from ordr.schemas.account import ResetPasswordSchema from ordr.schemas.account import ResetPasswordSchema
from ordr.views.forgotten_password import reset_password_form from ordr.views.account import reset_password_form
request = DummyRequest() request = DummyRequest()
parent = DummyResource(request=request) parent = DummyResource(request=request)
@ -147,7 +147,7 @@ def test_reset_password_form_processing_valid(dbsession): # noqa: F811
''' test reset password form processing ''' ''' test reset password form processing '''
from ordr.models.account import User, Role, Token, TokenSubject from ordr.models.account import User, Role, Token, TokenSubject
from ordr.resources.account import PasswordResetTokenResource from ordr.resources.account import PasswordResetTokenResource
from ordr.views.forgotten_password import reset_password_form_processing from ordr.views.account import reset_password_form_processing
data = { data = {
'__start__': 'password:mapping', '__start__': 'password:mapping',
@ -185,7 +185,7 @@ def test_reset_password_form_processing_invalid_data(dbsession): # noqa: F811
from ordr.models.account import Role, Token, TokenSubject from ordr.models.account import Role, Token, TokenSubject
from ordr.resources.account import PasswordResetTokenResource from ordr.resources.account import PasswordResetTokenResource
from ordr.schemas.account import ResetPasswordSchema from ordr.schemas.account import ResetPasswordSchema
from ordr.views.forgotten_password import reset_password_form_processing from ordr.views.account import reset_password_form_processing
data = { data = {
'__start__': 'password:mapping', '__start__': 'password:mapping',
@ -216,7 +216,7 @@ def test_reset_password_form_processing_cancel(dbsession): # noqa: F811
''' test reset password form processing ''' ''' test reset password form processing '''
from ordr.models.account import Role, Token, TokenSubject from ordr.models.account import Role, Token, TokenSubject
from ordr.resources.account import PasswordResetTokenResource from ordr.resources.account import PasswordResetTokenResource
from ordr.views.forgotten_password import reset_password_form_processing from ordr.views.account import reset_password_form_processing
data = { data = {
'__start__': 'password:mapping', '__start__': 'password:mapping',

95
tests/views/account/login_logout.py

@ -0,0 +1,95 @@
import pytest
from pyramid.httpexceptions import HTTPFound
from pyramid.testing import DummyRequest, DummyResource
from ordr.models.account import Role
from ... import ( # noqa: F401
app_config,
dbsession,
get_example_user,
get_post_request
)
def test_login():
''' test the view for the login form '''
from ordr.views.account import login
context = DummyResource(nav_active=None)
result = login(context, None)
assert result == {'loginerror': False}
assert context.nav_active == 'welcome'
@pytest.mark.parametrize( # noqa: F811
'role', [Role.USER, Role.PURCHASER, Role.ADMIN]
)
def test_check_login_ok(dbsession, role):
''' test the processing of the login form with valid credentials '''
from ordr.views.account 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)
context = DummyResource(nav_active=None)
result = check_login(context, request)
assert isinstance(result, HTTPFound)
assert result.location == 'http://example.com//'
@pytest.mark.parametrize( # noqa: F811
'role', [Role.UNVALIDATED, Role.NEW, Role.INACTIVE]
)
def test_check_login_not_activated(dbsession, role):
''' test the processing of the login form with an inactive user '''
from ordr.views.account 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)
context = DummyResource(nav_active=None)
result = check_login(context, request)
assert result == {'loginerror': True}
assert context.nav_active == 'welcome'
@pytest.mark.parametrize( # noqa: F811
'username,password', [
('', ''),
('TerryGilliam', ''),
('', 'Terry'),
('TerryGilliam', 'wrong password'),
('wrong username', 'Terry'),
]
)
def test_check_login_invalid_credentials(dbsession, username, password):
''' test the processing of the login form with invalid credentials '''
from ordr.views.account 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)
context = DummyResource(nav_active=None)
result = check_login(context, request)
assert result == {'loginerror': True}
assert context.nav_active == 'welcome'
def test_logout():
''' test the logout view '''
from ordr.views.account import logout
request = DummyRequest()
result = logout(None, request)
assert isinstance(result, HTTPFound)
assert result.location == 'http://example.com//'

20
tests/views/registration.py → tests/views/account/registration.py

@ -3,7 +3,7 @@ import deform
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from pyramid.testing import DummyRequest, DummyResource from pyramid.testing import DummyRequest, DummyResource
from .. import ( # noqa: F401 from ... import ( # noqa: F401
app_config, app_config,
dbsession, dbsession,
get_example_user, get_example_user,
@ -28,7 +28,7 @@ def test_registration_form():
''' test the view for the registration form ''' ''' test the view for the registration form '''
from ordr.resources.account import RegistrationResource from ordr.resources.account import RegistrationResource
from ordr.schemas.account import RegistrationSchema from ordr.schemas.account import RegistrationSchema
from ordr.views.registration import registration_form from ordr.views.account import registration_form
request = DummyRequest() request = DummyRequest()
parent = DummyResource(request=request) parent = DummyResource(request=request)
@ -44,7 +44,7 @@ def test_registration_form_valid(dbsession): # noqa: F811
''' test processing the registration form with valid data ''' ''' test processing the registration form with valid data '''
from ordr.models.account import User, Role, TokenSubject from ordr.models.account import User, Role, TokenSubject
from ordr.resources.account import RegistrationResource from ordr.resources.account import RegistrationResource
from ordr.views.registration import registration_form_processing from ordr.views.account import registration_form_processing
data = REGISTRATION_FORM_DATA.copy() data = REGISTRATION_FORM_DATA.copy()
request = get_post_request(data, dbsession=dbsession) request = get_post_request(data, dbsession=dbsession)
@ -76,7 +76,7 @@ def test_registration_form_valid(dbsession): # noqa: F811
def test_registration_form_invalid(dbsession): # noqa: F811 def test_registration_form_invalid(dbsession): # noqa: F811
''' test processing registration form with invalid data ''' ''' test processing registration form with invalid data '''
from ordr.views.registration import registration_form_processing from ordr.views.account import registration_form_processing
from ordr.resources.account import RegistrationResource from ordr.resources.account import RegistrationResource
data = REGISTRATION_FORM_DATA.copy() data = REGISTRATION_FORM_DATA.copy()
@ -91,7 +91,7 @@ def test_registration_form_invalid(dbsession): # noqa: F811
def test_registration_form_no_create_button(dbsession): # noqa: F811 def test_registration_form_no_create_button(dbsession): # noqa: F811
''' test processing registration form, create button not clicked ''' ''' test processing registration form, create button not clicked '''
from ordr.views.registration import registration_form_processing from ordr.views.account import registration_form_processing
from ordr.resources.account import RegistrationResource from ordr.resources.account import RegistrationResource
data = REGISTRATION_FORM_DATA.copy() data = REGISTRATION_FORM_DATA.copy()
@ -104,17 +104,17 @@ def test_registration_form_no_create_button(dbsession): # noqa: F811
assert result.location == 'http://example.com//' assert result.location == 'http://example.com//'
def test_registration_verify(): def test_registration_verify_email():
''' test the view displaying that a verifcation email has been sent ''' ''' test the view displaying that a verifcation email has been sent '''
from ordr.views.registration import verify from ordr.views.account import registration_verify_email
result = verify(None, None) result = registration_verify_email(None, None)
assert result == {} assert result == {}
def test_registration_completed(dbsession): # noqa: F811 def test_registration_completed(dbsession): # noqa: F811
''' test the view for the completed registration process ''' ''' test the view for the completed registration process '''
from ordr.models.account import User, Role, Token, TokenSubject from ordr.models.account import User, Role, Token, TokenSubject
from ordr.views.registration import completed from ordr.views.account import registration_completed
request = DummyRequest(dbsession=dbsession) request = DummyRequest(dbsession=dbsession)
user = get_example_user(Role.UNVALIDATED) user = get_example_user(Role.UNVALIDATED)
@ -123,7 +123,7 @@ def test_registration_completed(dbsession): # noqa: F811
dbsession.flush() dbsession.flush()
token = user.tokens[0] token = user.tokens[0]
context = DummyResource(model=token) context = DummyResource(model=token)
result = completed(context, request) result = registration_completed(context, request)
assert result == {} assert result == {}
assert user.role == Role.NEW assert user.role == Role.NEW

19
tests/views/account.py → tests/views/account/settings.py

@ -3,7 +3,7 @@ import deform
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from pyramid.testing import DummyRequest, DummyResource from pyramid.testing import DummyRequest, DummyResource
from .. import ( # noqa: F401 from ... import ( # noqa: F401
app_config, app_config,
dbsession, dbsession,
get_example_user, get_example_user,
@ -11,17 +11,6 @@ from .. import ( # noqa: F401
) )
def test_account_redirect():
''' redirect on root of account resource '''
from ordr.views.account import account
request = DummyRequest()
result = account(None, request)
assert isinstance(result, HTTPFound)
assert result.location == 'http://example.com//'
def test_settings_form(): def test_settings_form():
''' tests for displaying the settings form ''' ''' tests for displaying the settings form '''
from ordr.models.account import Role from ordr.models.account import Role
@ -181,10 +170,10 @@ def test_settings_form_processing_cancel(dbsession): # noqa: F811
assert account.first_name == 'Terry' assert account.first_name == 'Terry'
def test_verify_email(dbsession): # noqa: F811 def test_verify_email_change(dbsession): # noqa: F811
''' tests for processing the change password form ''' ''' tests for processing the change password form '''
from ordr.models.account import Role, Token, TokenSubject from ordr.models.account import Role, Token, TokenSubject
from ordr.views.account import verify_email from ordr.views.account import verify_email_change
user = get_example_user(Role.USER) user = get_example_user(Role.USER)
request = DummyRequest(dbsession=dbsession, user=user) request = DummyRequest(dbsession=dbsession, user=user)
@ -199,7 +188,7 @@ def test_verify_email(dbsession): # noqa: F811
token = dbsession.query(Token).first() token = dbsession.query(Token).first()
context = DummyResource(model=token) context = DummyResource(model=token)
result = verify_email(context, request) result = verify_email_change(context, request)
assert result == {} assert result == {}
assert user.email == 'amy@example.com' assert user.email == 'amy@example.com'
assert dbsession.query(Token).count() == 0 assert dbsession.query(Token).count() == 0

76
tests/views/pages.py

@ -3,14 +3,13 @@ import pytest
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from pyramid.testing import DummyRequest from pyramid.testing import DummyRequest
from ordr.models.account import Role
from .. import app_config, dbsession, get_example_user # noqa: F401 from .. import app_config, dbsession, get_example_user # noqa: F401
@pytest.mark.parametrize( @pytest.mark.parametrize(
'user,location', 'user,location',
[(None, '/login'), ('someone', '/orders')] [(None, '/account/login'), ('someone', '/orders')]
) )
def test_welcome(user, location): def test_welcome(user, location):
''' test redirects on web root ''' ''' test redirects on web root '''
@ -28,76 +27,3 @@ def test_faq():
from ordr.views.pages import faq from ordr.views.pages import faq
result = faq(None, None) result = faq(None, None)
assert result == {} assert result == {}
def test_login():
''' test the view for the login form '''
from ordr.views.pages import login
result = login(None, None)
assert result == {'loginerror': False}
@pytest.mark.parametrize( # noqa: F811
'role', [Role.USER, Role.PURCHASER, Role.ADMIN]
)
def test_check_login_ok(dbsession, role):
''' test the processing of the login form with valid credentials '''
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( # noqa: F811
'role', [Role.UNVALIDATED, Role.NEW, Role.INACTIVE]
)
def test_check_login_not_activated(dbsession, role):
''' test the processing of the login form with an inactive user '''
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( # noqa: F811
'username,password', [
('', ''),
('TerryGilliam', ''),
('', 'Terry'),
('TerryGilliam', 'wrong password'),
('wrong username', 'Terry'),
]
)
def test_check_login_invalid_credentials(dbsession, username, password):
''' test the processing of the login form with invalid credentials '''
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():
''' test the logout view '''
from ordr.views.pages import logout
request = DummyRequest()
result = logout(None, request)
assert isinstance(result, HTTPFound)
assert result.location == 'http://example.com//'