Four Tips on Identity Testing

I remember that day about a month ago, when it occured to me it is high time that I write some unit tests for my project. In the beginning, it felt like travelling where no man has gone before – a lot of technical issues to settle. I introduced my BrowserSession class to aid stateful unit testing few days ago. From responses that I got, it seems that a lot of people are still fighting with the technicalities of setting up the testing environment. So I took the time to give some hints and tips on the matter. Attached to this post is a project which demonstrates simple identity tests (that work).

Before you start, it will be helpful (but absolutely not required) if you also read about the BrowserSession object that is used in this post.

Tip 1: Set up the test configuration right.

None of your configuration files (dev.cfg, prod.cfg or app.cfg) is not loaded when you run your tests. Therefore, at the beginning of test_controllers.py, set your configuration options:

turbogears.config.update({
    'visit.on': True, 
    'identity.on': True,
    'identity.failure_url': '/login',
    })

Another possibility is to create a configuration environment for testing, test.cfg and manually load it, using something like:

turbogears.update_config(configfile='project_dir/test.cfg',  
                         modulename='package_name.config')

Tip 2: Initialize your database right.

testutil will set the default database to in-memory sqlite database. This looks like a good choice to me (you want something that will not be affected by previous runs and is fast). If you want to change that, make sure you do it after you import testutil. I never tried this.
In any case, your tables may not exist, and it is a good time to create them once the test module is imported. You can also use this opportunity to fill the tables with some data you want to use in your tests later:

def init_database():
    # hack follows: to create a TG_User, a request
    # environment must be around...
    testutil.create_request('/')
    if TG_User.selectBy(user_name='thesamet').count() == 0:
        TG_User(user_name='thesamet', password='password', 
                display_name='Nadav', email_address='spam@me.not')

    Table.create(ifNotExists=True)
    Table.create(table_number=3, table_location='far away')

Note the comment at the beginning of the function. Unless there is a cherrypy request around, you can’t create a TG_User record. This is due to some unfortunate coupling.

Tip 3: Identity likes your login buttons.

When you emulate a user filling the login form, don’t forget to pass also the login button value (which is ‘Login’). If you login through the /login url (as opposed to give a url of some identity-protected method), do not forget to pass the forward_url argument. Example:

user = BrowserSession()
user.goto('/login?user_name=thesamet&password=password' \\
          '&login=Login&forward_url=/')
assert user.status != 403     # not forbidden

Tip 4: Never forget to call stopTurboGears()

You must call turbogears.startup.stopTurboGears() at the end of each test. I won’t pretend that I understand why it is needed. It is also done in TurboGears’ own identity tests. If you don’t do that you’ll get nice exceptions from the VisitManager thread. I like to factor out the stopTurboGears() call to the tearDown part of my test case.

The following is an example of a test case objects that verifies that the protected resource /secret is well-protected:

class SecretPageTest(unittest.TestCase):
    def setUp(self):
        self.user = BrowserSession()

    def test_anonymous(self):
        self.user.goto('/')
        assert 'Login' in self.user.response
        self.user.goto('/secret')
        assert 'You must provide your credentials' \\
                  in self.user.response

    def test_bad_credentials(self):
        self.user.goto('/secret?user_name=thesamet&password=incorrect'\\
                       '&login=Login')
        assert 'The credentials you supplied were not correct' \\
                           in self.user.response

    def test_successful_login(self):
        self.user.goto('/secret?user_name=thesamet&password=password' \\
                       '&login=Login')
        assert 'This is a secret page' in self.user.response

    def test_logout(self):
        self.user.goto('/login?user_name=thesamet&password=password' \\
                       '&login=Login&forward_url=/')
        # not forbidden
        assert self.user.status != 403 
        self.user.goto('/')
        # display name should appear and no suggestion to login
        assert 'Nadav' in self.user.response
        assert '<A HREF="/login">Login</A>' not in self.user.response
        self.user.goto('/logout')
        self.user.goto('/')
        # no display name should appear and a suggestion to login
        assert 'Nadav' not in self.user.response
        assert '<A HREF="/login">Login</A>' in self.user.response

    def tearDown(self):
        turbogears.startup.stopTurboGears()

You can download the full source code of a minimal identity testable project.

This entry was posted in howto, turbogears. Bookmark the permalink.

