wsgi_intercept-0.5.1/0000755000076500000240000000000011660472720014770 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/AUTHORS.txt0000644000076500000240000000016611660467065016667 0ustar cdentstaff00000000000000AUTHORS Titus Brown Kumar McMillan CONTRIBUTORS David "Whit" Morris Jeffrey Cousens Gary Bernhardt Peter Henderson wsgi_intercept-0.5.1/CHANGELOG.txt0000644000076500000240000000166211660467065017033 0ustar cdentstaff00000000000000 0.5.0 ----- - issue19: Better HTTPS support in httplib. 0.4.0 ----- - issue13: ported a bugfix from twill that fixed the content response getting duplicated when using the Django [and possibly other?] wsgi handlers (reported by Ed Summers) 0.3.4 ----- - issue11: fixed bug where query string and path info was unquoted when it shouldn't be 0.3.3 ----- - issue9: fixed major bug where HTTPS was not supported by all intercept mechanisms except for zope.textbrowser (this was originally reported by Robert Leftwich on the twill list). 0.3.2 ----- - issue7: fixed masked exception in httplib2 (patch by Gary Bernhardt) - issue8: fixed missing QUERY_STRING in WSGI environment and un-closed WSGI iterator (patch by Gary Bernhardt) 0.3.1 ----- - issue3: made urllib2 intercept compatible with python 2.5 (patch by Jeffrey Cousens) - issue4: removed unnecessary import statement in wsgi_intercept.zope_testbrowser 0.3 --- - first releasewsgi_intercept-0.5.1/docs/0000755000076500000240000000000011660472720015720 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/docs/index.rst0000644000076500000240000000046511660467065017574 0ustar cdentstaff00000000000000=============================================================================== wsgi_intercept: installs a WSGI application in place of a real URI for testing. =============================================================================== .. contents:: .. include_docstring:: ./wsgi_intercept/__init__.py wsgi_intercept-0.5.1/docs/README.txt0000644000076500000240000000043711660467065017430 0ustar cdentstaff00000000000000 Build docs as HTML with:: python setup.py build_docs To publish docs to stdout in Google Code wiki format:: python setup.py publish_docs --google-user=x --google-password=x Just use literally "x" for user / pass since logging in and publishing is not implemented. Yea!wsgi_intercept-0.5.1/LICENSE.txt0000644000076500000240000000215011660467065016617 0ustar cdentstaff00000000000000MIT License see http://en.wikipedia.org/wiki/MIT_License Copyright (c) 2007 Titus Brown, Kumar McMillan 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.wsgi_intercept-0.5.1/PKG-INFO0000644000076500000240000002212711660472720016071 0ustar cdentstaff00000000000000Metadata-Version: 1.0 Name: wsgi_intercept Version: 0.5.1 Summary: installs a WSGI application in place of a real URI for testing. Home-page: http://code.google.com/p/wsgi-intercept/ Author: Titus Brown, Kumar McMillan Author-email: kumar.mcmillan@gmail.com License: MIT License Description: Introduction ============ Testing a WSGI application normally involves starting a server at a local host and port, then pointing your test code to that address. Instead, this library lets you intercept calls to any specific host/port combination and redirect them into a `WSGI application`_ importable by your test program. Thus, you can avoid spawning multiple processes or threads to test your Web app. How Does It Work? ================= ``wsgi_intercept`` works by replacing ``httplib.HTTPConnection`` with a subclass, ``wsgi_intercept.WSGI_HTTPConnection``. This class then redirects specific server/port combinations into a WSGI application by emulating a socket. If no intercept is registered for the host and port requested, those requests are passed on to the standard handler. The functions ``add_wsgi_intercept(host, port, app_create_fn, script_name='')`` and ``remove_wsgi_intercept(host,port)`` specify which URLs should be redirect into what applications. Note especially that ``app_create_fn`` is a *function object* returning a WSGI application; ``script_name`` becomes ``SCRIPT_NAME`` in the WSGI app's environment, if set. Install ======= :: easy_install wsgi_intercept (The ``easy_install`` command is bundled with the setuptools_ module) To use a `development version`_ of wsgi_intercept, run:: easy_install http://wsgi-intercept.googlecode.com/svn/trunk .. _setuptools: http://cheeseshop.python.org/pypi/setuptools/ .. _development version: http://wsgi-intercept.googlecode.com/svn/trunk/#egg=wsgi_intercept-dev Packages Intercepted ==================== Unfortunately each of the Web testing frameworks uses its own specific mechanism for making HTTP call-outs, so individual implementations are needed. Below are the packages supported and how to create an intercept. urllib2 ------- urllib2_ is a standard Python module, and ``urllib2.urlopen`` is a pretty normal way to open URLs. The following code will install the WSGI intercept stuff as a default urllib2 handler: :: >>> from wsgi_intercept.urllib2_intercept import install_opener >>> install_opener() #doctest: +ELLIPSIS >>> import wsgi_intercept >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> import urllib2 >>> urllib2.urlopen('http://some_host:80/').read() 'WSGI intercept successful!\n' The only tricky bit in there is that different handler classes need to be constructed for Python 2.3 and Python 2.4, because the httplib interface changed between those versions. .. _urllib2: http://docs.python.org/lib/module-urllib2.html httplib2 -------- httplib2_ is a 3rd party extension of the built-in ``httplib``. To intercept requests, it is similar to urllib2:: >>> from wsgi_intercept.httplib2_intercept import install >>> install() >>> import wsgi_intercept >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> import httplib2 >>> resp, content = httplib2.Http().request('http://some_host:80/', 'GET') >>> content 'WSGI intercept successful!\n' (Contributed by `David "Whit" Morris`_.) .. _httplib2: http://code.google.com/p/httplib2/ .. _David "Whit" Morris: http://public.xdi.org/=whit webtest ------- webtest_ is an extension to ``unittest`` that has some nice functions for testing Web sites. To install the WSGI intercept handler, do :: >>> import wsgi_intercept.webtest_intercept >>> class WSGI_Test(wsgi_intercept.webtest_intercept.WebCase): ... HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection ... HOST='localhost' ... PORT=80 ... ... def setUp(self): ... wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT, create_fn) ... >>> .. _webtest: http://www.cherrypy.org/file/trunk/cherrypy/test/webtest.py webunit ------- webunit_ is another unittest-like framework that contains nice functions for Web testing. (funkload_ uses webunit, too.) webunit needed to be patched to support different scheme handlers. The patched package is in webunit/wsgi_webunit/, and the only file that was changed was webunittest.py; the original is in webunittest-orig.py. To install the WSGI intercept handler, do :: >>> from httplib import HTTP >>> import wsgi_intercept.webunit_intercept >>> class WSGI_HTTP(HTTP): ... _connection_class = wsgi_intercept.WSGI_HTTPConnection ... >>> class WSGI_WebTestCase(wsgi_intercept.webunit_intercept.WebTestCase): ... scheme_handlers = dict(http=WSGI_HTTP) ... ... def setUp(self): ... wsgi_intercept.add_wsgi_intercept('127.0.0.1', 80, create_fn) ... >>> .. _webunit: http://mechanicalcat.net/tech/webunit/ mechanize --------- mechanize_ is John J. Lee's port of Perl's WWW::Mechanize to Python. It mimics a browser. (It's also what's behind twill_.) >>> import wsgi_intercept.mechanize_intercept >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> b = wsgi_intercept.mechanize_intercept.Browser() >>> response = b.open('http://some_host:80') >>> response.read() 'WSGI intercept successful!\n' .. _mechanize: http://wwwsearch.sf.net/ zope.testbrowser ---------------- zope.testbrowser_ is a prettified interface to mechanize_ that is used primarily for testing Zope applications. zope.testbrowser is also pretty easy :: >>> import wsgi_intercept.zope_testbrowser >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> b = wsgi_intercept.zope_testbrowser.WSGI_Browser('http://some_host:80/') >>> b.contents 'WSGI intercept successful!\n' .. _zope.testbrowser: http://www.python.org/pypi/zope.testbrowser History ======= Pursuant to Ian Bicking's `"best Web testing framework"`_ post, Titus Brown put together an `in-process HTTP-to-WSGI interception mechanism`_ for his own Web testing system, twill_. Because the mechanism is pretty generic -- it works at the httplib level -- Titus decided to try adding it into all of the *other* Python Web testing frameworks. This is the result. Mocking your HTTP Server ======================== Marc Hedlund has gone one further, and written a full-blown mock HTTP server for wsgi_intercept. Combined with wsgi_intercept itself, this lets you entirely replace client calls to a server with a mock setup that hits neither the network nor server code. You can see his work in the file ``mock_http.py``. Run ``mock_http.py`` to see a test. .. _twill: http://www.idyll.org/~t/www-tools/twill.html .. _"best Web testing framework": http://blog.ianbicking.org/best-of-the-web-app-test-frameworks.html .. _in-process HTTP-to-WSGI interception mechanism: http://www.advogato.org/person/titus/diary.html?start=119 .. _WSGI application: http://www.python.org/peps/pep-0333.html .. _funkload: http://funkload.nuxeo.org/ Project Home ============ If you aren't already there, this project lives on `Google Code`_. Please submit all bugs, patches, failing tests, et cetera using the `Issue Tracker`_ .. _Google Code: http://code.google.com/p/wsgi-intercept/ .. _Issue Tracker: http://code.google.com/p/wsgi-intercept/issues/list Platform: UNKNOWN wsgi_intercept-0.5.1/README.txt0000644000076500000240000000001311660467065016466 0ustar cdentstaff00000000000000see ./docs/wsgi_intercept-0.5.1/run-tests0000644000076500000240000000020311660467065016660 0ustar cdentstaff00000000000000#!/bin/sh if [ -e "`which tox`" ]; then tox $@ else echo "**** install tox to run the tests http://codespeak.net/tox/" fi wsgi_intercept-0.5.1/setup.cfg0000644000076500000240000000023511660472720016611 0ustar cdentstaff00000000000000[global] command_packages = wsgi_intercept.setup_cmd [nosetests] verbosity = 2 with-doctest = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 wsgi_intercept-0.5.1/setup.py0000644000076500000240000000326111660467065016512 0ustar cdentstaff00000000000000 from setuptools import setup, find_packages import compiler, pydoc from compiler import visitor class ModuleVisitor(object): def __init__(self): self.mod_doc = None self.mod_version = None def default(self, node): for child in node.getChildNodes(): self.visit(child) def visitModule(self, node): self.mod_doc = node.doc self.default(node) def visitAssign(self, node): if self.mod_version: return asn = node.nodes[0] assert asn.name == '__version__', ( "expected __version__ node: %s" % asn) self.mod_version = node.expr.value self.default(node) def get_module_meta(modfile): ast = compiler.parseFile(modfile) modnode = ModuleVisitor() visitor.walk(ast, modnode) if modnode.mod_doc is None: raise RuntimeError( "could not parse doc string from %s" % modfile) if modnode.mod_version is None: raise RuntimeError( "could not parse __version__ from %s" % modfile) return (modnode.mod_version,) + pydoc.splitdoc(modnode.mod_doc) version, description, long_description = get_module_meta("./wsgi_intercept/__init__.py") setup( name = 'wsgi_intercept', version = version, author = 'Titus Brown, Kumar McMillan', author_email = 'kumar.mcmillan@gmail.com', description = description, url="http://code.google.com/p/wsgi-intercept/", long_description = long_description, license = 'MIT License', packages = find_packages(), test_suite = "nose.collector", tests_require=['nose', 'Paste', 'httplib2', 'mechanize', 'mechanoid', 'WebTest', 'zope.testbrowser', 'webunit'], )wsgi_intercept-0.5.1/tox.ini0000644000076500000240000000027211660470757016314 0ustar cdentstaff00000000000000 [tox] envlist=py27 [testenv] deps=nose Paste httplib2 mechanize mechanoid WebTest zope.testbrowser webunit docutils commands= nosetests [] wsgi_intercept-0.5.1/wsgi_intercept/0000755000076500000240000000000011660472720020016 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept/__init__.py0000644000076500000240000004667211660472640022147 0ustar cdentstaff00000000000000 """installs a WSGI application in place of a real URI for testing. Introduction ============ Testing a WSGI application normally involves starting a server at a local host and port, then pointing your test code to that address. Instead, this library lets you intercept calls to any specific host/port combination and redirect them into a `WSGI application`_ importable by your test program. Thus, you can avoid spawning multiple processes or threads to test your Web app. How Does It Work? ================= ``wsgi_intercept`` works by replacing ``httplib.HTTPConnection`` with a subclass, ``wsgi_intercept.WSGI_HTTPConnection``. This class then redirects specific server/port combinations into a WSGI application by emulating a socket. If no intercept is registered for the host and port requested, those requests are passed on to the standard handler. The functions ``add_wsgi_intercept(host, port, app_create_fn, script_name='')`` and ``remove_wsgi_intercept(host,port)`` specify which URLs should be redirect into what applications. Note especially that ``app_create_fn`` is a *function object* returning a WSGI application; ``script_name`` becomes ``SCRIPT_NAME`` in the WSGI app's environment, if set. Install ======= :: easy_install wsgi_intercept (The ``easy_install`` command is bundled with the setuptools_ module) To use a `development version`_ of wsgi_intercept, run:: easy_install http://wsgi-intercept.googlecode.com/svn/trunk .. _setuptools: http://cheeseshop.python.org/pypi/setuptools/ .. _development version: http://wsgi-intercept.googlecode.com/svn/trunk/#egg=wsgi_intercept-dev Packages Intercepted ==================== Unfortunately each of the Web testing frameworks uses its own specific mechanism for making HTTP call-outs, so individual implementations are needed. Below are the packages supported and how to create an intercept. urllib2 ------- urllib2_ is a standard Python module, and ``urllib2.urlopen`` is a pretty normal way to open URLs. The following code will install the WSGI intercept stuff as a default urllib2 handler: :: >>> from wsgi_intercept.urllib2_intercept import install_opener >>> install_opener() #doctest: +ELLIPSIS >>> import wsgi_intercept >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> import urllib2 >>> urllib2.urlopen('http://some_host:80/').read() 'WSGI intercept successful!\\n' The only tricky bit in there is that different handler classes need to be constructed for Python 2.3 and Python 2.4, because the httplib interface changed between those versions. .. _urllib2: http://docs.python.org/lib/module-urllib2.html httplib2 -------- httplib2_ is a 3rd party extension of the built-in ``httplib``. To intercept requests, it is similar to urllib2:: >>> from wsgi_intercept.httplib2_intercept import install >>> install() >>> import wsgi_intercept >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> import httplib2 >>> resp, content = httplib2.Http().request('http://some_host:80/', 'GET') >>> content 'WSGI intercept successful!\\n' (Contributed by `David "Whit" Morris`_.) .. _httplib2: http://code.google.com/p/httplib2/ .. _David "Whit" Morris: http://public.xdi.org/=whit webtest ------- webtest_ is an extension to ``unittest`` that has some nice functions for testing Web sites. To install the WSGI intercept handler, do :: >>> import wsgi_intercept.webtest_intercept >>> class WSGI_Test(wsgi_intercept.webtest_intercept.WebCase): ... HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection ... HOST='localhost' ... PORT=80 ... ... def setUp(self): ... wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT, create_fn) ... >>> .. _webtest: http://www.cherrypy.org/file/trunk/cherrypy/test/webtest.py webunit ------- webunit_ is another unittest-like framework that contains nice functions for Web testing. (funkload_ uses webunit, too.) webunit needed to be patched to support different scheme handlers. The patched package is in webunit/wsgi_webunit/, and the only file that was changed was webunittest.py; the original is in webunittest-orig.py. To install the WSGI intercept handler, do :: >>> from httplib import HTTP >>> import wsgi_intercept.webunit_intercept >>> class WSGI_HTTP(HTTP): ... _connection_class = wsgi_intercept.WSGI_HTTPConnection ... >>> class WSGI_WebTestCase(wsgi_intercept.webunit_intercept.WebTestCase): ... scheme_handlers = dict(http=WSGI_HTTP) ... ... def setUp(self): ... wsgi_intercept.add_wsgi_intercept('127.0.0.1', 80, create_fn) ... >>> .. _webunit: http://mechanicalcat.net/tech/webunit/ mechanize --------- mechanize_ is John J. Lee's port of Perl's WWW::Mechanize to Python. It mimics a browser. (It's also what's behind twill_.) >>> import wsgi_intercept.mechanize_intercept >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> b = wsgi_intercept.mechanize_intercept.Browser() >>> response = b.open('http://some_host:80') >>> response.read() 'WSGI intercept successful!\\n' .. _mechanize: http://wwwsearch.sf.net/ zope.testbrowser ---------------- zope.testbrowser_ is a prettified interface to mechanize_ that is used primarily for testing Zope applications. zope.testbrowser is also pretty easy :: >>> import wsgi_intercept.zope_testbrowser >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> b = wsgi_intercept.zope_testbrowser.WSGI_Browser('http://some_host:80/') >>> b.contents 'WSGI intercept successful!\\n' .. _zope.testbrowser: http://www.python.org/pypi/zope.testbrowser History ======= Pursuant to Ian Bicking's `"best Web testing framework"`_ post, Titus Brown put together an `in-process HTTP-to-WSGI interception mechanism`_ for his own Web testing system, twill_. Because the mechanism is pretty generic -- it works at the httplib level -- Titus decided to try adding it into all of the *other* Python Web testing frameworks. This is the result. Mocking your HTTP Server ======================== Marc Hedlund has gone one further, and written a full-blown mock HTTP server for wsgi_intercept. Combined with wsgi_intercept itself, this lets you entirely replace client calls to a server with a mock setup that hits neither the network nor server code. You can see his work in the file ``mock_http.py``. Run ``mock_http.py`` to see a test. .. _twill: http://www.idyll.org/~t/www-tools/twill.html .. _"best Web testing framework": http://blog.ianbicking.org/best-of-the-web-app-test-frameworks.html .. _in-process HTTP-to-WSGI interception mechanism: http://www.advogato.org/person/titus/diary.html?start=119 .. _WSGI application: http://www.python.org/peps/pep-0333.html .. _funkload: http://funkload.nuxeo.org/ Project Home ============ If you aren't already there, this project lives on `Google Code`_. Please submit all bugs, patches, failing tests, et cetera using the `Issue Tracker`_ .. _Google Code: http://code.google.com/p/wsgi-intercept/ .. _Issue Tracker: http://code.google.com/p/wsgi-intercept/issues/list """ __version__ = '0.5.1' import sys from httplib import HTTPConnection import urllib from cStringIO import StringIO import traceback debuglevel = 0 # 1 basic # 2 verbose #### # # Specify which hosts/ports to target for interception to a given WSGI app. # # For simplicity's sake, intercept ENTIRE host/port combinations; # intercepting only specific URL subtrees gets complicated, because we don't # have that information in the HTTPConnection.connect() function that does the # redirection. # # format: key=(host, port), value=(create_app, top_url) # # (top_url becomes the SCRIPT_NAME) _wsgi_intercept = {} def add_wsgi_intercept(host, port, app_create_fn, script_name=''): """ Add a WSGI intercept call for host:port, using the app returned by app_create_fn with a SCRIPT_NAME of 'script_name' (default ''). """ _wsgi_intercept[(host, port)] = (app_create_fn, script_name) def remove_wsgi_intercept(*args): """ Remove the WSGI intercept call for (host, port). If no arguments are given, removes all intercepts """ global _wsgi_intercept if len(args)==0: _wsgi_intercept = {} else: key = (args[0], args[1]) if _wsgi_intercept.has_key(key): del _wsgi_intercept[key] # # make_environ: behave like a Web server. Take in 'input', and behave # as if you're bound to 'host' and 'port'; build an environment dict # for the WSGI app. # # This is where the magic happens, folks. # def make_environ(inp, host, port, script_name): """ Take 'inp' as if it were HTTP-speak being received on host:port, and parse it into a WSGI-ok environment dictionary. Return the dictionary. Set 'SCRIPT_NAME' from the 'script_name' input, and, if present, remove it from the beginning of the PATH_INFO variable. """ # # parse the input up to the first blank line (or its end). # environ = {} method_line = inp.readline() content_type = None content_length = None cookies = [] for line in inp: if not line.strip(): break k, v = line.strip().split(':', 1) v = v.lstrip() # # take care of special headers, and for the rest, put them # into the environ with HTTP_ in front. # if k.lower() == 'content-type': content_type = v elif k.lower() == 'content-length': content_length = v elif k.lower() == 'cookie' or k.lower() == 'cookie2': cookies.append(v) else: h = k.upper() h = h.replace('-', '_') environ['HTTP_' + h] = v if debuglevel >= 2: print 'HEADER:', k, v # # decode the method line # if debuglevel >= 2: print 'METHOD LINE:', method_line method, url, protocol = method_line.split(' ') # clean the script_name off of the url, if it's there. if not url.startswith(script_name): script_name = '' # @CTB what to do -- bad URL. scrap? else: url = url[len(script_name):] url = url.split('?', 1) path_info = url[0] query_string = "" if len(url) == 2: query_string = url[1] if debuglevel: print "method: %s; script_name: %s; path_info: %s; query_string: %s" % (method, script_name, path_info, query_string) r = inp.read() inp = StringIO(r) # # fill out our dictionary. # environ.update({ "wsgi.version" : (1,0), "wsgi.url_scheme": "http", "wsgi.input" : inp, # to read for POSTs "wsgi.errors" : StringIO(), "wsgi.multithread" : 0, "wsgi.multiprocess" : 0, "wsgi.run_once" : 0, "PATH_INFO" : path_info, "QUERY_STRING" : query_string, "REMOTE_ADDR" : '127.0.0.1', "REQUEST_METHOD" : method, "SCRIPT_NAME" : script_name, "SERVER_NAME" : host, "SERVER_PORT" : str(port), "SERVER_PROTOCOL" : protocol, }) # # query_string, content_type & length are optional. # if query_string: environ['QUERY_STRING'] = query_string if content_type: environ['CONTENT_TYPE'] = content_type if debuglevel >= 2: print 'CONTENT-TYPE:', content_type if content_length: environ['CONTENT_LENGTH'] = content_length if debuglevel >= 2: print 'CONTENT-LENGTH:', content_length # # handle cookies. # if cookies: environ['HTTP_COOKIE'] = "; ".join(cookies) if debuglevel: print 'WSGI environ dictionary:', environ return environ # # fake socket for WSGI intercept stuff. # class wsgi_fake_socket: """ Handle HTTP traffic and stuff into a WSGI application object instead. Note that this class assumes: 1. 'makefile' is called (by the response class) only after all of the data has been sent to the socket by the request class; 2. non-persistent (i.e. non-HTTP/1.1) connections. """ def __init__(self, app, host, port, script_name): self.app = app # WSGI app object self.host = host self.port = port self.script_name = script_name # SCRIPT_NAME (app mount point) self.inp = StringIO() # stuff written into this "socket" self.write_results = [] # results from the 'write_fn' self.results = None # results from running the app self.output = StringIO() # all output from the app, incl headers def makefile(self, *args, **kwargs): """ 'makefile' is called by the HTTPResponse class once all of the data has been written. So, in this interceptor class, we need to: 1. build a start_response function that grabs all the headers returned by the WSGI app; 2. create a wsgi.input file object 'inp', containing all of the traffic; 3. build an environment dict out of the traffic in inp; 4. run the WSGI app & grab the result object; 5. concatenate & return the result(s) read from the result object. @CTB: 'start_response' should return a function that writes directly to self.result, too. """ # dynamically construct the start_response function for no good reason. def start_response(status, headers, exc_info=None): # construct the HTTP request. self.output.write("HTTP/1.0 " + status + "\n") for k, v in headers: self.output.write('%s: %s\n' % (k, v,)) self.output.write('\n') def write_fn(s): self.write_results.append(s) return write_fn # construct the wsgi.input file from everything that's been # written to this "socket". inp = StringIO(self.inp.getvalue()) # build the environ dictionary. environ = make_environ(inp, self.host, self.port, self.script_name) # run the application. app_result = self.app(environ, start_response) self.result = iter(app_result) ### # read all of the results. the trick here is to get the *first* # bit of data from the app via the generator, *then* grab & return # the data passed back from the 'write' function, and then return # the generator data. this is because the 'write' fn doesn't # necessarily get called until the first result is requested from # the app function. # # see twill tests, 'test_wrapper_intercept' for a test that breaks # if this is done incorrectly. try: generator_data = None try: generator_data = self.result.next() finally: for data in self.write_results: self.output.write(data) if generator_data: self.output.write(generator_data) while 1: data = self.result.next() self.output.write(data) except StopIteration: pass if hasattr(app_result, 'close'): app_result.close() if debuglevel >= 2: print "***", self.output.getvalue(), "***" # return the concatenated results. return StringIO(self.output.getvalue()) def sendall(self, str): """ Save all the traffic to self.inp. """ if debuglevel >= 2: print ">>>", str, ">>>" self.inp.write(str) def close(self): "Do nothing, for now." pass # # WSGI_HTTPConnection # class WSGI_HTTPConnection(HTTPConnection): """ Intercept all traffic to certain hosts & redirect into a WSGI application object. """ def get_app(self, host, port): """ Return the app object for the given (host, port). """ key = (host, int(port)) app, script_name = None, None if _wsgi_intercept.has_key(key): (app_fn, script_name) = _wsgi_intercept[key] app = app_fn() return app, script_name def connect(self): """ Override the connect() function to intercept calls to certain host/ports. If no app at host/port has been registered for interception then a normal HTTPConnection is made. """ if debuglevel: sys.stderr.write('connect: %s, %s\n' % (self.host, self.port,)) try: (app, script_name) = self.get_app(self.host, self.port) if app: if debuglevel: sys.stderr.write('INTERCEPTING call to %s:%s\n' % \ (self.host, self.port,)) self.sock = wsgi_fake_socket(app, self.host, self.port, script_name) else: HTTPConnection.connect(self) except Exception, e: if debuglevel: # intercept & print out tracebacks traceback.print_exc() raise # # WSGI_HTTPSConnection # try: from httplib import HTTPSConnection except ImportError: pass else: class WSGI_HTTPSConnection(HTTPSConnection, WSGI_HTTPConnection): """ Intercept all traffic to certain hosts & redirect into a WSGI application object. """ def get_app(self, host, port): """ Return the app object for the given (host, port). """ key = (host, int(port)) app, script_name = None, None if _wsgi_intercept.has_key(key): (app_fn, script_name) = _wsgi_intercept[key] app = app_fn() return app, script_name def connect(self): """ Override the connect() function to intercept calls to certain host/ports. If no app at host/port has been registered for interception then a normal HTTPSConnection is made. """ if debuglevel: sys.stderr.write('connect: %s, %s\n' % (self.host, self.port,)) try: (app, script_name) = self.get_app(self.host, self.port) if app: if debuglevel: sys.stderr.write('INTERCEPTING call to %s:%s\n' % \ (self.host, self.port,)) self.sock = wsgi_fake_socket(app, self.host, self.port, script_name) else: HTTPSConnection.connect(self) except Exception, e: if debuglevel: # intercept & print out tracebacks traceback.print_exc() raise wsgi_intercept-0.5.1/wsgi_intercept/httplib2_intercept.py0000644000076500000240000000316711660467065024212 0ustar cdentstaff00000000000000 """intercept HTTP connections that use httplib2 (see wsgi_intercept/__init__.py for examples) """ import httplib2 import wsgi_intercept from httplib2 import SCHEME_TO_CONNECTION, HTTPConnectionWithTimeout, HTTPSConnectionWithTimeout import sys InterceptorMixin = wsgi_intercept.WSGI_HTTPConnection # might make more sense as a decorator def connect(self): """ Override the connect() function to intercept calls to certain host/ports. """ if wsgi_intercept.debuglevel: sys.stderr.write('connect: %s, %s\n' % (self.host, self.port,)) (app, script_name) = self.get_app(self.host, self.port) if app: if wsgi_intercept.debuglevel: sys.stderr.write('INTERCEPTING call to %s:%s\n' % \ (self.host, self.port,)) self.sock = wsgi_intercept.wsgi_fake_socket(app, self.host, self.port, script_name) else: self._connect() class HTTP_WSGIInterceptorWithTimeout(HTTPConnectionWithTimeout, InterceptorMixin): _connect = httplib2.HTTPConnectionWithTimeout.connect connect = connect class HTTPS_WSGIInterceptorWithTimeout(HTTPSConnectionWithTimeout, InterceptorMixin): _connect = httplib2.HTTPSConnectionWithTimeout.connect connect = connect def install(): SCHEME_TO_CONNECTION['http'] = HTTP_WSGIInterceptorWithTimeout SCHEME_TO_CONNECTION['https'] = HTTPS_WSGIInterceptorWithTimeout def uninstall(): SCHEME_TO_CONNECTION['http'] = HTTPConnectionWithTimeout SCHEME_TO_CONNECTION['https'] = HTTPSConnectionWithTimeout wsgi_intercept-0.5.1/wsgi_intercept/httplib_intercept.py0000644000076500000240000000103011660467065024113 0ustar cdentstaff00000000000000 """intercept HTTP connections that use httplib (see wsgi_intercept/__init__.py for examples) """ import httplib import wsgi_intercept import sys from httplib import ( HTTPConnection as OriginalHTTPConnection, HTTPSConnection as OriginalHTTPSConnection) def install(): httplib.HTTPConnection = wsgi_intercept.WSGI_HTTPConnection httplib.HTTPSConnection = wsgi_intercept.WSGI_HTTPSConnection def uninstall(): httplib.HTTPConnection = OriginalHTTPConnection httplib.HTTPSConnection = OriginalHTTPSConnection wsgi_intercept-0.5.1/wsgi_intercept/mechanize_intercept/0000755000076500000240000000000011660472720024036 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept/mechanize_intercept/__init__.py0000644000076500000240000000021511660467064026152 0ustar cdentstaff00000000000000"""intercept connections made using a mechanize Browser. (see wsgi_intercept/__init__.py for examples) """ from wsgi_browser import Browserwsgi_intercept-0.5.1/wsgi_intercept/mechanize_intercept/wsgi_browser.py0000644000076500000240000000205411660467064027132 0ustar cdentstaff00000000000000""" A mechanize browser that redirects specified HTTP connections to a WSGI object. """ from httplib import HTTP from mechanize import Browser as MechanizeBrowser from wsgi_intercept.urllib2_intercept import install_opener, uninstall_opener try: from mechanize import HTTPHandler except ImportError: # pre mechanize 0.1.0 it was a separate package # (this will break if it is combined with a newer mechanize) from ClientCookie import HTTPHandler import sys, os.path from wsgi_intercept.urllib2_intercept import WSGI_HTTPHandler, WSGI_HTTPSHandler class Browser(MechanizeBrowser): """ A version of the mechanize browser class that installs the WSGI intercept handler """ handler_classes = MechanizeBrowser.handler_classes.copy() handler_classes['http'] = WSGI_HTTPHandler handler_classes['https'] = WSGI_HTTPSHandler def __init__(self, *args, **kwargs): # install WSGI intercept handler. install(self) MechanizeBrowser.__init__(self, *args, **kwargs) def install(browser): install_opener()wsgi_intercept-0.5.1/wsgi_intercept/mock_http.py0000644000076500000240000000430211660467065022365 0ustar cdentstaff00000000000000# # mock_http.py # # Written by Marc Hedlund . # Released under the same terms as wsgi_intercept. # """ This is a dirt-simple example of using wsgi_intercept to set up a mock object HTTP server for testing HTTP clients. """ import sys sys.path.insert(0, 'urllib2') import unittest import urllib2 import wsgi_intercept from wsgi_intercept import urllib2_intercept as wsgi_urllib2 test_page = """ Mock HTTP Server

