diff --git a/VERSION b/VERSION
index 7fd3f52c..c53c8981 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.1 (2012-08-29 16:05:38) rc4
+Version 2.00.1 (2012-08-29 17:40:00) rc4
diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py
new file mode 100644
index 00000000..3101e28a
--- /dev/null
+++ b/gluon/contrib/webclient.py
@@ -0,0 +1,92 @@
+"""
+Developed by Massimo Di Pierro
+Released under the web2py license (LGPL)
+
+It an interface on top of urllib2 that allows authentication and understand
+web2py cookies and web2py forms. An example of usage is at the bottom.
+"""
+
+import re
+import urllib
+import urllib2
+
+class WebClient(object):
+ regex = re.compile('\.+?)" \/\>\.+?)" \/\>')
+
+ def __init__(self,app=''):
+ self.app = app
+ self.cookies = {}
+
+ def get(self,url,cookies=None,headers=None,auth=None):
+ return self.post(url,data=None,cookies=cookies,headers=headers)
+
+ def post(self,url,data=None,cookies=None,headers=None,auth=None):
+ self.url = self.app+url
+ if cookies is None: cookies = self.cookies
+ if auth:
+ auth_handler = urllib2.HTTPBasicAuthHandler()
+ auth_handler.add_password(**auth)
+ opener = urllib2.build_opener(auth_handler)
+ else:
+ opener = urllib2.build_opener()
+ headers_list = []
+ for key,value in (headers or {}).iteritems():
+ if isinstance(value,(list,tuple)):
+ for v in value: headers_list.append((key,v))
+ else:
+ headers_list.append((key,value))
+ for key,value in (cookies or {}).iteritems():
+ headers_list.append(('Cookie','%s=%s' % (key,value)))
+ for key,value in headers_list:
+ opener.addheaders.append((key,str(value)))
+ if data is not None:
+ # if there is only one form, set _formname automatically
+ if not '_formname' in data and len(self.forms)==1:
+ data['_formname'] = self.forms.keys()[0]
+ # if there is no formkey but it is known, set it
+ if '_formname' in data and not '_formkey' in data and \
+ data['_formname'] in self.forms:
+ data['_formkey'] = self.forms[data['_formname']]
+ data = urllib.urlencode(data)
+ self.request = opener.open(self.url,data)
+ else:
+ self.request = opener.open(self.url)
+ self.status = self.request.getcode()
+ self.text = self.request.read()
+ self.headers = dict(self.request.headers)
+ self.cookies = dict(item[:item.find(';')].split('=') for item in \
+ self.headers.get('set-cookie','').split(','))
+ self.forms = {}
+ for match in WebClient.regex.finditer(self.text):
+ self.forms[match.group('formname')] = match.group('formkey')
+
+def test_web2py_registration_and_login():
+ session = WebClient('http://127.0.0.1:8000/welcome/default/')
+ session.get('user/register')
+ session_id_welcome = session.cookies['session_id_welcome']
+ data = dict(first_name = 'Homer',
+ last_name = 'Simpson',
+ email = 'homer@web2py.com',
+ password = 'test',
+ password_two = 'test',
+ _formname = 'register')
+ session.post('user/register',data = data)
+
+ session.get('user/login')
+ data = dict(email='homer@web2py.com',
+ password='test',
+ _formname = 'login')
+ session.post('user/login',data = data)
+
+ session.get('index')
+
+ # check registration and login were successful
+ assert 'Welcome Homer' in session.text
+
+ # check we are always in the same session
+ assert session_id_welcome == session.cookies['session_id_welcome']
+
+
+if __name__ == '__main__':
+ test_web2py_registration_and_login()
+
diff --git a/gluon/tests/test.sh b/gluon/tests/test.sh
index a32b17c3..1e25f800 100755
--- a/gluon/tests/test.sh
+++ b/gluon/tests/test.sh
@@ -28,8 +28,10 @@ else
elif [ "$1" = "doctest" ]; then
# this has to run in gluon's parent; needs work
#
- # the problem is that doctests run this way have a very different environment,
- # apparently due to imports that don't happen in the normal course of running
+ # the problem is that doctests run this way
+ # have a very different environment,
+ # apparently due to imports that don't happen
+ # in the normal course of running
# doctest via __main__.
#
echo doctest not supported >&2
diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py
new file mode 100644
index 00000000..94a2b31d
--- /dev/null
+++ b/gluon/tests/test_web.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ Unit tests for running web2py
+"""
+import sys
+import os
+if os.path.isdir('gluon'):
+ sys.path.append(os.path.realpath('gluon'))
+else:
+ sys.path.append(os.path.realpath('../'))
+
+import unittest
+from gluon.contrib.webclient import WebClient
+
+class TestWeb(unittest.TestCase):
+ def testWebClient(self):
+ session = WebClient('http://127.0.0.1:8000/welcome/default/')
+ session.get('user/register')
+ session_id_welcome = session.cookies['session_id_welcome']
+ data = dict(first_name = 'Homer',
+ last_name = 'Simpson',
+ email = 'homer@web2py.com',
+ password = 'test',
+ password_two = 'test',
+ _formname = 'register')
+ session.post('user/register',data = data)
+
+ session.get('user/login')
+ data = dict(email='homer@web2py.com',
+ password='test',
+ _formname = 'login')
+ session.post('user/login',data = data)
+
+ session.get('index')
+ # check registration and login were successful
+ self.assertTrue('Welcome Homer' in session.text)
+ # check we are always in the same session
+ self.assertEqual(session_id_welcome,
+ session.cookies['session_id_welcome'])
+ self.assertEqual('a','b')