[Checkins] SVN: zope.testbrowser/trunk/ merged changes here from tags/3.10.3 which were accidently checked in there

Michael Howitz mh at gocept.com
Mon Jan 24 10:17:15 EST 2011


Log message for revision 119887:
  merged changes here from tags/3.10.3 which were accidently checked in there
  

Changed:
  U   zope.testbrowser/trunk/CHANGES.txt
  U   zope.testbrowser/trunk/buildout.cfg
  U   zope.testbrowser/trunk/setup.py
  U   zope.testbrowser/trunk/src/zope/testbrowser/README.txt
  A   zope.testbrowser/trunk/src/zope/testbrowser/tests/
  U   zope.testbrowser/trunk/src/zope/testbrowser/tests/test_doctests.py
  D   zope.testbrowser/trunk/src/zope/testbrowser/tests.py
  A   zope.testbrowser/trunk/src/zope/testbrowser/wsgi.py

-=-
Modified: zope.testbrowser/trunk/CHANGES.txt
===================================================================
--- zope.testbrowser/trunk/CHANGES.txt	2011-01-24 15:03:58 UTC (rev 119886)
+++ zope.testbrowser/trunk/CHANGES.txt	2011-01-24 15:17:15 UTC (rev 119887)
@@ -2,10 +2,10 @@
 CHANGES
 =======
 
-3.10.5 (unreleased)
+3.11.0 (2011-01-24)
 -------------------
 
-- Nothing changed yet.
+- Added `wsgi_intercept` support (came from ``zope.app.wsgi.testlayer``).
 
 
 3.10.4 (2011-01-14)
@@ -19,13 +19,13 @@
 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)

Modified: zope.testbrowser/trunk/buildout.cfg
===================================================================
--- zope.testbrowser/trunk/buildout.cfg	2011-01-24 15:03:58 UTC (rev 119886)
+++ zope.testbrowser/trunk/buildout.cfg	2011-01-24 15:17:15 UTC (rev 119887)
@@ -5,9 +5,10 @@
 [test]
 recipe = zc.recipe.testrunner
 defaults = ['--tests-pattern', '^f?tests$']
-eggs = zope.testbrowser [test]
+eggs = zope.testbrowser [test, wsgi]
 
 [interpreter]
 recipe = zc.recipe.egg
 eggs = zope.testbrowser
 interpreter = py
+

Modified: zope.testbrowser/trunk/setup.py
===================================================================
--- zope.testbrowser/trunk/setup.py	2011-01-24 15:03:58 UTC (rev 119886)
+++ zope.testbrowser/trunk/setup.py	2011-01-24 15:17:15 UTC (rev 119887)
@@ -27,7 +27,7 @@
 
 setup(
     name = 'zope.testbrowser',
-    version='3.10.5dev',
+    version='3.11.1dev',
     url = 'http://pypi.python.org/pypi/zope.testbrowser',
     license = 'ZPL 2.1',
     description = 'Programmable browser for functional black-box tests',
@@ -74,6 +74,9 @@
         'zope-functional-testing': [
             'zope.app.testing',
             ],
+        'wsgi': [
+            'wsgi_intercept',
+            ]
         },
     include_package_data = True,
     zip_safe = False,

Modified: zope.testbrowser/trunk/src/zope/testbrowser/README.txt
===================================================================
--- zope.testbrowser/trunk/src/zope/testbrowser/README.txt	2011-01-24 15:03:58 UTC (rev 119886)
+++ zope.testbrowser/trunk/src/zope/testbrowser/README.txt	2011-01-24 15:17:15 UTC (rev 119887)
@@ -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.
 
@@ -11,6 +17,39 @@
 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:
+
+    >>> import zope.testbrowser.wsgi
+    >>> 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``:
@@ -18,8 +57,12 @@
     >>> 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
     'http://localhost/@@/testbrowser/simple.html'

Modified: zope.testbrowser/trunk/src/zope/testbrowser/tests/test_doctests.py
===================================================================
--- zope.testbrowser/tags/3.10.3/src/zope/testbrowser/tests/test_doctests.py	2011-01-24 14:58:08 UTC (rev 119884)
+++ zope.testbrowser/trunk/src/zope/testbrowser/tests/test_doctests.py	2011-01-24 15:17:15 UTC (rev 119887)
@@ -23,19 +23,19 @@
     __name__, 'TestBrowserLayer', allow_teardown=True)
 
 