Mock HTTP Server

You have successfully reached the Mock HTTP Server.

""" class MockHttpServer: def __init__(self, port=8000): """Initializes the mock server on localhost port 8000. Use urllib2.urlopen('http://localhost:8000') to reach the test server. The constructor takes a 'port=' argument if you want the server to listen on a different port.""" wsgi_intercept.add_wsgi_intercept('localhost', port, self.interceptor) wsgi_urllib2.install_opener() def handleResponse(self, environment, start_response): """Processes a request to the mock server, and returns a String object containing the response document. The mock server will send this to the client code, which can read it as a StringIO document. This example always returns a successful response to any request; a more intricate mock server could examine the request environment to determine what sort of response to give.""" status = "200 OK" headers = [('Content-Type', 'text/html')] start_response(status, headers) return test_page def interceptor(self): """Sets this class as the handler for intercepted urllib2 requests.""" return self.handleResponse class MockHttpServerTest(unittest.TestCase): """Demonstrates the use of the MockHttpServer from client code.""" def setUp(self): self.server = MockHttpServer() def test_simple_get(self): result = urllib2.urlopen('http://localhost:8000/') self.assertEqual(result.read(), test_page) if __name__ == "__main__": unittest.main() wsgi_intercept-0.5.1/wsgi_intercept/setup_cmd/0000755000076500000240000000000011660472720022001 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept/setup_cmd/__init__.py0000644000076500000240000000000111660467064024106 0ustar cdentstaff00000000000000 wsgi_intercept-0.5.1/wsgi_intercept/setup_cmd/build_docs.py0000644000076500000240000001133311660467064024470 0ustar cdentstaff00000000000000 from distutils.cmd import Command from distutils.errors import * from distutils import log from docutils import statemachine from docutils.parsers.rst import directives from docutils.core import ( publish_file, publish_string, publish_doctree, publish_from_doctree) from docutils.parsers import rst from docutils.nodes import SparseNodeVisitor from docutils.readers.standalone import Reader from docutils.writers.html4css1 import HTMLTranslator, Writer from docutils import nodes import compiler from compiler import visitor from pprint import pprint import pydoc, os, shutil class DocInspector(object): """expose docstrings for objects by parsing the abstract syntax tree. splitdocfor() is the interface around this """ def __init__(self, filename): self.filename = filename self.top_level_doc = None self.map = {} def __getitem__(self, path): return self.map[path] def __contains__(self, path): return path in self.map def makepath(self, node): path = [n.name for n in node.lineage] + [node.name] # skip first name in lineage because that's the file ... return ".".join(path[1:]) def default(self, node): for child in node.getChildNodes(): self.visit(child, node.lineage + [node]) def visitModule(self, node): self.top_level_doc = node.doc node.name = self.filename node.lineage = [] # descend into classes and functions self.default(node) def visitClass(self, node, lineage=[]): node.lineage = lineage self.map[self.makepath(node)] = node.doc self.default(node) def visitFunction(self, node, lineage=[]): node.lineage = lineage self.map[self.makepath(node)] = node.doc self.default(node) def splitdocfor(path): """split the docstring for a path valid paths are:: ./path/to/module.py ./path/to/module.py:SomeClass.method returns (description, long_description) from the docstring for path or (None, None) if there isn't a docstring. Example:: >>> splitdocfor("./wsgi_intercept/__init__.py")[0] 'installs a WSGI application in place of a real URI for testing.' >>> splitdocfor("./wsgi_intercept/__init__.py:WSGI_HTTPConnection.get_app")[0] 'Return the app object for the given (host, port).' >>> """ if ":" in path: filename, objpath = path.split(':') else: filename, objpath = path, None inspector = DocInspector(filename) visitor.walk(compiler.parseFile(filename), inspector) if objpath is None: if inspector.top_level_doc is None: return None, None return pydoc.splitdoc(inspector.top_level_doc) else: if inspector[objpath] is None: return None, None return pydoc.splitdoc(inspector[objpath]) def include_docstring( name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): """include reStructuredText from a docstring. use the directive like: | .. include_docstring:: path/to/module.py | .. include_docstring:: path/to/module.py:SomeClass | .. include_docstring:: path/to/module.py:SomeClass.method """ rawpath = arguments[0] summary, body = splitdocfor(rawpath) # nabbed from docutils.parsers.rst.directives.misc.include include_lines = statemachine.string2lines(body, convert_whitespace=1) state_machine.insert_input(include_lines, None) return [] # return [publish_doctree(body)] include_docstring.arguments = (1, 0, 0) include_docstring.options = {} include_docstring.content = 0 directives.register_directive('include_docstring', include_docstring) class build_docs(Command): description = "build documentation for wsgi_intercept" user_options = [ # ('optname=', None, ""), ] def initialize_options(self): pass def finalize_options(self): pass def run(self): """build end-user documentation.""" if not os.path.exists('./build'): os.mkdir('./build') log.info("created build dir") if os.path.exists('./build/docs'): shutil.rmtree('./build/docs') os.mkdir("./build/docs") body = publish_file(open("./docs/index.rst", 'r'), destination=open("./build/docs/index.html", 'w'), writer_name='html', # settings_overrides={'halt_level':2, # 'report_level':5} ) log.info("published docs to: ./build/docs/index.html") wsgi_intercept-0.5.1/wsgi_intercept/setup_cmd/publish_docs.py0000644000076500000240000001462111660467064025042 0ustar cdentstaff00000000000000 import re, pydoc from distutils.cmd import Command from distutils.errors import * from distutils import log from docutils.core import publish_string, publish_parts from docutils import nodes from docutils.nodes import SparseNodeVisitor from docutils.writers import Writer import wsgi_intercept from mechanize import Browser wiki_word_re = re.compile(r'^[A-Z][a-z]+(?:[A-Z][a-z]+)+') class WikiWriter(Writer): def translate(self): visitor = WikiVisitor(self.document) self.document.walkabout(visitor) self.output = visitor.astext() class WikiVisitor(SparseNodeVisitor): """visits RST nodes and transforms into Moin Moin wiki syntax. swiped from the nose project, originally written by Jason Pellerin. """ def __init__(self, document): SparseNodeVisitor.__init__(self, document) self.list_depth = 0 self.list_item_prefix = None self.indent = self.old_indent = '' self.output = [] self.preformat = False self.section_level = 0 def astext(self): return ''.join(self.output) def visit_Text(self, node): #print "Text", node data = node.astext() if not self.preformat: data = data.lstrip('\n\r') data = data.replace('\r', '') data = data.replace('\n', ' ') self.output.append(data) def visit_bullet_list(self, node): self.list_depth += 1 self.list_item_prefix = (' ' * self.list_depth) + '* ' def depart_bullet_list(self, node): self.list_depth -= 1 if self.list_depth == 0: self.list_item_prefix = None else: self.list_item_prefix = (' ' * self.list_depth) + '* ' self.output.append('\n\n') def visit_list_item(self, node): self.old_indent = self.indent self.indent = self.list_item_prefix def depart_list_item(self, node): self.indent = self.old_indent def visit_literal_block(self, node): self.output.extend(['{{{', '\n']) self.preformat = True def depart_literal_block(self, node): self.output.extend(['\n', '}}}', '\n\n']) self.preformat = False def visit_doctest_block(self, node): self.output.extend(['{{{', '\n']) self.preformat = True def depart_doctest_block(self, node): self.output.extend(['\n', '}}}', '\n\n']) self.preformat = False def visit_paragraph(self, node): self.output.append(self.indent) def depart_paragraph(self, node): self.output.append('\n') if not isinstance(node.parent, nodes.list_item): self.output.append('\n') if self.indent == self.list_item_prefix: # we're in a sub paragraph of a list item self.indent = ' ' * self.list_depth def visit_reference(self, node): if node.has_key('refuri'): href = node['refuri'] elif node.has_key('refid'): href = '#' + node['refid'] else: href = None self.output.append('[' + href + ' ') def depart_reference(self, node): self.output.append(']') def _find_header_level(self, node): if isinstance(node.parent, nodes.topic): h_level = 2 # ?? elif isinstance(node.parent, nodes.document): h_level = 1 else: assert isinstance(node.parent, nodes.section), ( "unexpected parent: %s" % node.parent.__class__) h_level = self.section_level return h_level def _depart_header_node(self, node): h_level = self._find_header_level(node) self.output.append(' %s\n\n' % ('='*h_level)) self.list_depth = 0 self.indent = '' def _visit_header_node(self, node): h_level = self._find_header_level(node) self.output.append('%s ' % ('='*h_level)) def visit_subtitle(self, node): self._visit_header_node(node) def depart_subtitle(self, node): self._depart_header_node(node) def visit_title(self, node): self._visit_header_node(node) def depart_title(self, node): self._depart_header_node(node) def visit_title_reference(self, node): self.output.append("`") def depart_title_reference(self, node): self.output.append("`") def visit_section(self, node): self.section_level += 1 def depart_section(self, node): self.section_level -= 1 def visit_emphasis(self, node): self.output.append('*') def depart_emphasis(self, node): self.output.append('*') def visit_literal(self, node): self.output.append('`') def depart_literal(self, node): self.output.append('`') class publish_docs(Command): description = "publish documentation to front page of Google Code project" user_options = [ ('google-user=', None, "Google Code username"), ('google-password=', None, "Google Code password"), ] def initialize_options(self): self.google_user = None self.google_password = None def finalize_options(self): if self.google_user is None and self.google_password is None: raise DistutilsOptionError("--google-user and --google-password are required") def run(self): summary, doc = pydoc.splitdoc(wsgi_intercept.__doc__) wikidoc = publish_string(doc, writer=WikiWriter()) print wikidoc ## Google html is so broken that this isn't working :/ # br = Browser() # br.open('http://code.google.com/p/wsgi-intercept/admin') # url = br.geturl() # assert url.startswith('https://www.google.com/accounts/Login'), ( # "unexpected URL: %s" % url) # log.info("logging in to Google Code...") # forms = [f for f in br.forms()] # assert len(forms)==1, "unexpected forms: %s for %s" % (forms, br.geturl()) # br.select_form(nr=0) # br['Email'] = self.google_user # br['Passwd'] = self.google_password # admin = br.submit() # url = admin.geturl() # assert url=='http://code.google.com/p/wsgi-intercept/admin', ( # "unexpected URL: %s" % url) # br.select_form(nr=0) # br['projectdescription'] = wikidoc # br.submit() # print br.geturl() wsgi_intercept-0.5.1/wsgi_intercept/test/0000755000076500000240000000000011660472720020775 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept/test/__init__.py0000644000076500000240000000000011660467064023101 0ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept/test/test_httplib.py0000644000076500000240000000324411660467064024064 0ustar cdentstaff00000000000000#! /usr/bin/env python2.4 from nose.tools import with_setup, raises, eq_ from wsgi_intercept import httplib_intercept from socket import gaierror import wsgi_intercept from wsgi_intercept import test_wsgi_app import httplib _saved_debuglevel = None def http_install(): _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 httplib_intercept.install() wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, test_wsgi_app.create_fn) def http_uninstall(): wsgi_intercept.debuglevel = _saved_debuglevel wsgi_intercept.remove_wsgi_intercept('some_hopefully_nonexistant_domain', 80) httplib_intercept.uninstall() @with_setup(http_install, http_uninstall) def test_http_success(): http = httplib.HTTPConnection('some_hopefully_nonexistant_domain') http.request('GET', '/') content = http.getresponse().read() eq_(content, 'WSGI intercept successful!\n') assert test_wsgi_app.success() def https_install(): _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 httplib_intercept.install() wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 443, test_wsgi_app.create_fn) def https_uninstall(): wsgi_intercept.debuglevel = _saved_debuglevel wsgi_intercept.remove_wsgi_intercept('some_hopefully_nonexistant_domain', 443) httplib_intercept.uninstall() @with_setup(https_install, https_uninstall) def test_https_success(): http = httplib.HTTPSConnection('some_hopefully_nonexistant_domain') http.request('GET', '/') content = http.getresponse().read() eq_(content, 'WSGI intercept successful!\n') assert test_wsgi_app.success()wsgi_intercept-0.5.1/wsgi_intercept/test/test_httplib2.py0000644000076500000240000000236511660467064024151 0ustar cdentstaff00000000000000#! /usr/bin/env python2.4 from wsgi_intercept import httplib2_intercept from nose.tools import with_setup, raises, eq_ from socket import gaierror import wsgi_intercept from wsgi_intercept import test_wsgi_app import httplib2 _saved_debuglevel = None def install(port=80): _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 httplib2_intercept.install() wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', port, test_wsgi_app.create_fn) def uninstall(): wsgi_intercept.debuglevel = _saved_debuglevel httplib2_intercept.uninstall() @with_setup(install, uninstall) def test_success(): http = httplib2.Http() resp, content = http.request('http://some_hopefully_nonexistant_domain:80/', 'GET') eq_(content, "WSGI intercept successful!\n") assert test_wsgi_app.success() @with_setup(install, uninstall) @raises(gaierror) def test_bogus_domain(): wsgi_intercept.debuglevel = 1; httplib2_intercept.HTTP_WSGIInterceptorWithTimeout("_nonexistant_domain_").connect() @with_setup(lambda: install(443), uninstall) def test_https_success(): http = httplib2.Http() resp, content = http.request('https://some_hopefully_nonexistant_domain/', 'GET') assert test_wsgi_app.success()wsgi_intercept-0.5.1/wsgi_intercept/test/test_mechanize.py0000644000076500000240000000276311660467064024366 0ustar cdentstaff00000000000000 from nose.tools import with_setup, raises from urllib2 import URLError from wsgi_intercept.mechanize_intercept import Browser import wsgi_intercept from wsgi_intercept import test_wsgi_app from mechanize import Browser as MechanizeBrowser ### _saved_debuglevel = None def add_intercept(): # _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, test_wsgi_app.create_fn) def add_https_intercept(): wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 443, test_wsgi_app.create_fn) def remove_intercept(): wsgi_intercept.remove_wsgi_intercept('some_hopefully_nonexistant_domain', 80) # wsgi_intercept.debuglevel = _saved_debuglevel @with_setup(add_intercept, remove_intercept) def test_intercepted(): b = Browser() b.open('http://some_hopefully_nonexistant_domain:80/') assert test_wsgi_app.success() @with_setup(add_intercept) @raises(URLError) def test_intercept_removed(): remove_intercept() b = Browser() b.open('http://some_hopefully_nonexistant_domain:80/') @with_setup(add_https_intercept, remove_intercept) def test_https_intercept(): b = Browser() b.open('https://some_hopefully_nonexistant_domain:443/') assert test_wsgi_app.success() @with_setup(add_intercept, remove_intercept) def test_https_intercept_default_port(): b = Browser() b.open('https://some_hopefully_nonexistant_domain/') assert test_wsgi_app.success()wsgi_intercept-0.5.1/wsgi_intercept/test/test_webtest.py0000644000076500000240000000223211660467064024067 0ustar cdentstaff00000000000000#! /usr/bin/env python import sys import wsgi_intercept from wsgi_intercept import test_wsgi_app, webtest_intercept class WSGI_Test(webtest_intercept.WebCase): HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection HOST = 'some_hopefully_nonexistant_domain' def setUp(self): wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT, test_wsgi_app.create_fn) def tearDown(self): wsgi_intercept.remove_wsgi_intercept() def test_page(self): self.getPage('http://%s:%s/' % (self.HOST, self.PORT)) assert test_wsgi_app.success() class WSGI_HTTPS_Test(webtest_intercept.WebCase): HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection HOST = 'some_hopefully_nonexistant_domain' def setUp(self): wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT, test_wsgi_app.create_fn) def tearDown(self): wsgi_intercept.remove_wsgi_intercept() def test_page(self): self.getPage('https://%s:%s/' % (self.HOST, self.PORT)) assert test_wsgi_app.success() if __name__ == '__main__': webtest.main() wsgi_intercept-0.5.1/wsgi_intercept/test/test_webunit.py0000644000076500000240000000244011660467064024070 0ustar cdentstaff00000000000000#! /usr/bin/env python import sys, os.path import wsgi_intercept from wsgi_intercept import WSGI_HTTPConnection from wsgi_intercept import test_wsgi_app from httplib import HTTP class WSGI_HTTP(HTTP): _connection_class = WSGI_HTTPConnection ### from wsgi_intercept.webunit_intercept import WebTestCase import unittest class WSGI_WebTestCase(WebTestCase): scheme_handlers = dict(http=WSGI_HTTP) def setUp(self): wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, test_wsgi_app.create_fn) def tearDown(self): wsgi_intercept.remove_wsgi_intercept() def test_get(self): r = self.page('http://some_hopefully_nonexistant_domain/') assert test_wsgi_app.success() class WSGI_HTTPS_WebTestCase(WebTestCase): scheme_handlers = dict(https=WSGI_HTTP) def setUp(self): wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 443, test_wsgi_app.create_fn) def tearDown(self): wsgi_intercept.remove_wsgi_intercept() def test_get(self): r = self.page('https://some_hopefully_nonexistant_domain/') assert test_wsgi_app.success() if __name__ == '__main__': unittest.main() wsgi_intercept-0.5.1/wsgi_intercept/test/test_wsgi_compliance.py0000644000076500000240000000371711660467064025566 0ustar cdentstaff00000000000000#! /usr/bin/env python2.4 import warnings from nose.tools import eq_ from wsgi_intercept.httplib2_intercept import install, uninstall import wsgi_intercept from wsgi_intercept import test_wsgi_app import httplib2 from paste import lint _saved_debuglevel = None def prudent_wsgi_app(): return lint.middleware(test_wsgi_app.create_fn()) def setup(): warnings.simplefilter("error") _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 install() wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, prudent_wsgi_app) def test(): http = httplib2.Http() resp, content = http.request('http://some_hopefully_nonexistant_domain:80/', 'GET') assert test_wsgi_app.success() def test_quoting_issue11(): # see http://code.google.com/p/wsgi-intercept/issues/detail?id=11 http = httplib2.Http() inspected_env = {} def make_path_checking_app(): def path_checking_app(environ, start_response): inspected_env ['QUERY_STRING'] = environ['QUERY_STRING'] inspected_env ['PATH_INFO'] = environ['PATH_INFO'] status = '200 OK' response_headers = [('Content-type','text/plain')] start_response(status, response_headers) return [] return path_checking_app wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, make_path_checking_app) resp, content = http.request('http://some_hopefully_nonexistant_domain:80/spaced+words.html?word=something%20spaced', 'GET') assert ('QUERY_STRING' in inspected_env and 'PATH_INFO' in inspected_env), "path_checking_app() was never called?" eq_(inspected_env['PATH_INFO'], '/spaced+words.html') eq_(inspected_env['QUERY_STRING'], 'word=something%20spaced') def teardown(): warnings.resetwarnings() wsgi_intercept.debuglevel = _saved_debuglevel uninstall() if __name__ == '__main__': setup() try: test() finally: teardown() wsgi_intercept-0.5.1/wsgi_intercept/test/test_wsgi_urllib2.py0000644000076500000240000000316211660467064025021 0ustar cdentstaff00000000000000#! /usr/bin/env python import sys, os.path from nose.tools import with_setup import urllib2 from wsgi_intercept import urllib2_intercept import wsgi_intercept from wsgi_intercept import test_wsgi_app _saved_debuglevel = None def add_http_intercept(): _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, test_wsgi_app.create_fn) def add_https_intercept(): _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 443, test_wsgi_app.create_fn) def remove_intercept(): wsgi_intercept.debuglevel = _saved_debuglevel wsgi_intercept.remove_wsgi_intercept() @with_setup(add_http_intercept, remove_intercept) def test_http(): urllib2_intercept.install_opener() urllib2.urlopen('http://some_hopefully_nonexistant_domain:80/') assert test_wsgi_app.success() @with_setup(add_http_intercept, remove_intercept) def test_http_default_port(): urllib2_intercept.install_opener() urllib2.urlopen('http://some_hopefully_nonexistant_domain/') assert test_wsgi_app.success() @with_setup(add_https_intercept, remove_intercept) def test_https(): urllib2_intercept.install_opener() urllib2.urlopen('https://some_hopefully_nonexistant_domain:443/') assert test_wsgi_app.success() @with_setup(add_https_intercept, remove_intercept) def test_https_default_port(): urllib2_intercept.install_opener() urllib2.urlopen('https://some_hopefully_nonexistant_domain/') assert test_wsgi_app.success()wsgi_intercept-0.5.1/wsgi_intercept/test/test_zope_testbrowser.py0000644000076500000240000000253411660467064026037 0ustar cdentstaff00000000000000 from nose.tools import with_setup, raises from urllib2 import URLError from wsgi_intercept.zope_testbrowser.wsgi_testbrowser import WSGI_Browser import wsgi_intercept from wsgi_intercept import test_wsgi_app _saved_debuglevel = None def add_intercept(port=80): # _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', port, test_wsgi_app.create_fn) def remove_intercept(): wsgi_intercept.remove_wsgi_intercept() # wsgi_intercept.debuglevel = _saved_debuglevel @with_setup(add_intercept, remove_intercept) def test_intercepted(): b = WSGI_Browser() b.open('http://some_hopefully_nonexistant_domain:80/') assert test_wsgi_app.success() @with_setup(add_intercept) @raises(URLError) def test_intercept_removed(): remove_intercept() b = WSGI_Browser() b.open('http://some_hopefully_nonexistant_domain:80/') @with_setup(lambda: add_intercept(443), remove_intercept) def test_https_intercepted(): b = WSGI_Browser() b.open('https://some_hopefully_nonexistant_domain/') assert test_wsgi_app.success() @with_setup(lambda: add_intercept(443), remove_intercept) def test_https_intercepted_443_port(): b = WSGI_Browser() b.open('https://some_hopefully_nonexistant_domain:443/') assert test_wsgi_app.success()wsgi_intercept-0.5.1/wsgi_intercept/test_wsgi_app.py0000644000076500000240000000077311660467065023255 0ustar cdentstaff00000000000000""" A simple WSGI application for testing. """ _app_was_hit = False def success(): return _app_was_hit def simple_app(environ, start_response): """Simplest possible application object""" status = '200 OK' response_headers = [('Content-type','text/plain')] start_response(status, response_headers) global _app_was_hit _app_was_hit = True return ['WSGI intercept successful!\n'] def create_fn(): global _app_was_hit _app_was_hit = False return simple_app wsgi_intercept-0.5.1/wsgi_intercept/urllib2_intercept/0000755000076500000240000000000011660472720023446 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept/urllib2_intercept/__init__.py0000644000076500000240000000021111660467064025556 0ustar cdentstaff00000000000000 """intercept http requests made using the urllib2 module. (see wsgi_intercept/__init__.py for examples) """ from wsgi_urllib2 import *wsgi_intercept-0.5.1/wsgi_intercept/urllib2_intercept/wsgi_urllib2.py0000644000076500000240000000353111660467064026433 0ustar cdentstaff00000000000000import sys from wsgi_intercept import WSGI_HTTPConnection import urllib2, httplib from urllib2 import HTTPHandler from httplib import HTTP # # ugh, version dependence. # if sys.version_info[:2] == (2, 3): class WSGI_HTTP(HTTP): _connection_class = WSGI_HTTPConnection class WSGI_HTTPHandler(HTTPHandler): """ Override the default HTTPHandler class with one that uses the WSGI_HTTPConnection class to open HTTP URLs. """ def http_open(self, req): return self.do_open(WSGI_HTTP, req) # I'm not implementing HTTPS for 2.3 until someone complains about it! -Kumar WSGI_HTTPSHandler = None else: class WSGI_HTTPHandler(HTTPHandler): """ Override the default HTTPHandler class with one that uses the WSGI_HTTPConnection class to open HTTP URLs. """ def http_open(self, req): return self.do_open(WSGI_HTTPConnection, req) if hasattr(httplib, 'HTTPS'): # urllib2 does this check as well, I assume it's to see if # python was compiled with SSL support from wsgi_intercept import WSGI_HTTPSConnection from urllib2 import HTTPSHandler class WSGI_HTTPSHandler(HTTPSHandler): """ Override the default HTTPSHandler class with one that uses the WSGI_HTTPConnection class to open HTTPS URLs. """ def https_open(self, req): return self.do_open(WSGI_HTTPSConnection, req) else: WSGI_HTTPSHandler = None def install_opener(): handlers = [WSGI_HTTPHandler()] if WSGI_HTTPSHandler is not None: handlers.append(WSGI_HTTPSHandler()) opener = urllib2.build_opener(*handlers) urllib2.install_opener(opener) return opener def uninstall_opener(): urllib2.install_opener(None) wsgi_intercept-0.5.1/wsgi_intercept/webtest_intercept/0000755000076500000240000000000011660472720023550 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept/webtest_intercept/__init__.py0000644000076500000240000000021311660467065025663 0ustar cdentstaff00000000000000"""work with an intercepted HTTP connection in a webtest.WebCase (see wsgi_intercept/__init__.py for examples) """ from webtest import *wsgi_intercept-0.5.1/wsgi_intercept/webtest_intercept/webtest.py0000644000076500000240000003351611660467065025615 0ustar cdentstaff00000000000000"""Extensions to unittest for web frameworks. Use the WebCase.getPage method to request a page from your HTTP server. Framework Integration ===================== If you have control over your server process, you can handle errors in the server-side of the HTTP conversation a bit better. You must run both the client (your WebCase tests) and the server in the same process (but in separate threads, obviously). When an error occurs in the framework, call server_error. It will print the traceback to stdout, and keep any assertions you have from running (the assumption is that, if the server errors, the page output won't be of further significance to your tests). """ import os, sys, time, re import types import pprint import socket import httplib import traceback from unittest import * from unittest import _TextTestResult class TerseTestResult(_TextTestResult): def printErrors(self): # Overridden to avoid unnecessary empty line if self.errors or self.failures: if self.dots or self.showAll: self.stream.writeln() self.printErrorList('ERROR', self.errors) self.printErrorList('FAIL', self.failures) class TerseTestRunner(TextTestRunner): """A test runner class that displays results in textual form.""" def _makeResult(self): return TerseTestResult(self.stream, self.descriptions, self.verbosity) def run(self, test): "Run the given test case or test suite." # Overridden to remove unnecessary empty lines and separators result = self._makeResult() startTime = time.time() test(result) timeTaken = float(time.time() - startTime) result.printErrors() if not result.wasSuccessful(): self.stream.write("FAILED (") failed, errored = map(len, (result.failures, result.errors)) if failed: self.stream.write("failures=%d" % failed) if errored: if failed: self.stream.write(", ") self.stream.write("errors=%d" % errored) self.stream.writeln(")") return result class ReloadingTestLoader(TestLoader): def loadTestsFromName(self, name, module=None): """Return a suite of all tests cases given a string specifier. The name may resolve either to a module, a test case class, a test method within a test case class, or a callable object which returns a TestCase or TestSuite instance. The method optionally resolves the names relative to a given module. """ parts = name.split('.') if module is None: if not parts: raise ValueError("incomplete test name: %s" % name) else: parts_copy = parts[:] while parts_copy: target = ".".join(parts_copy) if target in sys.modules: module = reload(sys.modules[target]) break else: try: module = __import__(target) break except ImportError: del parts_copy[-1] if not parts_copy: raise parts = parts[1:] obj = module for part in parts: obj = getattr(obj, part) if type(obj) == types.ModuleType: return self.loadTestsFromModule(obj) elif (isinstance(obj, (type, types.ClassType)) and issubclass(obj, TestCase)): return self.loadTestsFromTestCase(obj) elif type(obj) == types.UnboundMethodType: return obj.im_class(obj.__name__) elif callable(obj): test = obj() if not isinstance(test, TestCase) and \ not isinstance(test, TestSuite): raise ValueError("calling %s returned %s, " "not a test" % (obj,test)) return test else: raise ValueError("don't know how to make test from: %s" % obj) try: # On Windows, msvcrt.getch reads a single char without output. import msvcrt def getchar(): return msvcrt.getch() except ImportError: # Unix getchr import tty, termios def getchar(): fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setraw(sys.stdin.fileno()) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch class WebCase(TestCase): HOST = "127.0.0.1" PORT = 8000 HTTP_CONN=httplib.HTTPConnection def getPage(self, url, headers=None, method="GET", body=None): """Open the url with debugging support. Return status, headers, body.""" ServerError.on = False self.url = url result = openURL(url, headers, method, body, self.HOST, self.PORT, self.HTTP_CONN) self.status, self.headers, self.body = result # Build a list of request cookies from the previous response cookies. self.cookies = [('Cookie', v) for k, v in self.headers if k.lower() == 'set-cookie'] if ServerError.on: raise ServerError() return result interactive = True console_height = 30 def _handlewebError(self, msg): if not self.interactive: raise self.failureException(msg) print print " ERROR:", msg p = " Show: [B]ody [H]eaders [S]tatus [U]RL; [I]gnore, [R]aise, or sys.e[X]it >> " print p, while True: i = getchar().upper() if i not in "BHSUIRX": continue print i.upper() # Also prints new line if i == "B": for x, line in enumerate(self.body.splitlines()): if (x + 1) % self.console_height == 0: # The \r and comma should make the next line overwrite print "<-- More -->\r", m = getchar().lower() # Erase our "More" prompt print " \r", if m == "q": break print line elif i == "H": pprint.pprint(self.headers) elif i == "S": print self.status elif i == "U": print self.url elif i == "I": # return without raising the normal exception return elif i == "R": raise self.failureException(msg) elif i == "X": self.exit() print p, def exit(self): sys.exit() def __call__(self, result=None): if result is None: result = self.defaultTestResult() result.startTest(self) if hasattr(self, '_testMethodName'): # 2.5 + ? testMethod = getattr(self, self._testMethodName) elif hasattr(self, '_TestCase__testMethodName'): # 2.4 testMethod = getattr(self, self._TestCase__testMethodName) else: raise AttributeError("Not sure how to get the test method in %s" % self) try: try: self.setUp() except (KeyboardInterrupt, SystemExit): raise except: result.addError(self, self._TestCase__exc_info()) return ok = 0 try: testMethod() ok = 1 except self.failureException: result.addFailure(self, self._TestCase__exc_info()) except (KeyboardInterrupt, SystemExit): raise except: result.addError(self, self._TestCase__exc_info()) try: self.tearDown() except (KeyboardInterrupt, SystemExit): raise except: result.addError(self, self._TestCase__exc_info()) ok = 0 if ok: result.addSuccess(self) finally: result.stopTest(self) def assertStatus(self, status, msg=None): """Fail if self.status != status.""" if isinstance(status, basestring): if not self.status == status: if msg is None: msg = 'Status (%s) != %s' % (`self.status`, `status`) self._handlewebError(msg) else: if not self.status in status: if msg is None: msg = 'Status (%s) not in %s' % (`self.status`, `status`) self._handlewebError(msg) def assertHeader(self, key, value=None, msg=None): """Fail if (key, [value]) not in self.headers.""" lowkey = key.lower() for k, v in self.headers: if k.lower() == lowkey: if value is None or str(value) == v: return if msg is None: if value is None: msg = '%s not in headers' % `key` else: msg = '%s:%s not in headers' % (`key`, `value`) self._handlewebError(msg) def assertNoHeader(self, key, msg=None): """Fail if key in self.headers.""" lowkey = key.lower() matches = [k for k, v in self.headers if k.lower() == lowkey] if matches: if msg is None: msg = '%s in headers' % `key` self._handlewebError(msg) def assertBody(self, value, msg=None): """Fail if value != self.body.""" if value != self.body: if msg is None: msg = 'expected body:\n%s\n\nactual body:\n%s' % (`value`, `self.body`) self._handlewebError(msg) def assertInBody(self, value, msg=None): """Fail if value not in self.body.""" if value not in self.body: if msg is None: msg = '%s not in body' % `value` self._handlewebError(msg) def assertNotInBody(self, value, msg=None): """Fail if value in self.body.""" if value in self.body: if msg is None: msg = '%s found in body' % `value` self._handlewebError(msg) def assertMatchesBody(self, pattern, msg=None, flags=0): """Fail if value (a regex pattern) is not in self.body.""" if re.search(pattern, self.body, flags) is None: if msg is None: msg = 'No match for %s in body' % `pattern` self._handlewebError(msg) def cleanHeaders(headers, method, body, host, port): """Return request headers, with required headers added (if missing).""" if headers is None: headers = [] # Add the required Host request header if not present. # [This specifies the host:port of the server, not the client.] found = False for k, v in headers: if k.lower() == 'host': found = True break if not found: headers.append(("Host", "%s:%s" % (host, port))) if method in ("POST", "PUT"): # Stick in default type and length headers if not present found = False for k, v in headers: if k.lower() == 'content-type': found = True break if not found: headers.append(("Content-Type", "application/x-www-form-urlencoded")) headers.append(("Content-Length", str(len(body or "")))) return headers def openURL(url, headers=None, method="GET", body=None, host="127.0.0.1", port=8000, http_conn=httplib.HTTPConnection): """Open the given HTTP resource and return status, headers, and body.""" headers = cleanHeaders(headers, method, body, host, port) # Trying 10 times is simply in case of socket errors. # Normal case--it should run once. trial = 0 while trial < 10: try: conn = http_conn(host, port) conn.putrequest(method.upper(), url) for key, value in headers: conn.putheader(key, value) conn.endheaders() if body is not None: conn.send(body) # Handle response response = conn.getresponse() status = "%s %s" % (response.status, response.reason) outheaders = [] for line in response.msg.headers: key, value = line.split(":", 1) outheaders.append((key.strip(), value.strip())) outbody = response.read() conn.close() return status, outheaders, outbody except socket.error: trial += 1 if trial >= 10: raise else: time.sleep(0.5) # Add any exceptions which your web framework handles # normally (that you don't want server_error to trap). ignored_exceptions = [] # You'll want set this to True when you can't guarantee # that each response will immediately follow each request; # for example, when handling requests via multiple threads. ignore_all = False class ServerError(Exception): on = False def server_error(exc=None): """Server debug hook. Return True if exception handled, False if ignored. You probably want to wrap this, so you can still handle an error using your framework when it's ignored. """ if exc is None: exc = sys.exc_info() if ignore_all or exc[0] in ignored_exceptions: return False else: ServerError.on = True print print "".join(traceback.format_exception(*exc)) return True wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/0000755000076500000240000000000011660472720023550 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/__init__.py0000644000076500000240000000027011660467064025665 0ustar cdentstaff00000000000000 """work intercepted HTTP connectsion in a webunit test case. (see wsgi_intercept/__init__.py for examples) """ from webunittest import WebTestCase __version__ = '1.3.8' wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/config.py0000644000076500000240000000144411660467064025377 0ustar cdentstaff00000000000000""" This file allows you to set up configuration variables to identify the machine and port to test. It needs some work, but in a nutshell, put a config.cfg in your "test" directory with the following contents:: [DEFAULT] machine = www.dev.ekorp.com port = 80 [dev-ekit] # uses DEFAULT [dev-lp] machine = www.lonelyplanet.dev.ekorp.com port = 80 Then set the environment var "TEST_CONFIG" to the config to use. """ import os if os.path.exists('test/config.cfg'): import ConfigParser cfg = ConfigParser.ConfigParser() cfg.read('test/config.cfg') # figure the active config active = os.environ.get('TEST_CONFIG', 'DEFAULT') # fetch the actual config info machine = cfg.get(active, 'machine') port = cfg.getint(active, 'port') wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/cookie.py0000644000076500000240000000712011660467064025400 0ustar cdentstaff00000000000000import re, urlparse, Cookie class Error: '''Handles a specific cookie error. message - a specific message as to why the cookie is erroneous ''' def __init__(self, message): self.message = str(message) def __str__(self): return 'COOKIE ERROR: %s'%self.message def parse_cookie(text, qparmre=re.compile( r'([\0- ]*([^\0- ;,=\"]+)="([^"]*)\"([\0- ]*[;,])?[\0- ]*)'), parmre=re.compile( r'([\0- ]*([^\0- ;,=\"]+)=([^\0- ;,\"]*)([\0- ]*[;,])?[\0- ]*)')): result = {} l = 0 while 1: if qparmre.match(text[l:]) >= 0: # Match quoted correct cookies name=qparmre.group(2) value=qparmre.group(3) l=len(qparmre.group(1)) elif parmre.match(text[l:]) >= 0: # Match evil MSIE cookies ;) name=parmre.group(2) value=parmre.group(3) l=len(parmre.group(1)) else: # this may be an invalid cookie. # We'll simply bail without raising an error # if the cookie is invalid. return result if not result.has_key(name): result[name]=value return result def decodeCookies(url, server, headers, cookies): '''Decode cookies into the supplied cookies dictionary http://www.ietf.org/rfc/rfc2109.txt ''' # the path of the request URL up to, but not including, the right-most / request_path = urlparse.urlparse(url)[2] if len(request_path) > 1 and request_path[-1] == '/': request_path = request_path[:-1] hdrcookies = Cookie.SimpleCookie("\n".join(map(lambda x: x.strip(), headers.getallmatchingheaders('set-cookie')))) for cookie in hdrcookies.values(): # XXX: there doesn't seem to be a way to determine if the # cookie was set or defaulted to an empty string :( if cookie['domain']: domain = cookie['domain'] # reject if The value for the Domain attribute contains no # embedded dots or does not start with a dot. if '.' not in domain: raise Error, 'Cookie domain "%s" has no "."'%domain if domain[0] != '.': raise Error, 'Cookie domain "%s" doesn\'t start '\ 'with "."'%domain # reject if The value for the request-host does not # domain-match the Domain attribute. if not server.endswith(domain): raise Error, 'Cookie domain "%s" doesn\'t match '\ 'request host "%s"'%(domain, server) # reject if The request-host is a FQDN (not IP address) and # has the form HD, where D is the value of the Domain # attribute, and H is a string that contains one or more dots. if re.search(r'[a-zA-Z]', server): H = server[:-len(domain)] if '.' in H: raise Error, 'Cookie domain "%s" too short '\ 'for request host "%s"'%(domain, server) else: domain = server # path check path = cookie['path'] or request_path # reject if Path attribute is not a prefix of the request-URI # (noting that empty request path and '/' are often synonymous, yay) if not (request_path.startswith(path) or (request_path == '' and cookie['path'] == '/')): raise Error, 'Cookie path "%s" doesn\'t match '\ 'request url "%s"'%(path, request_path) bydom = cookies.setdefault(domain, {}) bypath = bydom.setdefault(path, {}) bypath[cookie.key] = cookie wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/HTMLParser.py0000644000076500000240000003655411660467064026065 0ustar cdentstaff00000000000000"""A parser for HTML.""" # This file is derived from sgmllib.py, which is part of Python. # XXX There should be a way to distinguish between PCDATA (parsed # character data -- the normal case), RCDATA (replaceable character # data -- only char and entity references and end tags are special) # and CDATA (character data -- only end tags are special). import re import string # Regular expressions used for parsing interesting_normal = re.compile('[&<]') interesting_cdata = re.compile(r'<(/|\Z)') incomplete = re.compile('(&[a-zA-Z][-.a-zA-Z0-9]*|&#[0-9]*)') entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]') charref = re.compile('&#([0-9]+)[^0-9]') starttagopen = re.compile('<[a-zA-Z]') piopen = re.compile(r'<\?') piclose = re.compile('>') endtagopen = re.compile(']*>') commentopen = re.compile('" % data) def handle_decl(self, data): self.emitText("" % data) def handle_pi(self, data): self.emitText("" % data) def emitStartTag(self, name, attrlist, isend=0): if isend: if self.__debug: print '*** content' self.content.append(SimpleDOMNode(name, attrlist, [])) else: # generate a new scope and push the current one on the stack if self.__debug: print '*** push' newcontent = [] self.stack.append(self.content) self.content.append(SimpleDOMNode(name, attrlist, newcontent)) self.content = newcontent def emitEndTag(self, name): if self.__debug: print '*** pop' self.content = self.stack.pop() def emitText(self, text): self.content.append(text) def emitStartElement(self, name, attrlist, isend=0): # Handle the simple, common case self.emitStartTag(name, attrlist, isend) if isend: self.emitEndElement(name, isend) def emitEndElement(self, name, isend=0, implied=0): if not isend or implied: self.emitEndTag(name) if __name__ == '__main__': tester = SimpleDOMParser(debug=0) tester.parseFile('/tmp/test.html') dom = tester.getDOM() # html = dom.getByNameFlat('html')[0] # body = html.getByNameFlat('body')[0] # table = body.getByNameFlat('table')[0] # tr = table.getByNameFlat('tr')[1] # td = tr.getByNameFlat('td')[2] # print td import pprint;pprint.pprint(dom) wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/utility.py0000644000076500000240000000517211660467064025637 0ustar cdentstaff00000000000000# # Copyright (c) 2003 Richard Jones (http://mechanicalcat.net/richard) # Copyright (c) 2002 ekit.com Inc (http://www.ekit-inc.com/) # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) # # See the README for full license details. # # $Id: utility.py,v 1.3 2003/08/23 02:01:59 richard Exp $ import cStringIO import os.path class Upload: '''Simple "sentinel" class that lets us identify file uploads in POST data mappings. ''' def __init__(self, filename): self.filename = filename def __cmp__(self, other): return cmp(self.filename, other.filename) boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' sep_boundary = '\n--' + boundary end_boundary = sep_boundary + '--' def mimeEncode(data, sep_boundary=sep_boundary, end_boundary=end_boundary): '''Take the mapping of data and construct the body of a multipart/form-data message with it using the indicated boundaries. ''' ret = cStringIO.StringIO() for key, value in data.items(): if not key: continue # handle multiple entries for the same name if type(value) != type([]): value = [value] for value in value: ret.write(sep_boundary) # if key starts with a '$' then the entry is a file upload if isinstance(value, Upload): ret.write('\nContent-Disposition: form-data; name="%s"'%key) ret.write('; filename="%s"\n\n'%value.filename) if value.filename: value = open(os.path.join(value.filename), "rb").read() else: value = '' else: ret.write('\nContent-Disposition: form-data; name="%s"'%key) ret.write("\n\n") ret.write(str(value)) if value and value[-1] == '\r': ret.write('\n') # write an extra newline ret.write(end_boundary) return ret.getvalue() def log(message, content, logfile='logfile'): '''Log a single message to the indicated logfile ''' logfile = open(logfile, 'a') logfile.write('\n>>> %s\n'%message) logfile.write(str(content) + '\n') logfile.close() # # $Log: utility.py,v $ # Revision 1.3 2003/08/23 02:01:59 richard # fixes to cookie sending # # Revision 1.2 2003/07/22 01:19:22 richard # patches # # Revision 1.1.1.1 2003/07/22 01:01:44 richard # # # Revision 1.4 2002/02/25 02:59:09 rjones # *** empty log message *** # # Revision 1.3 2002/02/22 06:24:31 rjones # Code cleanup # # Revision 1.2 2002/02/13 01:16:56 rjones # *** empty log message *** # # # vim: set filetype=python ts=4 sw=4 et si wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/webunittest.py0000644000076500000240000006247411660467064026521 0ustar cdentstaff00000000000000# # Copyright (c) 2003 Richard Jones (http://mechanicalcat.net/richard) # Copyright (c) 2002 ekit.com Inc (http://www.ekit-inc.com/) # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) # # See the README for full license details. # # $Id: webunittest.py,v 1.12 2004/01/21 22:41:46 richard Exp $ import os, base64, urllib, urlparse, unittest, cStringIO, time, re, sys import httplib #try: # from M2Crypto import httpslib #except ImportError: # httpslib = None from SimpleDOM import SimpleDOMParser from IMGSucker import IMGSucker from utility import Upload, mimeEncode, boundary, log import cookie VERBOSE = os.environ.get('VERBOSE', '') class HTTPError: '''Wraps a HTTP response that is not 200. url - the URL that generated the error code, message, headers - the information returned by httplib.HTTP.getreply() ''' def __init__(self, response): self.response = response def __str__(self): return 'ERROR: %s'%str(self.response) class WebFetcher: '''Provide a "web client" class that handles fetching web pages. Handles basic authentication, HTTPS, detection of error content, ... Creates a HTTPResponse object on a valid response. Stores cookies received from the server. ''' scheme_handlers = dict(http = httplib.HTTP, https = httplib.HTTPS) def __init__(self): '''Initialise the server, port, authinfo, images and error_content attributes. ''' self.protocol = 'http' self.server = '127.0.0.1' self.port = 80 self.authinfo = '' self.url = None self.images = {} self.error_content = [] self.expect_codes = [200, 301, 302] self.expect_content = None self.expect_cookies = None self.accept_cookies = 1 self.cookies = {} result_count = 0 def clearContext(self): self.authinfo = '' self.cookies = {} self.url = None self.images = {} def setServer(self, server, port): '''Set the server and port number to perform the HTTP requests to. ''' self.server = server self.port = int(port) # # Authentication # def clearBasicAuth(self): '''Clear the current Basic authentication information ''' self.authinfo = '' def setBasicAuth(self, username, password): '''Set the Basic authentication information to the given username and password. ''' self.authinfo = base64.encodestring('%s:%s'%(username, password)).strip() # # cookie handling # def clearCookies(self): '''Clear all currently received cookies ''' self.cookies = {} def setAcceptCookies(self, accept=1): '''Indicate whether to accept cookies or not ''' self.accept_cookies = accept def registerErrorContent(self, content): '''Register the given string as content that should be considered a test failure (even though the response code is 200). ''' self.error_content.append(content) def removeErrorContent(self, content): '''Remove the given string from the error content list. ''' self.error_content.remove(content) def clearErrorContent(self): '''Clear the current list of error content strings. ''' self.error_content = [] def log(self, message, content): '''Log a message to the logfile ''' log(message, content, 'logfile.'+self.server) # # Register cookies we expect to send to the server # def registerExpectedCookie(self, cookie): '''Register a cookie name that we expect to send to the server. ''' if self.expect_cookies is None: self.expect_cookies = [cookie] return self.expect_cookies.append(cookie) self.expect_cookies.sort() def removeExpectedCookie(self, cookie): '''Remove the given cookie from the list of cookies we expect to send to the server. ''' self.expect_cookies.remove(cookie) def clearExpectedCookies(self): '''Clear the current list of cookies we expect to send to the server. ''' self.expect_cookies = None # # POST # def post(self, url, params, code=None, **kw): '''Perform a HTTP POST using the specified URL and form parameters. ''' if code is None: code = self.expect_codes WebTestCase.result_count = WebTestCase.result_count + 1 try: response = self.fetch(url, params, ok_codes=code, **kw) except HTTPError, error: self.log('post'+`(url, params)`, error.response.body) raise self.failureException, str(error.response) return response def postAssertCode(self, url, params, code=None, **kw): '''Perform a HTTP POST and assert that the return code from the server is one of the indicated codes. ''' if code is None: code = self.expect_codes WebTestCase.result_count = WebTestCase.result_count + 1 if type(code) != type([]): code = [code] try: response = self.fetch(url, params, ok_codes = code, **kw) except HTTPError, error: self.log('postAssertCode'+`(url, params, code)`, error.response.body) raise self.failureException, str(error.response) return response def postAssertContent(self, url, params, content, code=None, **kw): '''Perform a HTTP POST and assert that the data returned from the server contains the indicated content string. ''' if code is None: code = self.expect_codes WebTestCase.result_count = WebTestCase.result_count + 1 if type(code) != type([]): code = [code] try: response = self.fetch(url, params, ok_codes = code, **kw) except HTTPError, error: self.log('postAssertContent'+`(url, params, code)`, error.response.body) raise self.failureException, str(error) if response.body.find(content) == -1: self.log('postAssertContent'+`(url, params, content)`, response.body) raise self.failureException, 'Expected content not in response' return response def postAssertNotContent(self, url, params, content, code=None, **kw): '''Perform a HTTP POST and assert that the data returned from the server doesn't contain the indicated content string. ''' if code is None: code = self.expect_codes WebTestCase.result_count = WebTestCase.result_count + 1 if type(code) != type([]): code = [code] try: response = self.fetch(url, params, ok_codes = code, **kw) except HTTPError, error: self.log('postAssertNotContent'+`(url, params, code)`, error.response.body) raise self.failureException, str(error) if response.body.find(content) != -1: self.log('postAssertNotContent'+`(url, params, content)`, response.body) raise self.failureException, 'Expected content was in response' return response def postPage(self, url, params, code=None, **kw): '''Perform a HTTP POST using the specified URL and form parameters and then retrieve all image and linked stylesheet components for the resulting HTML page. ''' if code is None: code = self.expect_codes WebTestCase.result_count = WebTestCase.result_count + 1 try: response = self.fetch(url, params, ok_codes=code, **kw) except HTTPError, error: self.log('postPage %r'%((url, params),), error.response.body) raise self.failureException, str(error) # Check return code for redirect while response.code in (301, 302): try: # Figure the location - which may be relative newurl = response.headers['Location'] url = urlparse.urljoin(url, newurl) response = self.fetch(url, ok_codes=code) except HTTPError, error: self.log('postPage %r'%url, error.response.body) raise self.failureException, str(error) # read and parse the content page = response.body if hasattr(self, 'results') and self.results: self.writeResult(url, page) try: self.pageImages(url, page) except HTTPError, error: raise self.failureException, str(error) return response # # GET # def assertCode(self, url, code=None, **kw): '''Perform a HTTP GET and assert that the return code from the server one of the indicated codes. ''' if code is None: code = self.expect_codes return self.postAssertCode(url, None, code=code, **kw) get = getAssertCode = assertCode def assertContent(self, url, content, code=None, **kw): '''Perform a HTTP GET and assert that the data returned from the server contains the indicated content string. ''' if code is None: code = self.expect_codes return self.postAssertContent(url, None, content, code) getAssertContent = assertContent def assertNotContent(self, url, content, code=None, **kw): '''Perform a HTTP GET and assert that the data returned from the server contains the indicated content string. ''' if code is None: code = self.expect_codes return self.postAssertNotContent(url, None, content, code) getAssertNotContent = assertNotContent def page(self, url, code=None, **kw): '''Perform a HTTP GET using the specified URL and then retrieve all image and linked stylesheet components for the resulting HTML page. ''' if code is None: code = self.expect_codes WebTestCase.result_count = WebTestCase.result_count + 1 return self.postPage(url, None, code=code, **kw) def get_base_url(self): # try to get a tag and use that to root the URL on if hasattr(self, 'getDOM'): base = self.getDOM().getByName('base') if base: # return base[0].href if self.url is not None: # join the request URL with the "current" URL return self.url return None # # The function that does it all # def fetch(self, url, postdata=None, server=None, port=None, protocol=None, ok_codes=None): '''Run a single test request to the indicated url. Use the POST data if supplied. Raises failureException if the returned data contains any of the strings indicated to be Error Content. Returns a HTTPReponse object wrapping the response from the server. ''' # see if the url is fully-qualified (not just a path) t_protocol, t_server, t_url, x, t_args, x = urlparse.urlparse(url) if t_server: protocol = t_protocol if ':' in t_server: server, port = t_server.split(':') else: server = t_server if protocol == 'http': port = '80' else: port = '443' url = t_url if t_args: url = url + '?' + t_args # ignore the machine name if the URL is for localhost if t_server == 'localhost': server = None elif not server: # no server was specified with this fetch, or in the URL, so # see if there's a base URL to use. base = self.get_base_url() if base: t_protocol, t_server, t_url, x, x, x = urlparse.urlparse(base) if t_protocol: protocol = t_protocol if t_server: server = t_server if t_url: url = urlparse.urljoin(t_url, url) # TODO: allow override of the server and port from the URL! if server is None: server = self.server if port is None: port = self.port if protocol is None: protocol = self.protocol if ok_codes is None: ok_codes = self.expect_codes if protocol == 'http': handler = self.scheme_handlers.get('http') h = handler(server, int(port)) if int(port) == 80: host_header = server else: host_header = '%s:%s'%(server, port) elif protocol == 'https': #if httpslib is None: #raise ValueError, "Can't fetch HTTPS: M2Crypto not installed" handler = self.scheme_handlers.get('https') h = handler(server, int(port)) if int(port) == 443: host_header = server else: host_header = '%s:%s'%(server, port) else: raise ValueError, protocol params = None if postdata: for field,value in postdata.items(): if type(value) == type({}): postdata[field] = [] for k,selected in value.items(): if selected: postdata[field].append(k) # Do a post with the data file params = mimeEncode(postdata) h.putrequest('POST', url) h.putheader('Content-type', 'multipart/form-data; boundary=%s'% boundary) h.putheader('Content-length', str(len(params))) else: # Normal GET h.putrequest('GET', url) # Other Full Request headers if self.authinfo: h.putheader('Authorization', "Basic %s"%self.authinfo) h.putheader('Host', host_header) # Send cookies # - check the domain, max-age (seconds), path and secure # (http://www.ietf.org/rfc/rfc2109.txt) cookies_used = [] cookie_list = [] for domain, cookies in self.cookies.items(): # check cookie domain if not server.endswith(domain): continue for path, cookies in cookies.items(): # check that the path matches urlpath = urlparse.urlparse(url)[2] if not urlpath.startswith(path) and not (path == '/' and urlpath == ''): continue for sendcookie in cookies.values(): # and that the cookie is or isn't secure if sendcookie['secure'] and protocol != 'https': continue # TODO: check max-age cookie_list.append("%s=%s;"%(sendcookie.key, sendcookie.coded_value)) cookies_used.append(sendcookie.key) if cookie_list: h.putheader('Cookie', ' '.join(cookie_list)) # check that we sent the cookies we expected to if self.expect_cookies is not None: assert cookies_used == self.expect_cookies, \ "Didn't use all cookies (%s expected, %s used)"%( self.expect_cookies, cookies_used) # finish the headers h.endheaders() if params is not None: h.send(params) # handle the reply errcode, errmsg, headers = h.getreply() # get the body and save it f = h.getfile() g = cStringIO.StringIO() d = f.read() while d: g.write(d) d = f.read() response = HTTPResponse(self.cookies, protocol, server, port, url, errcode, errmsg, headers, g.getvalue(), self.error_content) f.close() if errcode not in ok_codes: if VERBOSE: sys.stdout.write('e') sys.stdout.flush() raise HTTPError(response) # decode the cookies if self.accept_cookies: try: # decode the cookies and update the cookies store cookie.decodeCookies(url, server, headers, self.cookies) except: if VERBOSE: sys.stdout.write('c') sys.stdout.flush() raise # Check errors if self.error_content: data = response.body for content in self.error_content: if data.find(content) != -1: msg = "Matched error: %s"%content if hasattr(self, 'results') and self.results: self.writeError(url, msg) self.log('Matched error'+`(url, content)`, data) if VERBOSE: sys.stdout.write('c') sys.stdout.flush() raise self.failureException, msg if VERBOSE: sys.stdout.write('_') sys.stdout.flush() return response def pageImages(self, url, page): '''Given the HTML page that was loaded from url, grab all the images. ''' sucker = IMGSucker(url, self) sucker.feed(page) sucker.close() class WebTestCase(WebFetcher, unittest.TestCase): '''Extend the standard unittest TestCase with some HTTP fetching and response testing functions. ''' def __init__(self, methodName='runTest'): '''Initialise the server, port, authinfo, images and error_content attributes. ''' unittest.TestCase.__init__(self, methodName=methodName) WebFetcher.__init__(self) class HTTPResponse(WebFetcher, unittest.TestCase): '''Wraps a HTTP response. protocol, server, port, url - the request server and URL code, message, headers - the information returned by httplib.HTTP.getreply() body - the response body returned by httplib.HTTP.getfile() ''' def __init__(self, cookies, protocol, server, port, url, code, message, headers, body, error_content=[]): WebFetcher.__init__(self) # single cookie store per test self.cookies = cookies self.error_content = error_content[:] # this is the request that generated this response self.protocol = protocol self.server = server self.port = port self.url = url # info about the response self.code = code self.message = message self.headers = headers self.body = body self.dom = None def __str__(self): return '%s\nHTTP Response %s: %s'%(self.url, self.code, self.message) def getDOM(self): '''Get a DOM for this page ''' if self.dom is None: parser = SimpleDOMParser() try: parser.parseString(self.body) except: log('HTTPResponse.getDOM'+`(self.url, self.code, self.message, self.headers)`, self.body) raise self.dom = parser.getDOM() return self.dom def extractForm(self, path=[], include_submit=0, include_button=0): '''Extract a form (as a dictionary) from this page. The "path" is a list of 2-tuples ('element name', index) to follow to find the form. So: ..

