Skip to content

Commit

Permalink
Added wsgi_intercept support (came from zope.app.wsgi.testlayer).
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Howitz committed Jan 24, 2011
1 parent 5e18078 commit ba1ef1e
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 3 deletions.
10 changes: 8 additions & 2 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@
CHANGES
=======

3.11.0 (unreleased)
-------------------

- Added `wsgi_intercept` support (came from ``zope.app.wsgi.testlayer``).


3.10.3 (2010-10-15)
-------------------

- Fixed backwards compatibility with zope.app.wsgi.testlayer.
- Fixed backwards compatibility with ``zope.app.wsgi.testlayer``.


3.10.2 (2010-10-15)
-------------------

- Fixed Python2.7 compatibility in Browser.handleErrors.
- Fixed Python 2.7 compatibility in Browser.handleErrors.


3.10.1 (2010-09-21)
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@
'zope-functional-testing': [
'zope.app.testing',
],
'wsgi': [
'wsgi_intercept',
]
},
include_package_data = True,
zip_safe = False,
Expand Down
44 changes: 43 additions & 1 deletion src/zope/testbrowser/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Detailed Documentation
======================

Different Browsers
------------------

HTTP Browser
~~~~~~~~~~~~

The ``zope.testbrowser.browser`` module exposes a ``Browser`` class that
simulates a web browser similar to Mozilla Firefox or IE.

Expand All @@ -11,14 +17,50 @@ simulates a web browser similar to Mozilla Firefox or IE.
This version of the browser object can be used to access any web site just as
you would do using a normal web browser.

WSGI Test Browser
~~~~~~~~~~~~~~~~~

There is also a special version of the ``Browser`` class which uses
`wsgi_intercept`_ and can be used to do functional testing of WSGI
applications, it can be imported from ``zope.testbrowser.wsgi``:

>>> from zope.testbrowser.wsgi import Browser
>>> browser = Browser()

.. _`wsgi_intercept`: http://pypi.python.org/pypi/wsgi_intercept

To use this browser you have to:

* use the `wsgi` extra of the ``zope.testbrowser`` egg,

* write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the
``make_wsgi_app`` method,

* use an instance of the class as the test layer of your test.

Example:

>>> class SimpleLayer(zope.testbrowser.wsgi.Layer):
... def make_wsgi_app(self):
... return simple_app

Where ``simple_app`` is the callable of your WSGI application.

Zope 3 Test Browser
~~~~~~~~~~~~~~~~~~~

There is also a special version of the ``Browser`` class used to do functional
testing of Zope 3 applications, it can be imported from
``zope.testbrowser.testing``:

>>> from zope.testbrowser.testing import Browser
>>> browser = Browser()

An initial page to load can be passed to the ``Browser`` constructor:
Bowser Usage
------------

All browsers are used the same way. An initial page to load can be passed
to the ``Browser`` constructor:

>>> browser = Browser('http://localhost/@@/testbrowser/simple.html')
>>> browser.url
Expand Down
44 changes: 44 additions & 0 deletions src/zope/testbrowser/tests/test_wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
##############################################################################
#
# Copyright (c) 2011 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################

import unittest
import zope.testbrowser.wsgi


# Copied from PEP #333
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']


class SimpleLayer(zope.testbrowser.wsgi.Layer):

def make_wsgi_app(self):
return simple_app

SIMPLE_LAYER = SimpleLayer()


class TestWSGI(unittest.TestCase):

layer = SIMPLE_LAYER

def test_(self):
browser = zope.testbrowser.wsgi.Browser()
browser.open('http://localhost')
self.assertEqual('Hello world!\n', browser.contents)
# XXX test for authorization header munging is missing
129 changes: 129 additions & 0 deletions src/zope/testbrowser/wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
##############################################################################
#
# Copyright (c) 2010-2011 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
import base64
import re
import wsgi_intercept
import wsgi_intercept.mechanize_intercept
import zope.testbrowser.browser


# List of hostname where the test browser/http function replies to
TEST_HOSTS = ['localhost', '127.0.0.1']


class InterceptBrowser(wsgi_intercept.mechanize_intercept.Browser):

default_schemes = ['http']
default_others = ['_http_error',
'_http_default_error']
default_features = ['_redirect', '_cookies', '_referer', '_refresh',
'_equiv', '_basicauth', '_digestauth']


class Browser(zope.testbrowser.browser.Browser):
"""Override the zope.testbrowser.browser.Browser interface so that it
uses InterceptBrowser.
"""

def __init__(self, *args, **kw):
kw['mech_browser'] = InterceptBrowser()
super(Browser, self).__init__(*args, **kw)


# Compatibility helpers to behave like zope.app.testing

basicre = re.compile('Basic (.+)?:(.+)?$')


def auth_header(header):
"""This function takes an authorization HTTP header and encode the
couple user, password into base 64 like the HTTP protocol wants
it.
"""
match = basicre.match(header)
if match:
u, p = match.group(1, 2)
if u is None:
u = ''
if p is None:
p = ''
auth = base64.encodestring('%s:%s' % (u, p))
return 'Basic %s' % auth[:-1]
return header


def is_wanted_header(header):
"""Return True if the given HTTP header key is wanted.
"""
key, value = header
return key.lower() not in ('x-content-type-warning', 'x-powered-by')


class AuthorizationMiddleware(object):
"""This middleware makes the WSGI application compatible with the
HTTPCaller behavior defined in zope.app.testing.functional:
- It modifies the HTTP Authorization header to encode user and
password into base64 if it is Basic authentication.
"""

def __init__(self, wsgi_stack):
self.wsgi_stack = wsgi_stack

def __call__(self, environ, start_response):
# Handle authorization
auth_key = 'HTTP_AUTHORIZATION'
if auth_key in environ:
environ[auth_key] = auth_header(environ[auth_key])

# Remove unwanted headers
def application_start_response(status, headers, exc_info=None):
headers = filter(is_wanted_header, headers)
start_response(status, headers)

for entry in self.wsgi_stack(environ, application_start_response):
yield entry


class Layer(object):
"""Test layer which sets up WSGI application for use with
wsgi_intercept/testbrowser.
"""

__bases__ = ()
__name__ = 'Layer'

def make_wsgi_app(self):
# Override this method in subclasses of this layer in order to set up
# the WSGI application.
raise NotImplementedError

def cooperative_super(self, method_name):
# Calling `super` for multiple inheritance:
method = getattr(super(Layer, self), method_name, None)
if method is not None:
method()

def setUp(self):
self.cooperative_super('setUp')
self.app = self.make_wsgi_app()
factory = lambda: AuthorizationMiddleware(self.app)

for host in TEST_HOSTS:
wsgi_intercept.add_wsgi_intercept(host, 80, factory)

def tearDown(self):
for host in TEST_HOSTS:
wsgi_intercept.remove_wsgi_intercept(host, 80)
self.cooperative_super('tearDown')

0 comments on commit ba1ef1e

Please sign in to comment.