Commits

Gustavo Narea  committed 5be101c

Added sample functional test suite which covers authentication and authorization separately

  • Participants
  • Parent commits f80c64b

Comments (0)

Files changed (5)

File pylonssecuredapp/tests/__init__.py

-"""Pylons application test package
+"""
+Pylons application test package
 
 This package assumes the Pylons environment is already loaded, such as
 when this script is imported from the `nosetests --with-pylons=test.ini`
 This module initializes the application via ``websetup`` (`paster
 setup-app`) and provides the base testing objects.
 """
+
+from os import path
 from unittest import TestCase
 
 from paste.deploy import loadapp
 from routes.util import URLGenerator
 from webtest import TestApp
 
-import pylons.test
+from pylonssecuredapp.model import meta
+from pylonssecuredapp.websetup import setup_db
 
-__all__ = ['environ', 'url', 'TestController']
+__all__ = ['create_db', 'destroy_db', 'TestController']
 
-# Invoke websetup with the current config file
-SetupCommand('setup-app').run([config['__file__']])
 
 environ = {}
 
+
 class TestController(TestCase):
-
-    def __init__(self, *args, **kwargs):
-        if pylons.test.pylonsapp:
-            wsgiapp = pylons.test.pylonsapp
-        else:
-            wsgiapp = loadapp('config:%s' % config['__file__'])
+    """
+    Base test case for functional tests in the Web interface.
+    
+    """
+    
+    # Protected areas should be tested with authentication disabled:
+    application_under_test = 'main_without_authn'
+    
+    def setUp(self):
+        """Load the application and create the database with sample records."""
+        # Loading the application:
+        conf_dir = config['here']
+        wsgiapp = loadapp('config:test.ini#%s' % self.application_under_test,
+                          relative_to=conf_dir)
         self.app = TestApp(wsgiapp)
         url._push_object(URLGenerator(config['routes.map'], environ))
-        TestCase.__init__(self, *args, **kwargs)
+        # Creating a temporary database with mock records:
+        create_db()
+    
+    def tearDown(self):
+        """Clean up the database"""
+        destroy_db()
+
+
+#{ Database utilities
+
+
+def create_db():
+    """Create the tables with sample records"""
+    meta.Session.remove()
+    setup_db(meta.engine)
+
+
+def destroy_db():
+    """Destroy the database created by :func:`create_db`."""
+    meta.metadata.drop_all(meta.engine)
+
+
+#}

File pylonssecuredapp/tests/functional/test_authn.py

+# -*- coding: utf-8 -*-
+"""
+Integration tests for the :mod:`repoze.who`-powered authentication sub-system.
+
+As the app grows and the authentication method changes, only these tests
+should be updated.
+
+"""
+
+from pylonssecuredapp.tests import TestController
+
+
+class TestAuthentication(TestController):
+    """
+    Tests for the default authentication setup.
+
+    By default in TurboGears 2, :mod:`repoze.who` is configured with the same
+    plugins specified by repoze.what-quickstart (which are listed in
+    http://code.gustavonarea.net/repoze.what-quickstart/#repoze.what.plugins.quickstart.setup_sql_auth).
+
+    As the settings for those plugins change, or the plugins are replaced,
+    these tests should be updated.
+
+    """
+
+    application_under_test = 'main'
+
+    def test_forced_login(self):
+        """
+        Anonymous users must be redirected to the login form when authorization
+        is denied.
+
+        Next, upon successful login they should be redirected to the initially
+        requested page.
+
+        """
+        # Requesting a protected area
+        resp = self.app.get('/cp/', status=302)
+        assert resp.location.startswith('http://localhost/login')
+        # Getting the login form:
+        resp = resp.follow(status=200)
+        form = resp.form
+        # Submitting the login form:
+        form['login'] = u'gustavo'
+        form['password'] = 'freedomware'
+        post_login = form.submit(status=302)
+        # Being redirected to the initially requested page:
+        assert post_login.location.startswith('http://localhost/post_login')
+        initial_page = post_login.follow(status=302)
+        assert 'authtkt' in initial_page.request.cookies, \
+               "Session cookie wasn't defined: %s" % initial_page.request.cookies
+        assert initial_page.location.startswith('http://localhost/cp/'), \
+               initial_page.location
+
+    def test_voluntary_login(self):
+        """Voluntary logins must work correctly"""
+        # Going to the login form voluntarily:
+        resp = self.app.get('/login', status=200)
+        form = resp.form
+        # Submitting the login form:
+        form['login'] = u'gustavo'
+        form['password'] = 'freedomware'
+        post_login = form.submit(status=302)
+        # Being redirected to the home page:
+        assert post_login.location.startswith('http://localhost/post_login')
+        home_page = post_login.follow(status=302)
+        assert 'authtkt' in home_page.request.cookies, \
+               'Session cookie was not defined: %s' % home_page.request.cookies
+        assert home_page.location == 'http://localhost/'
+
+    def test_logout(self):
+        """Logouts must work correctly"""
+        # Logging in voluntarily the quick way:
+        resp = self.app.get('/login_handler?login=gustavo&password=freedomware',
+                            status=302)
+        resp = resp.follow(status=302)
+        assert 'authtkt' in resp.request.cookies, \
+               'Session cookie was not defined: %s' % resp.request.cookies
+        # Logging out:
+        resp = self.app.get('/logout', status=302)
+        assert resp.location.startswith('http://localhost/post_logout')
+        # Finally, redirected to the home page:
+        home_page = resp.follow(status=302)
+        assert home_page.request.cookies.get('authtkt') == '', \
+               'Session cookie was not deleted: %s' % home_page.request.cookies
+        assert home_page.location == 'http://localhost/', home_page.location

File pylonssecuredapp/tests/functional/test_panel.py

+from pylons import url
+
 from pylonssecuredapp.tests import *
 
+
 class TestPanelController(TestController):
+    """
+    Sample functional test case on a protected area of the web site.
+    
+    """
 
-    def test_index(self):
-        response = self.app.get(url(controller='panel', action='index'))
-        # Test response...
+    def test_authz_granted_in_index(self):
+        """Authorization in the panel must be granted to the right subject"""
+        environ = {'REMOTE_USER': 'gustavo'}
+        resp = self.app.get(url(controller='panel', action='index'),
+                            extra_environ=environ, status=200)
+        assert "This controller is completely protected" in resp.body
+        
+    def test_authz_denied_in_index(self):
+        """Authorization in the panel must be denied to unauthorized subjects"""
+        # As an anonymous; it's enough to check that we got a 401:
+        self.app.get(url(controller='panel', action='index'), status=401)
+        
+        # As an authenticated (but still not allowed) user; it's enough to 
+        # check that we got a 403:
+        environ = {'REMOTE_USER': 'somebody'}
+        self.app.get(url(controller='panel', action='index'),
+                     extra_environ=environ, status=403)

File pylonssecuredapp/tests/functional/test_root.py

+from pylons import url
+
 from pylonssecuredapp.tests import *
 
 class TestRootController(TestController):
 port = 5000
 
 [app:main]
+sqlalchemy.url = sqlite:///:memory:
 use = config:development.ini
 
+[app:main_without_authn]
+skip_authentication = True
+use = main
+
 # Add additional test specific configuration options as necessary.