Browse files

added tests for tgext.admin

  • Loading branch information...
1 parent 3936d9f commit da436633ec023517fa8bd112bec8e0890fd3a7ff percious17 committed Jan 18, 2009
View
19 license.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2009 Christopher Perkins
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
View
2 tgext/admin/controller.py
@@ -22,7 +22,7 @@
try:
import chameleon.genshi
import pylons.config
- if 'chameleon_genshi' in pylons.config['renderers']:
+ if hasattr(pylons.config, 'renderers') and 'chameleon_genshi' in pylons.config['renderers']:
engine = 'chameleon_genshi'
else:
import warnings
View
0 tgext/admin/test/__init__.py
No changes.
View
61 tgext/admin/test/base.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import os, shutil
+from unittest import TestCase
+from xmlrpclib import loads, dumps
+
+import beaker
+from paste.registry import RegistryManager
+from webtest import TestApp
+from paste import httpexceptions
+
+import tg
+from routes import Mapper
+from tg.controllers import TGController
+from pylons.testutil import ControllerWrap, SetupCacheGlobal
+
+from beaker.middleware import CacheMiddleware
+
+
+data_dir = os.path.dirname(os.path.abspath(__file__))
+session_dir = os.path.join(data_dir, 'session')
+
+def setup_session_dir():
+ if not os.path.exists(session_dir):
+ os.makedirs(session_dir)
+
+def teardown_session_dir():
+ shutil.rmtree(session_dir, ignore_errors=True)
+
+
+default_environ = {
+ 'pylons.use_webob' : True,
+ 'pylons.routes_dict': dict(action='index'),
+ 'paste.config': dict(global_conf=dict(debug=True))
+}
+
+default_map = Mapper()
+
+# Setup a default route for the error controller:
+default_map.connect('error/:action/:id', controller='error')
+# Setup a default route for the root of object dispatch
+default_map.connect('*url', controller='root', action='routes_placeholder')
+
+
+def make_app(controller_klass=None, environ=None):
+ """Creates a `TestApp` instance."""
+ if environ is None:
+ environ = {}
+ environ['pylons.routes_dict'] = {}
+ environ['pylons.routes_dict']['action'] = "routes_placeholder"
+
+ if controller_klass is None:
+ controller_klass = TGController
+
+ app = ControllerWrap(controller_klass)
+ app = SetupCacheGlobal(app, environ, setup_cache=True, setup_session=True)
+ app = RegistryManager(app)
+ app = beaker.middleware.SessionMiddleware(app, {}, data_dir=session_dir)
+ app = CacheMiddleware(app, {}, data_dir=os.path.join(data_dir, 'cache'))
+ app = httpexceptions.make_middleware(app)
+ return TestApp(app)
View
0 tgext/admin/test/controllers/__init__.py
No changes.
View
14 tgext/admin/test/controllers/root.py
@@ -0,0 +1,14 @@
+from tg.controllers import TGController
+from tgext.admin import AdminController
+from tgext.admin.test.model import DBSession
+import tgext.admin.test.model as model
+
+
+class UnSecuredAdminController(AdminController):
+ allow_only = None
+
+
+class RootController(TGController):
+ admin =UnSecuredAdminController(model, DBSession)
+
+
View
0 tgext/admin/test/lib/__init__.py
No changes.
View
0 tgext/admin/test/lib/base.py
No changes.
View
0 tgext/admin/test/lib/helpers.py
No changes.
View
229 tgext/admin/test/model/__init__.py
@@ -0,0 +1,229 @@
+#most of this file was taken from turbogears default template
+from hashlib import sha1
+import os
+import md5
+import sha
+from datetime import datetime
+
+#from sqlalchemy.types import *
+from sqlalchemy import *
+from sqlalchemy.orm import relation, backref, synonym
+from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base, synonym_for
+
+
+from zope.sqlalchemy import ZopeTransactionExtension
+from sqlalchemy.orm import scoped_session, sessionmaker
+
+# Global session manager. DBSession() returns the session object
+# appropriate for the current web request.
+maker = sessionmaker(autoflush=True, autocommit=False,
+ extension=ZopeTransactionExtension())
+DBSession = scoped_session(maker)
+
+DeclarativeBase = declarative_base()
+
+metadata = DeclarativeBase.metadata
+
+# This is the association table for the many-to-many relationship between
+# groups and permissions.
+group_permission_table = Table('tg_group_permission', metadata,
+ Column('group_id', Integer, ForeignKey('tg_group.group_id',
+ onupdate="CASCADE", ondelete="CASCADE")),
+ Column('permission_id', Integer, ForeignKey('tg_permission.permission_id',
+ onupdate="CASCADE", ondelete="CASCADE"))
+)
+
+# This is the association table for the many-to-many relationship between
+# groups and members - this is, the memberships.
+user_group_table = Table('tg_user_group', metadata,
+ Column('user_id', Integer, ForeignKey('tg_user.user_id',
+ onupdate="CASCADE", ondelete="CASCADE")),
+ Column('group_id', Integer, ForeignKey('tg_group.group_id',
+ onupdate="CASCADE", ondelete="CASCADE"))
+)
+
+# auth model
+
+class Group(DeclarativeBase):
+ """An ultra-simple group definition.
+ """
+ __tablename__ = 'tg_group'
+
+ group_id = Column(Integer, autoincrement=True, primary_key=True)
+ group_name = Column(Unicode(16), unique=True)
+ display_name = Column(Unicode(255))
+ created = Column(DateTime, default=datetime.now)
+ users = relation('User', secondary=user_group_table, backref='groups')
+
+ def __repr__(self):
+ return '<Group: name=%s>' % self.group_name
+
+class Town(DeclarativeBase):
+ __tablename__ = 'town'
+ town_id = Column(Integer, primary_key=True)
+ name = Column(String(32))
+
+class User(DeclarativeBase):
+ """Reasonably basic User definition. Probably would want additional
+ attributes.
+ """
+ __tablename__ = 'tg_user'
+
+ user_id = Column(Integer, autoincrement=True, primary_key=True)
+ user_name = Column(Unicode(16), unique=True)
+ email_address = Column(Unicode(255), unique=True)
+ display_name = Column(Unicode(255))
+ _password = Column('password', Unicode(40))
+ created = Column(DateTime, default=datetime.now)
+ town_id = Column(Integer, ForeignKey('town.town_id'))
+ town = relation(Town)
+
+ def __repr__(self):
+ return '<User: email="%s", display name="%s">' % (
+ self.email_address, self.display_name)
+
+ @property
+ def permissions(self):
+ perms = set()
+ for g in self.groups:
+ perms = perms | set(g.permissions)
+ return perms
+
+ @classmethod
+ def by_email_address(cls, email):
+ """A class method that can be used to search users
+ based on their email addresses since it is unique.
+ """
+ return DBSession.query(cls).filter(cls.email_address==email).first()
+
+ @classmethod
+ def by_user_name(cls, username):
+ """A class method that permits to search users
+ based on their user_name attribute.
+ """
+ return DBSession.query(cls).filter(cls.user_name==username).first()
+
+
+ def _set_password(self, password):
+ """encrypts password on the fly using the encryption
+ algo defined in the configuration
+ """
+ #unfortunately, this causes coverage not to work
+ #self._password = self._encrypt_password(algorithm, password)
+
+ def _get_password(self):
+ """returns password
+ """
+ return self._password
+
+ password = synonym('password', descriptor=property(_get_password,
+ _set_password))
+
+ def _encrypt_password(self, algorithm, password):
+ """Hash the given password with the specified algorithm. Valid values
+ for algorithm are 'md5' and 'sha1'. All other algorithm values will
+ be essentially a no-op."""
+ hashed_password = password
+
+ if isinstance(password, unicode):
+ password_8bit = password.encode('UTF-8')
+ else:
+ password_8bit = password
+
+ #creates a salted sha password
+ salt = sha1()
+ salt.update(os.urandom(60))
+ hash = sha1()
+ hash.update(password_8bit + salt.hexdigest())
+ hashed_password = salt.hexdigest() + hash.hexdigest()
+
+ # make sure the hased password is an UTF-8 object at the end of the
+ # process because SQLAlchemy _wants_ a unicode object for Unicode columns
+ if not isinstance(hashed_password, unicode):
+ hashed_password = hashed_password.decode('UTF-8')
+
+ return hashed_password
+
+ def validate_password(self, password):
+ """Check the password against existing credentials.
+ this method _MUST_ return a boolean.
+
+ @param password: the password that was provided by the user to
+ try and authenticate. This is the clear text version that we will
+ need to match against the (possibly) encrypted one in the database.
+ @type password: unicode object
+ """
+ hashed_pass = sha1()
+ hashed_pass.update(password + self.password[:40])
+
+ return self.password[40:] == hashed_pass.hexdigest()
+
+
+class Permission(DeclarativeBase):
+ """A relationship that determines what each Group can do
+ """
+ __tablename__ = 'tg_permission'
+
+ permission_id = Column(Integer, autoincrement=True, primary_key=True)
+ permission_name = Column(Unicode(16), unique=True)
+ description = Column(Unicode(255))
+ groups = relation(Group, secondary=group_permission_table,
+ backref='permissions')
+
+class Example(DeclarativeBase):
+ __tablename__ = 'example'
+
+ example_id = Column(Integer, primary_key=True)
+ created = Column(DateTime, default=datetime.now)
+ blob = Column(BLOB )
+ binary = Column(Binary )
+ boolean = Column(Boolean )
+ char = Column(CHAR(200) )
+ cLOB = Column(CLOB(200) )
+ date_ = Column( DATE )
+ datetime_ = Column( DATETIME )
+ decimal = Column(DECIMAL )
+ date = Column(Date )
+ dateTime = Column(DateTime )
+ float__ = Column( FLOAT )
+ float_ = Column(Float )
+ int_ = Column(INT )
+ integer = Column(Integer, default=10)
+ # (NCHAR = #Column NCHAR )
+ numeric = Column(Numeric )
+ pickletype = Column(PickleType )
+ smallint = Column(SMALLINT )
+ smalliunteger = Column(SmallInteger )
+ string = Column(String(20) )
+ text = Column(TEXT(20) )
+ time_ = Column(TIME )
+ time = Column(Time )
+ timestamp = Column(TIMESTAMP )
+ unicode_ = Column(Unicode(200) )
+ varchar = Column(VARCHAR(200) )
+ password = Column(String(20) )
+
+class Document(DeclarativeBase):
+
+ __tablename__ = 'document'
+ document_id = Column(Integer, primary_key=True)
+ created = Column(DateTime, default=datetime.now)
+ blob = Column(BLOB )
+ owner = Column(Integer, ForeignKey('tg_user.user_id'))
+ url = Column(String(500))
+
+ def _get_address(self):
+ return self.url
+
+ def _set_address(self, value):
+ self.url = value
+
+ address = synonym('address', descriptor=property(_get_address,
+ _set_address))
+
+#model_initialized = False
+
+def init_model(engine):
+ global model_initialized
+
+ DBSession.configure(bind=engine)
View
0 tgext/admin/test/templates/__init__.py
No changes.
View
39 tgext/admin/test/templates/master.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ py:strip="">
+<head py:match="head" py:attrs="select('@*')">
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title py:replace="''">Your title goes here</title>
+ <meta py:replace="select('*')"/>
+ <link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/css/style.css')}" />
+</head>
+
+<body py:match="body" py:attrs="select('@*')">
+ <ul id="mainmenu">
+ <li class="first"><a href="${tg.url('/')}" class="${('', 'active')[defined('page') and page==page=='index']}">Welcome</a></li>
+ <li><a href="${tg.url('/about')}" class="${('', 'active')[defined('page') and page==page=='about']}">About</a></li>
+ <li py:if="tg.auth_stack_enabled"><a href="${tg.url('/auth')}" class="${('', 'active')[defined('page') and page==page=='auth']}">Authentication</a></li>
+ <li><a href="http://groups.google.com/group/turbogears">Contact</a></li>
+ <span py:if="tg.auth_stack_enabled" py:strip="True">
+ <li py:if="not request.identity" id="login" class="loginlogout"><a href="${tg.url('/login')}">Login</a></li>
+ <li py:if="request.identity" id="login" class="loginlogout"><a href="${tg.url('/logout_handler')}">Logout</a></li>
+ <li py:if="request.identity" id="admin" class="loginlogout"><a href="${tg.url('/admin')}">Admin</a></li>
+ </span>
+ </ul>
+ <div id="content">
+ <py:if test="defined('page')">
+ <div class="currentpage">
+ Now Viewing: <span py:replace="page"/>
+ </div>
+ </py:if>
+ <div id="${tg.flash_status}" py:if="tg.flash" class="flash"
+ py:content="tg.flash">
+ </div>
+ <div py:replace="select('*|text()')"/>
+ <!-- End of main_content -->
+ </div>
+</body>
+</html>
View
202 tgext/admin/test/test_controller.py
@@ -0,0 +1,202 @@
+import os, sys
+import tgext.admin
+from tg.test_stack import TestConfig, app_from_config
+from tgext.admin.test.model import User, Group, Town
+from tg.util import Bunch
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+from tgext.admin.test.model import metadata, DBSession
+
+root = os.path.abspath(os.path.dirname(__file__))
+sys.path.insert(0, root)
+test_db_path = 'sqlite:///'+root+'/test.db'
+paths=Bunch(
+ root=root,
+ controllers=os.path.join(root, 'controllers'),
+ static_files=os.path.join(root, 'public'),
+ templates=os.path.join(root, 'templates')
+ )
+
+base_config = TestConfig(folder = 'rendering',
+ values = {'use_sqlalchemy': True,
+ 'model':tgext.admin.test.model,
+ 'session':tgext.admin.test.model.DBSession,
+ 'pylons.helpers': Bunch(),
+ 'use_legacy_renderer': False,
+ # this is specific to mako
+ # to make sure inheritance works
+ 'use_dotted_templatenames': True,
+ 'paths':paths,
+ 'package':tgext.admin.test,
+ 'sqlalchemy.url':test_db_path
+ }
+ )
+
+def setup_records(session):
+
+
+ #session.expunge_all()
+
+ user = User()
+ user.user_name = u'asdf'
+ user.email_address = u"asdf@asdf.com"
+ user.password = u"asdf"
+ session.add(user)
+
+ arvada = Town(name=u'Arvada')
+ session.add(arvada)
+ session.flush()
+ user.town = arvada
+
+ session.add(Town(name=u'Denver'))
+ session.add(Town(name=u'Golden'))
+ session.add(Town(name=u'Boulder'))
+
+ #test_table.insert(values=dict(BLOB=FieldStorage('asdf', StringIO()).value)).execute()
+ #user_reference_table.insert(values=dict(user_id=user.user_id)).execute()
+
+# print user.user_id
+ for i in ['managers', 'users']:
+ group = Group(group_name=unicode(i))
+ session.add(group)
+
+ user.groups.append(group)
+
+ session.flush()
+ return user
+
+def setup():
+ engine = create_engine(test_db_path)
+ metadata.bind = engine
+ metadata.drop_all()
+ metadata.create_all()
+ session = sessionmaker(bind=engine)()
+ setup_records(session)
+ session.commit()
+
+def teardown():
+ os.remove(test_db_path[10:])
+
+class TestAdminController:
+ def __init__(self, *args, **kargs):
+ self.app = app_from_config(base_config)
+
+ def test_index(self):
+ resp = self.app.get('/admin/')
+ assert 'Document' in resp, resp
+
+ def test_list_documents(self):
+ resp = self.app.get('/admin/document').follow()
+ assert """<table dojoType="dojox.grid.DataGrid" id="" store="_store" columnReordering="true" rowsPerPage="20" delayScroll="true" class="">
+ <thead>
+ <tr>
+ <th field="__actions__">actions</th>
+ <th field="document_id" width="auto">document_id
+ </th><th field="created" width="auto">created
+ </th><th field="blob" width="auto">blob
+ </th><th field="owner" width="auto">owner
+ </th><th field="url" width="auto">url
+ </th><th field="address" width="auto">address
+ </th>
+ </tr>
+ </thead>
+ <div dojoType="dojox.data.QueryReadStore" jsId="_store" id="_store" url=".json"></div>
+</table>
+""" in resp, resp
+
+ def test_documents_new(self):
+ resp = self.app.get('/admin/document/new')
+ assert """<tr id="blob.container" class="even" title="">
+ <td class="labelcol">
+ <label id="blob.label" for="blob" class="fieldlabel">Blob</label>
+ </td>
+ <td class="fieldcol">
+ <input type="file" name="blob" class="filefield" id="blob" value="" />
+ </td>
+ </tr>""" in resp, resp
+
+ def test_get_users(self):
+ resp = self.app.get('/admin/user/')
+ assert """<table dojoType="dojox.grid.DataGrid" id="" store="_store" columnReordering="true" rowsPerPage="20" delayScroll="true" class="">
+ <thead>
+ <tr>
+ <th field="__actions__">actions</th>
+ <th field="user_name" width="auto">user_name
+ </th><th field="email_address" width="auto">email_address
+ </th><th field="display_name" width="auto">display_name
+""" in resp, resp
+
+ def test_get_users_json(self):
+ resp = self.app.get('/admin/user.json')
+ assert """{"numRows": 1, "items": [{"town": "Arvada", "user_id": "1", "created":""" in resp, resp
+
+ def test_edit_user(self):
+ resp = self.app.get('/admin/user/1/edit')
+ assert """<td class="fieldcol">
+ <input type="text" name="user_name" class="textfield required" id="user_name" value="asdf" />
+ </td>
+ </tr><tr id="email_address.container" class="odd" title="">
+ <td class="labelcol">
+ <label id="email_address.label" for="email_address" class="fieldlabel required">Email Address</label>
+ </td>
+ <td class="fieldcol">
+ <input type="text" name="email_address" class="textfield required" id="email_address" value="asdf@asdf.com" />
+ </td>""" in resp, resp
+
+ def test_edit_user_success(self):
+ resp = self.app.post('/admin/user/1/', params={'sprox_id':'put__User',
+ '_method':'PUT',
+ 'user_name':'someone',
+ 'display_name':'someone2',
+ 'email_address':'asdf2@asdf2.com',
+ '_password':'pass',
+ 'password':'pass',
+ 'town':'1',
+ 'town_id':'1',
+ 'user_id':'1',
+ 'created':'2009-01-11 13:54:01'}).follow()
+ #resp = self.app.get('/admin/user.json')
+ #assert """"email_address": "asdf2@asdf2.com",""" in resp, resp
+
+ resp = self.app.post('/admin/user/1/', params={'sprox_id':'put__User',
+ '_method':'PUT',
+ 'user_name':'someone',
+ 'display_name':'someone2',
+ 'email_address':'asdf@asdf.com',
+ '_password':'pass',
+ 'password':'pass',
+ 'town':'1',
+ 'town_id':'1',
+ 'user_id':'1',
+ 'created':'2009-01-11 13:54:01'}).follow()
+# resp = self.app.get('/admin/user.json')
+# assert """"email_address": "asdf@asdf.com",""" in resp, resp
+
+ def _test_add_and_remove_user(self):
+ resp = self.app.post('/admin/user/', params={'sprox_id':'add__User',
+ 'user_name':'someone',
+ 'display_name':'someone2',
+ 'email_address':'asdf2@asdf2.com',
+ '_password':'pass',
+ 'password':'pass',
+ 'town':'1',
+ 'town_id':'1',
+ 'user_id':'2',
+ 'created':'2009-01-11 13:54:01'}).follow()
+ #assert '<td>asdf2@asdf2' in resp, resp
+ resp = self.app.get('/admin/user/2/', params={'user_id':'2', '_method':'DELETE'}).follow()
+ #assert 'asdf2@asdf2' not in resp, resp
+
+ def test_add_user_existing_username(self):
+ resp = self.app.post('/admin/user/create', params={'sprox_id':'add__User',
+ 'user_name':u'asdf',
+ 'display_name':'someone2',
+ 'email_address':'asdf2@asdf2.com',
+ '_password':'pass',
+ 'password':'pass',
+ 'town':'1',
+ 'town_id':'1',
+ 'user_id':'2',
+ 'created':'2009-01-11 13:54:01'})
+ assert 'That value already exists' in resp, resp

0 comments on commit da43663

Please sign in to comment.