-def FileSuite(*filenames):
-    kw = dict(optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS,
-              checker=zope.testbrowser.tests.helper.checker,
-              package='zope.testbrowser')
+def test_suite():
+    flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
     suite = zope.app.testing.functional.FunctionalDocFileSuite(
-        *filenames, **kw)
+        'README.txt',
+        'cookies.txt',
+        'fixed-bugs.txt',
+        optionflags=flags,
+        checker=zope.testbrowser.tests.helper.checker,
+        package='zope.testbrowser')
     suite.layer = TestBrowserLayer
-    return suite
 
-
-def test_suite():
-    suite = FileSuite('README.txt', 'cookies.txt', 'fixed-bugs.txt')
-    wire = FileSuite('over_the_wire.txt')
+    wire = doctest.DocFileSuite('over_the_wire.txt', optionflags=flags,
+                                package='zope.testbrowser')
     wire.level = 2
 
     return unittest.TestSuite((suite, wire))

Deleted: zope.testbrowser/trunk/src/zope/testbrowser/tests.py
===================================================================
--- zope.testbrowser/trunk/src/zope/testbrowser/tests.py	2011-01-24 15:03:58 UTC (rev 119886)
+++ zope.testbrowser/trunk/src/zope/testbrowser/tests.py	2011-01-24 15:17:15 UTC (rev 119887)
@@ -1,503 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.0 (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.
-#
-##############################################################################
-"""Real test for file-upload and beginning of a better internal test framework
-"""
-import unittest
-
-import cStringIO
-import doctest
-import httplib
-import mechanize
-import os
-import re
-import socket
-import sys
-
-from zope.app.testing.functional import FunctionalDocFileSuite
-import zope.app.testing.functional
-import zope.testbrowser.browser
-import zope.testing.renormalizing
-
-
-def set_next_response(body, headers=None, status='200', reason='OK'):
-    global next_response_body
-    global next_response_headers
-    global next_response_status
-    global next_response_reason
-    if headers is None:
-        headers = (
-            'Content-Type: text/html\r\n'
-            'Content-Length: %s\r\n'
-            % len(body))
-    next_response_body = body
-    next_response_headers = headers
-    next_response_status = status
-    next_response_reason = reason
-
-
-class FauxConnection(object):
-    """A ``mechanize`` compatible connection object."""
-
-    def __init__(self, host, timeout=None):
-        pass
-
-    def set_debuglevel(self, level):
-        pass
-
-    def _quote(self, url):
-        # the publisher expects to be able to split on whitespace, so we have
-        # to make sure there is none in the URL
-        return url.replace(' ', '%20')
-
-    def request(self, method, url, body=None, headers=None):
-        if body is None:
-            body = ''
-
-        if url == '':
-            url = '/'
-
-        url = self._quote(url)
-
-        # Construct the headers.
-        header_chunks = []
-        if headers is not None:
-            for header in headers.items():
-                header_chunks.append('%s: %s' % header)
-            headers = '\n'.join(header_chunks) + '\n'
-        else:
-            headers = ''
-
-        # Construct the full HTTP request string, since that is what the
-        # ``HTTPCaller`` wants.
-        request_string = (method + ' ' + url + ' HTTP/1.1\n'
-                          + headers + '\n' + body)
-
-        print request_string.replace('\r', '')
-
-    def getresponse(self):
-        """Return a ``mechanize`` compatible response.
-
-        The goal of this method is to convert the Zope Publisher's response to
-        a ``mechanize`` compatible response, which is also understood by
-        mechanize.
-        """
-        return FauxResponse(next_response_body,
-                            next_response_headers,
-                            next_response_status,
-                            next_response_reason,
-                            )
-
-
-class FauxResponse(object):
-
-    def __init__(self, content, headers, status, reason):
-        self.content = content
-        self.status = status
-        self.reason = reason
-        self.msg = httplib.HTTPMessage(cStringIO.StringIO(headers), 0)
-        self.content_as_file = cStringIO.StringIO(self.content)
-
-    def read(self, amt=None):
-        return self.content_as_file.read(amt)
-
-    def close(self):
-        """To overcome changes in mechanize and socket in python2.5"""
-        pass
-
-
-class FauxHTTPHandler(mechanize.HTTPHandler):
-
-    http_request = mechanize.HTTPHandler.do_request_
-
-    def http_open(self, req):
-        """Open an HTTP connection having a ``mechanize`` request."""
-        # Here we connect to the publisher.
-
-        if sys.version_info > (2, 6) and not hasattr(req, 'timeout'):
-            # Workaround mechanize incompatibility with Python
-            # 2.6. See: LP #280334
-            req.timeout = socket._GLOBAL_DEFAULT_TIMEOUT
-        return self.do_open(FauxConnection, req)
-
-
-class FauxMechanizeBrowser(mechanize.Browser):
-
-    handler_classes = {
-        # scheme handlers
-        "http": FauxHTTPHandler,
-
-        "_http_error": mechanize.HTTPErrorProcessor,
-        "_http_default_error": mechanize.HTTPDefaultErrorHandler,
-
-        # feature handlers
-        "_authen": mechanize.HTTPBasicAuthHandler,
-        "_redirect": mechanize.HTTPRedirectHandler,
-        "_cookies": mechanize.HTTPCookieProcessor,
-        "_refresh": mechanize.HTTPRefreshProcessor,
-        "_referer": mechanize.Browser.handler_classes['_referer'],
-        "_equiv": mechanize.HTTPEquivProcessor,
-        }
-
-    default_schemes = ["http"]
-    default_others = ["_http_error", "_http_default_error"]
-    default_features = ["_authen", "_redirect", "_cookies"]
-
-
-class Browser(zope.testbrowser.browser.Browser):
-
-    def __init__(self, url=None):
-        mech_browser = FauxMechanizeBrowser()
-        super(Browser, self).__init__(url=url, mech_browser=mech_browser)
-
-    def open(self, body, headers=None, status=200, reason='OK',
-             url='http://localhost/'):
-        set_next_response(body, headers, status, reason)
-        zope.testbrowser.browser.Browser.open(self, url)
-
-
-def test_submit_duplicate_name():
-    """
-    This test was inspired by bug #723 as testbrowser would pick up the wrong
-    button when having the same name twice in a form.
-
-    >>> browser = Browser()
-
-
-    When given a form with two submit buttons that have the same name:
-
-    >>> browser.open('''\
-    ... <html><body>
-    ...   <form action="." method="post" enctype="multipart/form-data">
-    ...      <input type="submit" name="submit_me" value="GOOD" />
-    ...      <input type="submit" name="submit_me" value="BAD" />
-    ...   </form></body></html>
-    ... ''') # doctest: +ELLIPSIS
-    GET / HTTP/1.1
-    ...
-
-
-    We can specify the second button through it's label/value:
-
-    >>> browser.getControl('BAD')
-    <SubmitControl name='submit_me' type='submit'>
-    >>> browser.getControl('BAD').value
-    'BAD'
-    >>> browser.getControl('BAD').click() # doctest: +REPORT_NDIFF +ELLIPSIS
-    POST / HTTP/1.1
-    ...
-    Content-type: multipart/form-data; ...
-    Content-disposition: form-data; name="submit_me"
-    <BLANKLINE>
-    BAD
-    ...
-
-
-    This also works if the labels have whitespace around them (this tests a
-    regression caused by the original fix for the above):
-
-    >>> browser.open('''\
-    ... <html><body>
-    ...   <form action="." method="post" enctype="multipart/form-data">
-    ...      <input type="submit" name="submit_me" value=" GOOD " />
-    ...      <input type="submit" name="submit_me" value=" BAD " />
-    ...   </form></body></html>
-    ... ''') # doctest: +ELLIPSIS
-    GET / HTTP/1.1
-    ...
-    >>> browser.getControl('BAD')
-    <SubmitControl name='submit_me' type='submit'>
-    >>> browser.getControl('BAD').value
-    ' BAD '
-    >>> browser.getControl('BAD').click() # doctest: +REPORT_NDIFF +ELLIPSIS
-    POST / HTTP/1.1
-    ...
-    Content-type: multipart/form-data; ...
-    Content-disposition: form-data; name="submit_me"
-    <BLANKLINE>
-     BAD 
-    ...
-"""
-
-
-def test_file_upload():
-    """
-
-    >>> browser = Browser()
-
-
-    When given a form with a file-upload
-
-    >>> browser.open('''\
-    ... <html><body>
-    ...   <form action="." method="post" enctype="multipart/form-data">
-    ...      <input name="foo" type="file" />
-    ...      <input type="submit" value="OK" />
-    ...   </form></body></html>
-    ... ''') # doctest: +ELLIPSIS
-    GET / HTTP/1.1
-    ...
-
-
-    Fill in the form value using add_file:
-
-    >>> browser.getControl(name='foo').add_file(
-    ...     cStringIO.StringIO('sample_data'), 'text/foo', 'x.foo')
-    >>> browser.getControl('OK').click() # doctest: +REPORT_NDIFF +ELLIPSIS
-    POST / HTTP/1.1
-    ...
-    Content-type: multipart/form-data; ...
-    Content-disposition: form-data; name="foo"; filename="x.foo"
-    Content-type: text/foo
-    <BLANKLINE>
-    sample_data
-    ...
-
-
-    You can pass a string to add_file:
-
-    >>> browser.getControl(name='foo').add_file(
-    ...     'blah blah blah', 'text/blah', 'x.blah')
-    >>> browser.getControl('OK').click() # doctest: +REPORT_NDIFF +ELLIPSIS
-    POST / HTTP/1.1
-    ...
-    Content-type: multipart/form-data; ...
-    Content-disposition: form-data; name="foo"; filename="x.blah"
-    Content-type: text/blah
-    <BLANKLINE>
-    blah blah blah
-    ...
-    """
-
-def test_submit_gets_referrer():
-    """
-    Test for bug #98437: No HTTP_REFERER was sent when submitting a form.
-
-    >>> browser = Browser()
-
-
-    A simple form for testing, like abobe.
-
-    >>> browser.open('''\
-    ... <html><body>
-    ...   <form id="form" action="." method="post"
-    ...                   enctype="multipart/form-data">
-    ...      <input type="submit" name="submit_me" value="GOOD" />
-    ...   </form></body></html>
-    ... ''') # doctest: +ELLIPSIS
-    GET / HTTP/1.1
-    ...
-
-
-    Now submit the form, and see that we get an referrer along:
-
-    >>> form = browser.getForm(id='form')
-    >>> form.submit(name='submit_me') # doctest: +ELLIPSIS
-    POST / HTTP/1.1
-    ...
-    Referer: http://localhost/
-    ...
-"""
-
-
-def test_new_instance_no_contents_should_not_fail(self):
-    """
-    When first instantiated, the browser has no contents.
-    (Regression test for <http://bugs.launchpad.net/zope3/+bug/419119>)
-
-    >>> browser = Browser()
-    >>> print browser.contents
-    None
-    """
-
-
-def test_strip_linebreaks_from_textarea(self):
-    """
-    >>> browser = Browser()
-
-    According to http://www.w3.org/TR/html4/appendix/notes.html#h-B.3.1 line
-    break immediately after start tags or immediately before end tags must be
-    ignored, but real browsers only ignore a line break after a start tag.
-    So if we give the following form:
-
-    >>> browser.open('''
-    ... <html><body>
-    ...   <form action="." method="post" enctype="multipart/form-data">
-    ...      <textarea name="textarea">
-    ... Foo
-    ... </textarea>
-    ...   </form></body></html>
-    ... ''') # doctest: +ELLIPSIS
-    GET / HTTP/1.1
-    ...
-
-
-    The value of the textarea won't contain the first line break:
-
-    >>> browser.getControl(name='textarea').value
-    'Foo\\n'
-
-
-    Of course, if we add line breaks, so that there are now two line breaks
-    after the start tag, the textarea value will start and end with a line
-    break.
-
-    >>> browser.open('''
-    ... <html><body>
-    ...   <form action="." method="post" enctype="multipart/form-data">
-    ...      <textarea name="textarea">
-    ...
-    ... Foo
-    ... </textarea>
-    ...   </form></body></html>
-    ... ''') # doctest: +ELLIPSIS
-    GET / HTTP/1.1
-    ...
-
-    >>> browser.getControl(name='textarea').value
-    '\\nFoo\\n'
-
-
-    Also, if there is some other whitespace after the start tag, it will be
-    preserved.
-
-    >>> browser.open('''
-    ... <html><body>
-    ...   <form action="." method="post" enctype="multipart/form-data">
-    ...      <textarea name="textarea">  Foo  </textarea>
-    ...   </form></body></html>
-    ... ''') # doctest: +ELLIPSIS
-    GET / HTTP/1.1
-    ...
-
-    >>> browser.getControl(name='textarea').value
-    '  Foo  '
-    """
-
-
-def test_relative_link():
-    """
-    RFC 1808 specifies how relative URLs should be resolved, let's see
-    that we conform to it. Let's start with a simple example.
-
-    >>> browser = Browser()
-    >>> browser.open('''\
-    ... <html><body>
-    ...     <a href="foo">link</a>
-    ... </body></html>
-    ... ''', url='http://localhost/bar') # doctest: +ELLIPSIS
-    GET /bar HTTP/1.1
-    ...
-
-    >>> link = browser.getLink('link')
-    >>> link.url
-    'http://localhost/foo'
-
-
-    It's possible to have a relative URL consisting of only a query part. In
-    that case it should simply be appended to the base URL.
-
-    >>> browser.open('''\
-    ... <html><body>
-    ...     <a href="?key=value">link</a>
-    ... </body></html>
-    ... ''', url='http://localhost/bar') # doctest: +ELLIPSIS
-    GET /bar HTTP/1.1
-    ...
-
-    >>> link = browser.getLink('link')
-    >>> link.url
-    'http://localhost/bar?key=value'
-
-
-    In the example above, the base URL was the page URL, but we can also
-    specify a base URL using a <base> tag.
-
-    >>> browser.open('''\
-    ... <html><head><base href="http://localhost/base" /></head><body>
-    ...     <a href="?key=value">link</a>
-    ... </body></html>
-    ... ''', url='http://localhost/base/bar') # doctest: +ELLIPSIS
-    GET /base/bar HTTP/1.1
-    ...
-
-    >>> link = browser.getLink('link')
-    >>> link.url
-    'http://localhost/base?key=value'
-    """
-
-
-class win32CRLFtransformer(object):
-    def sub(self, replacement, text):
-        return text.replace(r'\r', '')
-
-checker = zope.testing.renormalizing.RENormalizing([
-    (re.compile(r'^--\S+\.\S+\.\S+', re.M), '-' * 30),
-    (re.compile(r'boundary=\S+\.\S+\.\S+'), 'boundary=' + '-' * 30),
-    (re.compile(r'^---{10}.*', re.M), '-' * 30),
-    (re.compile(r'boundary=-{10}.*'), 'boundary=' + '-' * 30),
-    (re.compile(r'User-agent:\s+\S+'), 'User-agent: Python-urllib/2.4'),
-    (re.compile(r'HTTP_USER_AGENT:\s+\S+'),
-     'HTTP_USER_AGENT: Python-urllib/2.4'),
-    (re.compile(r'Content-[Ll]ength:.*'), 'Content-Length: 123'),
-    (re.compile(r'Status: 200.*'), 'Status: 200 OK'),
-    (win32CRLFtransformer(), None),
-    (re.compile(r'User-Agent: Python-urllib/2.5'),
-     'User-agent: Python-urllib/2.4'),
-    (re.compile(r'User-Agent: Python-urllib/2.6'),
-     'User-agent: Python-urllib/2.4'),
-    (re.compile(r'Host: localhost'), 'Connection: close'),
-    (re.compile(r'Content-Type: '), 'Content-type: '),
-    (re.compile(r'Content-Disposition: '), 'Content-disposition: '),
-    ])
-
-TestBrowserLayer = zope.app.testing.functional.ZCMLLayer(
-    os.path.join(os.path.split(__file__)[0], 'ftests/ftesting.zcml'),
-    __name__, 'TestBrowserLayer', allow_teardown=True)
-
-
-def test_suite():
-    flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
-
-    readme = FunctionalDocFileSuite('README.txt', optionflags=flags,
-        checker=checker)
-    readme.layer = TestBrowserLayer
-
-    cookies = FunctionalDocFileSuite('cookies.txt', optionflags=flags,
-        checker=checker)
-    cookies.layer = TestBrowserLayer
-
-    fixed_bugs = FunctionalDocFileSuite('fixed-bugs.txt', optionflags=flags)
-    fixed_bugs.layer = TestBrowserLayer
-
-    wire = doctest.DocFileSuite('over_the_wire.txt', optionflags=flags)
-    wire.level = 2
-
-    this_file = doctest.DocTestSuite(checker=checker)
-
-    return unittest.TestSuite((this_file, readme, fixed_bugs, wire, cookies))
-
-def run_suite(suite):
-    runner = unittest.TextTestRunner(sys.stdout, verbosity=1)
-    result = runner.run(suite)
-    if not result.wasSuccessful():
-        if len(result.errors) == 1 and not result.failures:
-            err = result.errors[0][1]
-        elif len(result.failures) == 1 and not result.errors:
-            err = result.failures[0][1]
-        else:
-            err = "errors occurred; run in verbose mode for details"
-        print err
-
-if __name__ == "__main__":
-    run_suite(test_suite())

Copied: zope.testbrowser/trunk/src/zope/testbrowser/wsgi.py (from rev 119884, zope.testbrowser/tags/3.10.3/src/zope/testbrowser/wsgi.py)
===================================================================
--- zope.testbrowser/trunk/src/zope/testbrowser/wsgi.py	                        (rev 0)
+++ zope.testbrowser/trunk/src/zope/testbrowser/wsgi.py	2011-01-24 15:17:15 UTC (rev 119887)
@@ -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')



More information about the checkins mailing list