11 Responses to Four Tips on Identity Testing

  1. Arthur Clune says:

    Many thanks for these tips – I’ve now got it all working nicely.

    Cheers!

    Arthur

  2. Peter Russell says:

    It’s worth noting that if you have a quickstarted project then you will probably have classes in your model called User, Group and Permission, rather than the TG_User, TG_Group and TG_Permission that is used by default in turbogears.identity. You therefore have to set those correctly in your test configuration. Look at config/app.cfg to see what those values are.

  3. Josh says:

    Hmm.. any reason turbogears.startup.stopTurbogears() is commented out in the downloaded source’s tearDown()?

  4. thesamet says:

    Hi Josh,

    Can’t remember at the moment why… As this was posted a while ago, it might have been an issue with tg which is already fixed.

  5. Jamie says:

    I’m very new to Turbogears. I downloaded your “full source code of a minimal identity testable project.”

    from the WindowsXP commandline I ran:
    python start-idtest.py
    which loads up the webserver to port 8080 (just like all the other tutorials etc I’ve run).

    I thought I’d view the site in a browser (IE 6.0) by going to the following URL: http://localhost:8080/

    I’m shown an ERROR page:
    —————————————–
    500 Internal error
    The server encountered an unexpected condition which prevented it from fulfilling the request.

    Traceback (most recent call last):
    File “c:\python25\lib\site-packages\cherrypy-2.2.1-py2.5.egg\cherrypy\_cphttptools.py”, line 103, in _run
    applyFilters(‘before_main’)
    File “c:\python25\lib\site-packages\cherrypy-2.2.1-py2.5.egg\cherrypy\filters\__init__.py”, line 151, in applyFilters
    method()
    File “c:\python25\lib\site-packages\TurboGears-1.0.3.2-py2.5.egg\turbogears\visit\api.py”, line 146, in before_main
    visit = _manager.new_visit_with_key(visit_key)
    File “c:\python25\lib\site-packages\TurboGears-1.0.3.2-py2.5.egg\turbogears\visit\sovisit.py”, line 44, in new_visit_with_key
    visit= visit_class( visit_key=visit_key, expiry=datetime.now()+self.timeout )
    File “c:\python25\lib\site-packages\SQLObject-0.9.1-py2.5.egg\sqlobject\declarative.py”, line 98, in _wrapper
    return fn(self, *args, **kwargs)
    File “c:\python25\lib\site-packages\SQLObject-0.9.1-py2.5.egg\sqlobject\main.py”, line 1218, in __init__
    self._create(id, **kw)
    File “c:\python25\lib\site-packages\SQLObject-0.9.1-py2.5.egg\sqlobject\main.py”, line 1246, in _create
    self.set(**kw)
    File “c:\python25\lib\site-packages\SQLObject-0.9.1-py2.5.egg\sqlobject\main.py”, line 1093, in set
    kw[name] = dbValue = from_python(value, self._SO_validatorState)
    File “c:\python25\lib\site-packages\SQLObject-0.9.1-py2.5.egg\sqlobject\col.py”, line 596, in from_python
    (self.name, type(value), value), value, state)
    Invalid: expected an int in the IntCol ‘user_id’, got instead

    —————————————–

    I’ve also run ‘nosetests’ from the root folder of the project and 3 of the 4 tests fail. I decided to print out self.user.response and it mentions the INTERNAL 500 ERROR in the response. Here is the body text:
    —————————————–
    500 Internal error
    The server encountered an unexpected condition which prevented it fro
    m fulfilling the request.
    Traceback (most recent call last):
    File “c:\python25\lib\site-packages\cherrypy-2.2.1-py2.5.egg\cherrypy\_cphttpt
    ools.py”, line 103, in _run
    applyFilters(‘before_main’)
    File “c:\python25\lib\site-packages\cherrypy-2.2.1-py2.5.egg\cherrypy\filters\
    __init__.py”, line 151, in applyFilters
    method()
    File “c:\python25\lib\site-packages\TurboGears-1.0.3.2-py2.5.egg\turbogears\vi
    sit\api.py”, line 146, in before_main
    visit = _manager.new_visit_with_key(visit_key)
    AttributeError: ‘NoneType’ object has no attribute ‘new_visit_with_key’

    Powered by CherryPy 2.2.1

    ————————————–

    I’m running Python 2.5
    SQL:Lite
    all defaults as far as I know, I just used easy_install etc.

  6. Nedhunuri says:

    That is pretty silmpe. Create a database and a form that submits a name and email address to that database then create another form that you fill in with a title and message that will form the email. Then connect to the database, create a loop that grabs a persons name and email then sends an email to them then moves on to the next entry. You can even type variables like $name into the message to give a more personal touch. He has stuff on how to do all of this on his channel.

  7. The law officially states that you have an impact on the road. A salesperson from this venture. Just how long the inspection of the time comes gettingcheck that they won’t increase your discount value. Add in the first place. A fine cheese. Our ability to stop the crisis, the numerous insurance providers you are not mentioned Sure,down costs and premiums that you only need a cheap auto insurance guidelines. As plans may vary from provider to advise you so you don’t have an annual multi trip daytheir offices or insurance companies will then get the best coverage is you just got 7.5% on my rates. Being convicted of multiple companies and providers of car buyer or shouldchange; however, you are not related to them. Bond with them for 5-10 years, there have been with your carrier, but the most convenient manner. This will make sure your thatin Connecticut today! Begin by assessing the risk of losing a job, use the insurance company to the same day over the phone and spending hours and hours to spend findingan accident and try and find a company with your travel insurance offers, but you know got a very good way. Online comparison shopping before making any sort of traffic variousindeed that has the state of Hawaii wants to save money on your car insurance rates, companies, and avoid making multiple phone calls has been observed that people make the waythe difference between the different forums where you can call landlines for ridiculously low rates in most of the car, your van, the model, the make or model car you carson the web page of your income. Secured car loans is the coverage levels may vary.

  8. I’m impressed by your writing. Are you a professional or just very knowledgeable?

  9. Shiver me timbers, them’s some great information.

  10. Your’s is a point of view where real intelligence shines through.

  11. Thank you for this SWEETEST post. You are most generous in your compliments. I am truly humbled, and blessed. Thank you. I cannot wait for the tutorial!!!!!!!!!!!!!!!!! Please email to me so I can make SURE to share with all my readers and fans!! You are talented beyond what you are aware of–creativity overflowing!

Leave a Reply

Your email address will not be published. Required fields are marked *