...

...

To extract the second form, any of these could be used: [('html',0), ('body',0), ('p',1), ('form',0)] [('form',1)] [('p',1)] ''' return self.getDOM().extractElements(path, include_submit, include_button) def getForm(self, formnum, getmethod, postargs, *args): '''Given this page, extract the "formnum"th form from it, fill the form with the "postargs" and post back to the server using the "postmethod" with additional "args". NOTE: the form submission will include any "default" values from the form extracted from this page. To "remove" a value from the form, just pass a value None for the elementn and it will be removed from the form submission. example WebTestCase: page = self.get('/foo') page.getForm(0, self.post, {'name': 'blahblah', 'password': 'foo'}) or the slightly more complex: page = self.get('/foo') page.getForm(0, self.assertContent, {'name': 'blahblah', 'password': None}, 'password incorrect') ''' formData, url = self.getFormData(formnum, postargs) # whack on the url params l = [] for k, v in formData.items(): if isinstance(v, type([])): for item in v: l.append('%s=%s'%(urllib.quote(k), urllib.quote_plus(item, safe=''))) else: l.append('%s=%s'%(urllib.quote(k), urllib.quote_plus(v, safe=''))) if l: url = url + '?' + '&'.join(l) # make the post return getmethod(url, *args) def postForm(self, formnum, postmethod, postargs, *args): '''Given this page, extract the "formnum"th form from it, fill the form with the "postargs" and post back to the server using the "postmethod" with additional "args". NOTE: the form submission will include any "default" values from the form extracted from this page. To "remove" a value from the form, just pass a value None for the elementn and it will be removed from the form submission. example WebTestCase: page = self.get('/foo') page.postForm(0, self.post, {'name': 'blahblah', 'password': 'foo'}) or the slightly more complex: page = self.get('/foo') page.postForm(0, self.postAssertContent, {'name': 'blahblah', 'password': None}, 'password incorrect') ''' formData, url = self.getFormData(formnum, postargs) # make the post return postmethod(url, formData, *args) def getFormData(self, formnum, postargs={}): ''' Postargs are in the same format as the data returned by the SimpleDOM extractElements() method, and they are merged with the existing form data. ''' dom = self.getDOM() form = dom.getByName('form')[formnum] formData = form.extractElements() # Make sure all the postargs are present in the form: # TODO this test needs to be switchable, as it barfs when you explicitly # identify a submit button in the form - the existing form data doesn't # have submit buttons in it # for k in postargs.keys(): # assert formData.has_key(k), (formData, k) formData.update(postargs) for k,v in postargs.items(): if v is None: del formData[k] # transmogrify select/checkbox/radio select options from dicts # (key:'selected') to lists of values for k,v in formData.items(): if isinstance(v, type({})): l = [] for kk,vv in v.items(): if vv in ('selected', 'checked'): l.append(kk) formData[k] = l if form.hasattr('action'): url = form.action base = self.get_base_url() if not url or url == '.': if base and base[0].hasattr('href'): url = base[0].href elif self.url.endswith('/'): url = self.url elif self.url.startswith('http') or self.url.startswith('/'): url = '%s/' % '/'.join(self.url.split('/')[:-1]) else: url = '/%s/' % '/'.join(self.url.split('/')[:-1]) elif not (url.startswith('/') or url.startswith('http')): url = urlparse.urljoin(base, url) else: url = self.url return formData, url # # $Log: webunittest.py,v $ # Revision 1.12 2004/01/21 22:41:46 richard # *** empty log message *** # # Revision 1.11 2004/01/20 23:59:39 richard # *** empty log message *** # # Revision 1.10 2003/11/06 06:50:29 richard # *** empty log message *** # # Revision 1.9 2003/11/03 05:11:17 richard # *** empty log message *** # # Revision 1.5 2003/10/08 05:37:32 richard # fixes # # Revision 1.4 2003/08/23 02:01:59 richard # fixes to cookie sending # # Revision 1.3 2003/08/22 00:46:29 richard # much fixes # # Revision 1.2 2003/07/22 01:19:22 richard # patches # # Revision 1.1.1.1 2003/07/22 01:01:44 richard # # # Revision 1.11 2002/02/27 03:00:08 rjones # more tests, bugfixes # # Revision 1.10 2002/02/26 03:14:41 rjones # more tests # # Revision 1.9 2002/02/25 02:58:47 rjones # *** empty log message *** # # Revision 1.8 2002/02/22 06:24:31 rjones # Code cleanup # # Revision 1.7 2002/02/22 04:15:34 rjones # web test goodness # # Revision 1.6 2002/02/13 04:32:50 rjones # *** empty log message *** # # Revision 1.5 2002/02/13 04:24:42 rjones # *** empty log message *** # # Revision 1.4 2002/02/13 02:21:59 rjones # *** empty log message *** # # Revision 1.3 2002/02/13 01:48:23 rjones # *** empty log message *** # # Revision 1.2 2002/02/13 01:16:56 rjones # *** empty log message *** # # # vim: set filetype=python ts=4 sw=4 et si wsgi_intercept-0.5.1/wsgi_intercept/zope_testbrowser/0000755000076500000240000000000011660472720023436 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept/zope_testbrowser/__init__.py0000644000076500000240000000023311660467064025552 0ustar cdentstaff00000000000000"""intercepting HTTP connections made using zope.testbrowser. (see wsgi_intercept/__init__.py for examples) """ from wsgi_testbrowser import WSGI_Browserwsgi_intercept-0.5.1/wsgi_intercept/zope_testbrowser/wsgi_testbrowser.py0000644000076500000240000000123711660467064027434 0ustar cdentstaff00000000000000""" A zope.testbrowser-style Web browser interface that redirects specified connections to a WSGI application. """ from mechanize import Browser as MechanizeBrowser from wsgi_intercept.mechanize_intercept import Browser as InterceptBrowser from zope.testbrowser.browser import Browser as ZopeTestbrowser from httplib import HTTP import sys, os.path class WSGI_Browser(ZopeTestbrowser): """ Override the zope.testbrowser.browser.Browser interface so that it uses PatchedMechanizeBrowser """ def __init__(self, *args, **kwargs): kwargs['mech_browser'] = InterceptBrowser() ZopeTestbrowser.__init__(self, *args, **kwargs) wsgi_intercept-0.5.1/wsgi_intercept.egg-info/0000755000076500000240000000000011660472720021510 5ustar cdentstaff00000000000000wsgi_intercept-0.5.1/wsgi_intercept.egg-info/dependency_links.txt0000644000076500000240000000000111660472720025556 0ustar cdentstaff00000000000000 wsgi_intercept-0.5.1/wsgi_intercept.egg-info/PKG-INFO0000644000076500000240000002212711660472720022611 0ustar cdentstaff00000000000000Metadata-Version: 1.0 Name: wsgi-intercept Version: 0.5.1 Summary: installs a WSGI application in place of a real URI for testing. Home-page: http://code.google.com/p/wsgi-intercept/ Author: Titus Brown, Kumar McMillan Author-email: kumar.mcmillan@gmail.com License: MIT License Description: Introduction ============ Testing a WSGI application normally involves starting a server at a local host and port, then pointing your test code to that address. Instead, this library lets you intercept calls to any specific host/port combination and redirect them into a `WSGI application`_ importable by your test program. Thus, you can avoid spawning multiple processes or threads to test your Web app. How Does It Work? ================= ``wsgi_intercept`` works by replacing ``httplib.HTTPConnection`` with a subclass, ``wsgi_intercept.WSGI_HTTPConnection``. This class then redirects specific server/port combinations into a WSGI application by emulating a socket. If no intercept is registered for the host and port requested, those requests are passed on to the standard handler. The functions ``add_wsgi_intercept(host, port, app_create_fn, script_name='')`` and ``remove_wsgi_intercept(host,port)`` specify which URLs should be redirect into what applications. Note especially that ``app_create_fn`` is a *function object* returning a WSGI application; ``script_name`` becomes ``SCRIPT_NAME`` in the WSGI app's environment, if set. Install ======= :: easy_install wsgi_intercept (The ``easy_install`` command is bundled with the setuptools_ module) To use a `development version`_ of wsgi_intercept, run:: easy_install http://wsgi-intercept.googlecode.com/svn/trunk .. _setuptools: http://cheeseshop.python.org/pypi/setuptools/ .. _development version: http://wsgi-intercept.googlecode.com/svn/trunk/#egg=wsgi_intercept-dev Packages Intercepted ==================== Unfortunately each of the Web testing frameworks uses its own specific mechanism for making HTTP call-outs, so individual implementations are needed. Below are the packages supported and how to create an intercept. urllib2 ------- urllib2_ is a standard Python module, and ``urllib2.urlopen`` is a pretty normal way to open URLs. The following code will install the WSGI intercept stuff as a default urllib2 handler: :: >>> from wsgi_intercept.urllib2_intercept import install_opener >>> install_opener() #doctest: +ELLIPSIS >>> import wsgi_intercept >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> import urllib2 >>> urllib2.urlopen('http://some_host:80/').read() 'WSGI intercept successful!\n' The only tricky bit in there is that different handler classes need to be constructed for Python 2.3 and Python 2.4, because the httplib interface changed between those versions. .. _urllib2: http://docs.python.org/lib/module-urllib2.html httplib2 -------- httplib2_ is a 3rd party extension of the built-in ``httplib``. To intercept requests, it is similar to urllib2:: >>> from wsgi_intercept.httplib2_intercept import install >>> install() >>> import wsgi_intercept >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> import httplib2 >>> resp, content = httplib2.Http().request('http://some_host:80/', 'GET') >>> content 'WSGI intercept successful!\n' (Contributed by `David "Whit" Morris`_.) .. _httplib2: http://code.google.com/p/httplib2/ .. _David "Whit" Morris: http://public.xdi.org/=whit webtest ------- webtest_ is an extension to ``unittest`` that has some nice functions for testing Web sites. To install the WSGI intercept handler, do :: >>> import wsgi_intercept.webtest_intercept >>> class WSGI_Test(wsgi_intercept.webtest_intercept.WebCase): ... HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection ... HOST='localhost' ... PORT=80 ... ... def setUp(self): ... wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT, create_fn) ... >>> .. _webtest: http://www.cherrypy.org/file/trunk/cherrypy/test/webtest.py webunit ------- webunit_ is another unittest-like framework that contains nice functions for Web testing. (funkload_ uses webunit, too.) webunit needed to be patched to support different scheme handlers. The patched package is in webunit/wsgi_webunit/, and the only file that was changed was webunittest.py; the original is in webunittest-orig.py. To install the WSGI intercept handler, do :: >>> from httplib import HTTP >>> import wsgi_intercept.webunit_intercept >>> class WSGI_HTTP(HTTP): ... _connection_class = wsgi_intercept.WSGI_HTTPConnection ... >>> class WSGI_WebTestCase(wsgi_intercept.webunit_intercept.WebTestCase): ... scheme_handlers = dict(http=WSGI_HTTP) ... ... def setUp(self): ... wsgi_intercept.add_wsgi_intercept('127.0.0.1', 80, create_fn) ... >>> .. _webunit: http://mechanicalcat.net/tech/webunit/ mechanize --------- mechanize_ is John J. Lee's port of Perl's WWW::Mechanize to Python. It mimics a browser. (It's also what's behind twill_.) >>> import wsgi_intercept.mechanize_intercept >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> b = wsgi_intercept.mechanize_intercept.Browser() >>> response = b.open('http://some_host:80') >>> response.read() 'WSGI intercept successful!\n' .. _mechanize: http://wwwsearch.sf.net/ zope.testbrowser ---------------- zope.testbrowser_ is a prettified interface to mechanize_ that is used primarily for testing Zope applications. zope.testbrowser is also pretty easy :: >>> import wsgi_intercept.zope_testbrowser >>> from wsgi_intercept.test_wsgi_app import create_fn >>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn) >>> b = wsgi_intercept.zope_testbrowser.WSGI_Browser('http://some_host:80/') >>> b.contents 'WSGI intercept successful!\n' .. _zope.testbrowser: http://www.python.org/pypi/zope.testbrowser History ======= Pursuant to Ian Bicking's `"best Web testing framework"`_ post, Titus Brown put together an `in-process HTTP-to-WSGI interception mechanism`_ for his own Web testing system, twill_. Because the mechanism is pretty generic -- it works at the httplib level -- Titus decided to try adding it into all of the *other* Python Web testing frameworks. This is the result. Mocking your HTTP Server ======================== Marc Hedlund has gone one further, and written a full-blown mock HTTP server for wsgi_intercept. Combined with wsgi_intercept itself, this lets you entirely replace client calls to a server with a mock setup that hits neither the network nor server code. You can see his work in the file ``mock_http.py``. Run ``mock_http.py`` to see a test. .. _twill: http://www.idyll.org/~t/www-tools/twill.html .. _"best Web testing framework": http://blog.ianbicking.org/best-of-the-web-app-test-frameworks.html .. _in-process HTTP-to-WSGI interception mechanism: http://www.advogato.org/person/titus/diary.html?start=119 .. _WSGI application: http://www.python.org/peps/pep-0333.html .. _funkload: http://funkload.nuxeo.org/ Project Home ============ If you aren't already there, this project lives on `Google Code`_. Please submit all bugs, patches, failing tests, et cetera using the `Issue Tracker`_ .. _Google Code: http://code.google.com/p/wsgi-intercept/ .. _Issue Tracker: http://code.google.com/p/wsgi-intercept/issues/list Platform: UNKNOWN wsgi_intercept-0.5.1/wsgi_intercept.egg-info/SOURCES.txt0000644000076500000240000000313511660472720023376 0ustar cdentstaff00000000000000AUTHORS.txt CHANGELOG.txt LICENSE.txt README.txt run-tests setup.cfg setup.py tox.ini docs/README.txt docs/index.rst wsgi_intercept/__init__.py wsgi_intercept/httplib2_intercept.py wsgi_intercept/httplib_intercept.py wsgi_intercept/mock_http.py wsgi_intercept/test_wsgi_app.py wsgi_intercept.egg-info/PKG-INFO wsgi_intercept.egg-info/SOURCES.txt wsgi_intercept.egg-info/dependency_links.txt wsgi_intercept.egg-info/top_level.txt wsgi_intercept/mechanize_intercept/__init__.py wsgi_intercept/mechanize_intercept/wsgi_browser.py wsgi_intercept/setup_cmd/__init__.py wsgi_intercept/setup_cmd/build_docs.py wsgi_intercept/setup_cmd/publish_docs.py wsgi_intercept/test/__init__.py wsgi_intercept/test/test_httplib.py wsgi_intercept/test/test_httplib2.py wsgi_intercept/test/test_mechanize.py wsgi_intercept/test/test_webtest.py wsgi_intercept/test/test_webunit.py wsgi_intercept/test/test_wsgi_compliance.py wsgi_intercept/test/test_wsgi_urllib2.py wsgi_intercept/test/test_zope_testbrowser.py wsgi_intercept/urllib2_intercept/__init__.py wsgi_intercept/urllib2_intercept/wsgi_urllib2.py wsgi_intercept/webtest_intercept/__init__.py wsgi_intercept/webtest_intercept/webtest.py wsgi_intercept/webunit_intercept/HTMLParser.py wsgi_intercept/webunit_intercept/IMGSucker.py wsgi_intercept/webunit_intercept/SimpleDOM.py wsgi_intercept/webunit_intercept/__init__.py wsgi_intercept/webunit_intercept/config.py wsgi_intercept/webunit_intercept/cookie.py wsgi_intercept/webunit_intercept/utility.py wsgi_intercept/webunit_intercept/webunittest.py wsgi_intercept/zope_testbrowser/__init__.py wsgi_intercept/zope_testbrowser/wsgi_testbrowser.pywsgi_intercept-0.5.1/wsgi_intercept.egg-info/top_level.txt0000644000076500000240000000001711660472720024240 0ustar cdentstaff00000000000000wsgi_intercept