summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorBernhard M. Wiedemann <bernhard+hgcommit@lsmod.de>2018-02-21 10:25:37 +0100
committerBernhard M. Wiedemann <bernhard+hgcommit@lsmod.de>2018-02-21 10:25:37 +0100
commitaca5f3fb0fbc754d2dd8e676dc640f1432a164bd (patch)
tree8fbeda74980baf7acd06c5b1f792a3c218eaa1ff /tests
downloadpaste-git-aca5f3fb0fbc754d2dd8e676dc640f1432a164bd.tar.gz
make tests pass after 2031
Diffstat (limited to 'tests')
-rw-r--r--tests/__init__.py7
-rwxr-xr-xtests/cgiapp_data/error.cgi3
-rwxr-xr-xtests/cgiapp_data/form.cgi69
-rwxr-xr-xtests/cgiapp_data/ok.cgi5
-rwxr-xr-xtests/cgiapp_data/stderr.cgi8
-rw-r--r--tests/test_auth/__init__.py0
-rw-r--r--tests/test_auth/test_auth_cookie.py46
-rw-r--r--tests/test_auth/test_auth_digest.py93
-rw-r--r--tests/test_cgiapp.py59
-rw-r--r--tests/test_cgitb_catcher.py78
-rw-r--r--tests/test_config.py85
-rw-r--r--tests/test_doctests.py63
-rw-r--r--tests/test_errordocument.py92
-rw-r--r--tests/test_exceptions/__init__.py1
-rw-r--r--tests/test_exceptions/test_error_middleware.py109
-rw-r--r--tests/test_exceptions/test_formatter.py183
-rw-r--r--tests/test_exceptions/test_httpexceptions.py97
-rw-r--r--tests/test_exceptions/test_reporter.py50
-rw-r--r--tests/test_fileapp.py242
-rw-r--r--tests/test_fixture.py28
-rw-r--r--tests/test_grantip.py37
-rw-r--r--tests/test_gzipper.py19
-rw-r--r--tests/test_httpheaders.py159
-rw-r--r--tests/test_httpserver.py45
-rw-r--r--tests/test_import_string.py16
-rw-r--r--tests/test_multidict.py162
-rw-r--r--tests/test_profilemiddleware.py29
-rw-r--r--tests/test_proxy.py12
-rw-r--r--tests/test_recursive.py105
-rw-r--r--tests/test_registry.py314
-rw-r--r--tests/test_request.py66
-rw-r--r--tests/test_request_form.py36
-rw-r--r--tests/test_response.py11
-rw-r--r--tests/test_session.py56
-rw-r--r--tests/test_template.txt136
-rw-r--r--tests/test_urlmap.py53
-rw-r--r--tests/test_urlparser.py178
-rw-r--r--tests/test_util/__init__.py0
-rw-r--r--tests/test_util/test_datetimeutil.py135
-rw-r--r--tests/test_util/test_mimeparse.py235
-rw-r--r--tests/test_util/test_quoting.py28
-rw-r--r--tests/test_wsgilib.py52
-rw-r--r--tests/test_wsgiwrappers.py146
-rw-r--r--tests/urlparser_data/__init__.py1
-rw-r--r--tests/urlparser_data/deep/index.html1
-rw-r--r--tests/urlparser_data/deep/sub/Main.txt1
-rw-r--r--tests/urlparser_data/find_file/dir with spaces/test 4.html1
-rw-r--r--tests/urlparser_data/find_file/index.txt1
-rw-r--r--tests/urlparser_data/find_file/test 3.html1
-rw-r--r--tests/urlparser_data/find_file/test2.html1
-rw-r--r--tests/urlparser_data/hook/__init__.py10
-rw-r--r--tests/urlparser_data/hook/app.py9
-rw-r--r--tests/urlparser_data/hook/index.py9
-rw-r--r--tests/urlparser_data/not_found/__init__.py1
-rw-r--r--tests/urlparser_data/not_found/recur/__init__.py9
-rw-r--r--tests/urlparser_data/not_found/recur/isfound.txt1
-rw-r--r--tests/urlparser_data/not_found/simple/__init__.py3
-rw-r--r--tests/urlparser_data/not_found/simple/found.txt1
-rw-r--r--tests/urlparser_data/not_found/user/__init__.py12
-rw-r--r--tests/urlparser_data/not_found/user/list.py8
-rw-r--r--tests/urlparser_data/python/__init__.py1
-rw-r--r--tests/urlparser_data/python/simpleapp.py5
-rw-r--r--tests/urlparser_data/python/stream.py7
-rw-r--r--tests/urlparser_data/python/sub/__init__.py1
-rw-r--r--tests/urlparser_data/python/sub/simpleapp.py4
-rw-r--r--tests/urlparser_data/secured.txt1
66 files changed, 3437 insertions, 0 deletions
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..3e2e36a
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,7 @@
+import sys
+import os
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
+
+import pkg_resources
+pkg_resources.require('Paste')
diff --git a/tests/cgiapp_data/error.cgi b/tests/cgiapp_data/error.cgi
new file mode 100755
index 0000000..e11c766
--- /dev/null
+++ b/tests/cgiapp_data/error.cgi
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+
+print('hey you!')
diff --git a/tests/cgiapp_data/form.cgi b/tests/cgiapp_data/form.cgi
new file mode 100755
index 0000000..c4c562d
--- /dev/null
+++ b/tests/cgiapp_data/form.cgi
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+
+import cgi
+import six
+
+print('Content-type: text/plain')
+print('')
+
+if six.PY3:
+ # Python 3: cgi.FieldStorage keeps some field names as unicode and some as
+ # the repr() of byte strings, duh.
+
+ class FieldStorage(cgi.FieldStorage):
+
+ def _key_candidates(self, key):
+ yield key
+
+ try:
+ # assume bytes, coerce to str
+ try:
+ yield key.decode(self.encoding)
+ except UnicodeDecodeError:
+ pass
+ except AttributeError:
+ # assume str, coerce to bytes
+ try:
+ yield key.encode(self.encoding)
+ except UnicodeEncodeError:
+ pass
+
+ def __getitem__(self, key):
+
+ superobj = super(FieldStorage, self)
+
+ error = None
+
+ for candidate in self._key_candidates(key):
+ if isinstance(candidate, bytes):
+ # ouch
+ candidate = repr(candidate)
+ try:
+ return superobj.__getitem__(candidate)
+ except KeyError as e:
+ if error is None:
+ error = e
+
+ # fall through, re-raise the first KeyError
+ raise error
+
+ def __contains__(self, key):
+ superobj = super(FieldStorage, self)
+
+ for candidate in self._key_candidates(key):
+ if superobj.__contains__(candidate):
+ return True
+ return False
+
+else: # PY2
+
+ FieldStorage = cgi.FieldStorage
+
+
+form = FieldStorage()
+
+print('Filename: %s' % form['up'].filename)
+print('Name: %s' % form['name'].value)
+print('Content: %s' % form['up'].file.read())
diff --git a/tests/cgiapp_data/ok.cgi b/tests/cgiapp_data/ok.cgi
new file mode 100755
index 0000000..d03f0b9
--- /dev/null
+++ b/tests/cgiapp_data/ok.cgi
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+print('Content-type: text/html; charset=UTF-8')
+print('Status: 200 Okay')
+print('')
+print('This is the body')
diff --git a/tests/cgiapp_data/stderr.cgi b/tests/cgiapp_data/stderr.cgi
new file mode 100755
index 0000000..d2520b6
--- /dev/null
+++ b/tests/cgiapp_data/stderr.cgi
@@ -0,0 +1,8 @@
+#!/usr/bin/env python
+from __future__ import print_function
+import sys
+print('Status: 500 Server Error')
+print('Content-type: text/html')
+print()
+print('There was an error')
+print('some data on the error', file=sys.stderr)
diff --git a/tests/test_auth/__init__.py b/tests/test_auth/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/test_auth/__init__.py
diff --git a/tests/test_auth/test_auth_cookie.py b/tests/test_auth/test_auth_cookie.py
new file mode 100644
index 0000000..38e37b8
--- /dev/null
+++ b/tests/test_auth/test_auth_cookie.py
@@ -0,0 +1,46 @@
+# (c) 2005 Clark C. Evans
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+from six.moves import xrange
+import six
+
+from paste.auth import cookie
+from paste.wsgilib import raw_interactive, dump_environ
+from paste.response import header_value
+from paste.httpexceptions import *
+
+def build(application,setenv, *args, **kwargs):
+ def setter(environ, start_response):
+ save = environ['paste.auth.cookie'].append
+ for (k,v) in setenv.items():
+ save(k)
+ environ[k] = v
+ return application(environ, start_response)
+ return cookie.middleware(setter,*args,**kwargs)
+
+def test_noop():
+ app = build(dump_environ,{})
+ (status,headers,content,errors) = \
+ raw_interactive(app)
+ assert not header_value(headers,'Set-Cookie')
+
+def test_basic(key='key', val='bingles'):
+ app = build(dump_environ,{key:val})
+ (status,headers,content,errors) = \
+ raw_interactive(app)
+ value = header_value(headers,'Set-Cookie')
+ assert "Path=/;" in value
+ assert "expires=" not in value
+ cookie = value.split(";")[0]
+ (status,headers,content,errors) = \
+ raw_interactive(app,{'HTTP_COOKIE': cookie})
+ expected = ("%s: %s" % (key,val.replace("\n","\n ")))
+ if six.PY3:
+ expected = expected.encode('utf8')
+ assert expected in content
+
+def test_roundtrip():
+ roundtrip = str('').join(map(chr, xrange(256)))
+ test_basic(roundtrip,roundtrip)
+
diff --git a/tests/test_auth/test_auth_digest.py b/tests/test_auth/test_auth_digest.py
new file mode 100644
index 0000000..1d44038
--- /dev/null
+++ b/tests/test_auth/test_auth_digest.py
@@ -0,0 +1,93 @@
+# (c) 2005 Clark C. Evans
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+from paste.auth.digest import *
+from paste.wsgilib import raw_interactive
+from paste.httpexceptions import *
+from paste.httpheaders import AUTHORIZATION, WWW_AUTHENTICATE, REMOTE_USER
+import os
+import six
+
+def application(environ, start_response):
+ content = REMOTE_USER(environ)
+ start_response("200 OK",(('Content-Type', 'text/plain'),
+ ('Content-Length', len(content))))
+
+ if six.PY3:
+ content = content.encode('utf8')
+ return [content]
+
+realm = "tag:clarkevans.com,2005:testing"
+
+def backwords(environ, realm, username):
+ """ dummy password hash, where user password is just reverse """
+ password = list(username)
+ password.reverse()
+ password = "".join(password)
+ return digest_password(realm, username, password)
+
+application = AuthDigestHandler(application,realm,backwords)
+application = HTTPExceptionHandler(application)
+
+def check(username, password, path="/"):
+ """ perform two-stage authentication to verify login """
+ (status,headers,content,errors) = \
+ raw_interactive(application,path, accept='text/html')
+ assert status.startswith("401")
+ challenge = WWW_AUTHENTICATE(headers)
+ response = AUTHORIZATION(username=username, password=password,
+ challenge=challenge, path=path)
+ assert "Digest" in response and username in response
+ (status,headers,content,errors) = \
+ raw_interactive(application,path,
+ HTTP_AUTHORIZATION=response)
+ if status.startswith("200"):
+ return content
+ if status.startswith("401"):
+ return None
+ assert False, "Unexpected Status: %s" % status
+
+def test_digest():
+ assert b'bing' == check("bing","gnib")
+ assert check("bing","bad") is None
+
+#
+# The following code uses sockets to test the functionality,
+# to enable use:
+#
+# $ TEST_SOCKET py.test
+#
+
+if os.environ.get("TEST_SOCKET",""):
+ from six.moves.urllib.error import HTTPError
+ from six.moves.urllib.request import build_opener, HTTPDigestAuthHandler
+ from paste.debug.testserver import serve
+ server = serve(application)
+
+ def authfetch(username,password,path="/",realm=realm):
+ server.accept(2)
+ import socket
+ socket.setdefaulttimeout(5)
+ uri = ("http://%s:%s" % server.server_address) + path
+ auth = HTTPDigestAuthHandler()
+ auth.add_password(realm,uri,username,password)
+ opener = build_opener(auth)
+ result = opener.open(uri)
+ return result.read()
+
+ def test_success():
+ assert "bing" == authfetch('bing','gnib')
+
+ def test_failure():
+ # urllib tries 5 more times before it gives up
+ server.accept(5)
+ try:
+ authfetch('bing','wrong')
+ assert False, "this should raise an exception"
+ except HTTPError as e:
+ assert e.code == 401
+
+ def test_shutdown():
+ server.stop()
+
diff --git a/tests/test_cgiapp.py b/tests/test_cgiapp.py
new file mode 100644
index 0000000..900e83e
--- /dev/null
+++ b/tests/test_cgiapp.py
@@ -0,0 +1,59 @@
+import os
+import sys
+from nose.tools import assert_raises
+from paste.cgiapp import CGIApplication, CGIError
+from paste.fixture import *
+
+data_dir = os.path.join(os.path.dirname(__file__), 'cgiapp_data')
+
+# these CGI scripts can't work on Windows or Jython
+if sys.platform != 'win32' and not sys.platform.startswith('java'):
+
+ # Ensure the CGI scripts are called with the same python interpreter. Put a
+ # symlink to the interpreter executable into the path...
+ def setup_module():
+ global oldpath, pyexelink
+ oldpath = os.environ.get('PATH', None)
+ os.environ['PATH'] = data_dir + os.path.pathsep + oldpath
+ pyexelink = os.path.join(data_dir, "python")
+ try:
+ os.unlink(pyexelink)
+ except OSError:
+ pass
+ os.symlink(sys.executable, pyexelink)
+
+ # ... and clean up again.
+ def teardown_module():
+ global oldpath, pyexelink
+ os.unlink(pyexelink)
+ if oldpath is not None:
+ os.environ['PATH'] = oldpath
+ else:
+ del os.environ['PATH']
+
+ def test_ok():
+ app = TestApp(CGIApplication({}, script='ok.cgi', path=[data_dir]))
+ res = app.get('')
+ assert res.header('content-type') == 'text/html; charset=UTF-8'
+ assert res.full_status == '200 Okay'
+ assert 'This is the body' in res
+
+ def test_form():
+ app = TestApp(CGIApplication({}, script='form.cgi', path=[data_dir]))
+ res = app.post('', params={'name': b'joe'},
+ upload_files=[('up', 'file.txt', b'x'*10000)])
+ assert 'file.txt' in res
+ assert 'joe' in res
+ assert 'x'*10000 in res
+
+ def test_error():
+ app = TestApp(CGIApplication({}, script='error.cgi', path=[data_dir]))
+ assert_raises(CGIError, app.get, '', status=500)
+
+ def test_stderr():
+ app = TestApp(CGIApplication({}, script='stderr.cgi', path=[data_dir]))
+ res = app.get('', expect_errors=True)
+ assert res.status == 500
+ assert 'error' in res
+ assert b'some data' in res.errors
+
diff --git a/tests/test_cgitb_catcher.py b/tests/test_cgitb_catcher.py
new file mode 100644
index 0000000..a63f7d8
--- /dev/null
+++ b/tests/test_cgitb_catcher.py
@@ -0,0 +1,78 @@
+from paste.fixture import *
+from paste.cgitb_catcher import CgitbMiddleware
+from paste import lint
+from .test_exceptions.test_error_middleware import clear_middleware
+
+def do_request(app, expect_status=500):
+ app = lint.middleware(app)
+ app = CgitbMiddleware(app, {}, display=True)
+ app = clear_middleware(app)
+ testapp = TestApp(app)
+ res = testapp.get('', status=expect_status,
+ expect_errors=True)
+ return res
+
+
+############################################################
+## Applications that raise exceptions
+############################################################
+
+def bad_app():
+ "No argument list!"
+ return None
+
+def start_response_app(environ, start_response):
+ "raise error before start_response"
+ raise ValueError("hi")
+
+def after_start_response_app(environ, start_response):
+ start_response("200 OK", [('Content-type', 'text/plain')])
+ raise ValueError('error2')
+
+def iter_app(environ, start_response):
+ start_response("200 OK", [('Content-type', 'text/plain')])
+ return yielder([b'this', b' is ', b' a', None])
+
+def yielder(args):
+ for arg in args:
+ if arg is None:
+ raise ValueError("None raises error")
+ yield arg
+
+############################################################
+## Tests
+############################################################
+
+def test_makes_exception():
+ res = do_request(bad_app)
+ print(res)
+ if six.PY3:
+ assert 'bad_app() takes 0 positional arguments but 2 were given' in res
+ else:
+ assert 'bad_app() takes no arguments (2 given' in res
+ assert 'iterator = application(environ, start_response_wrapper)' in res
+ assert 'lint.py' in res
+ assert 'cgitb_catcher.py' in res
+
+def test_start_res():
+ res = do_request(start_response_app)
+ print(res)
+ assert 'ValueError: hi' in res
+ assert 'test_cgitb_catcher.py' in res
+ assert 'line 26, in start_response_app' in res
+
+def test_after_start():
+ res = do_request(after_start_response_app, 200)
+ print(res)
+ assert 'ValueError: error2' in res
+ assert 'line 30' in res
+
+def test_iter_app():
+ res = do_request(iter_app, 200)
+ print(res)
+ assert 'None raises error' in res
+ assert 'yielder' in res
+
+
+
+
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 0000000..8119157
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,85 @@
+# (c) 2007 Philip Jenvey; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+from nose.tools import assert_raises
+from paste.config import CONFIG, ConfigMiddleware
+from paste.fixture import TestApp
+import six
+
+test_key = 'test key'
+
+def reset_config():
+ while True:
+ try:
+ CONFIG._pop_object()
+ except IndexError:
+ break
+
+def app_with_config(environ, start_response):
+ start_response('200 OK', [('Content-type','text/plain')])
+ lines = ['Variable is: %s\n' % CONFIG[test_key],
+ 'Variable is (in environ): %s' % environ['paste.config'][test_key]]
+ if six.PY3:
+ lines = [line.encode('utf8') for line in lines]
+ return lines
+
+class NestingAppWithConfig(object):
+ def __init__(self, app):
+ self.app = app
+
+ def __call__(self, environ, start_response):
+ response = self.app(environ, start_response)
+ assert isinstance(response, list)
+ supplement = ['Nesting variable is: %s' % CONFIG[test_key],
+ 'Nesting variable is (in environ): %s' % \
+ environ['paste.config'][test_key]]
+ if six.PY3:
+ supplement = [line.encode('utf8') for line in supplement]
+ response.extend(supplement)
+ return response
+
+def test_request_config():
+ try:
+ config = {test_key: 'test value'}
+ app = ConfigMiddleware(app_with_config, config)
+ res = TestApp(app).get('/')
+ assert 'Variable is: test value' in res
+ assert 'Variable is (in environ): test value' in res
+ finally:
+ reset_config()
+
+def test_request_config_multi():
+ try:
+ config = {test_key: 'test value'}
+ app = ConfigMiddleware(app_with_config, config)
+ config = {test_key: 'nesting value'}
+ app = ConfigMiddleware(NestingAppWithConfig(app), config)
+ res = TestApp(app).get('/')
+ assert 'Variable is: test value' in res
+ assert 'Variable is (in environ): test value' in res
+ assert 'Nesting variable is: nesting value' in res
+ print(res)
+ assert 'Nesting variable is (in environ): nesting value' in res
+ finally:
+ reset_config()
+
+def test_process_config(request_app=test_request_config):
+ try:
+ process_config = {test_key: 'bar', 'process_var': 'foo'}
+ CONFIG.push_process_config(process_config)
+
+ assert CONFIG[test_key] == 'bar'
+ assert CONFIG['process_var'] == 'foo'
+
+ request_app()
+
+ assert CONFIG[test_key] == 'bar'
+ assert CONFIG['process_var'] == 'foo'
+ CONFIG.pop_process_config()
+
+ assert_raises(AttributeError, lambda: 'process_var' not in CONFIG)
+ assert_raises(IndexError, CONFIG.pop_process_config)
+ finally:
+ reset_config()
+
+def test_process_config_multi():
+ test_process_config(test_request_config_multi)
diff --git a/tests/test_doctests.py b/tests/test_doctests.py
new file mode 100644
index 0000000..d59d666
--- /dev/null
+++ b/tests/test_doctests.py
@@ -0,0 +1,63 @@
+import six
+import doctest
+from paste.util.import_string import simple_import
+import os
+
+filenames = [
+ 'tests/test_template.txt',
+ ]
+
+modules = [
+ 'paste.util.template',
+ 'paste.util.looper',
+ # This one opens up httpserver, which is bad:
+ #'paste.auth.cookie',
+ #'paste.auth.multi',
+ #'paste.auth.digest',
+ #'paste.auth.basic',
+ #'paste.auth.form',
+ #'paste.progress',
+ 'paste.exceptions.serial_number_generator',
+ 'paste.evalexception.evalcontext',
+ 'paste.util.dateinterval',
+ 'paste.util.quoting',
+ 'paste.wsgilib',
+ 'paste.url',
+ 'paste.request',
+ ]
+
+options = doctest.ELLIPSIS | doctest.REPORT_ONLY_FIRST_FAILURE
+if six.PY3:
+ options |= doctest.IGNORE_EXCEPTION_DETAIL
+
+def test_doctests():
+ for filename in filenames:
+ filename = os.path.join(
+ os.path.dirname(os.path.dirname(__file__)),
+ filename)
+ yield do_doctest, filename
+
+def do_doctest(filename):
+ failure, total = doctest.testfile(
+ filename, module_relative=False,
+ optionflags=options)
+ assert not failure, "Failure in %r" % filename
+
+def test_doctest_mods():
+ for module in modules:
+ yield do_doctest_mod, module
+
+def do_doctest_mod(module):
+ module = simple_import(module)
+ failure, total = doctest.testmod(
+ module, optionflags=options)
+ assert not failure, "Failure in %r" % module
+
+if __name__ == '__main__':
+ import sys
+ import doctest
+ args = sys.argv[1:]
+ if not args:
+ args = filenames
+ for filename in args:
+ doctest.testfile(filename, module_relative=False)
diff --git a/tests/test_errordocument.py b/tests/test_errordocument.py
new file mode 100644
index 0000000..efeae61
--- /dev/null
+++ b/tests/test_errordocument.py
@@ -0,0 +1,92 @@
+from paste.errordocument import forward
+from paste.fixture import *
+from paste.recursive import RecursiveMiddleware
+
+def simple_app(environ, start_response):
+ start_response("200 OK", [('Content-type', 'text/plain')])
+ return [b'requested page returned']
+
+def not_found_app(environ, start_response):
+ start_response("404 Not found", [('Content-type', 'text/plain')])
+ return [b'requested page returned']
+
+def test_ok():
+ app = TestApp(simple_app)
+ res = app.get('')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'requested page returned' in res
+
+def error_docs_app(environ, start_response):
+ if environ['PATH_INFO'] == '/not_found':
+ start_response("404 Not found", [('Content-type', 'text/plain')])
+ return [b'Not found']
+ elif environ['PATH_INFO'] == '/error':
+ start_response("200 OK", [('Content-type', 'text/plain')])
+ return [b'Page not found']
+ else:
+ return simple_app(environ, start_response)
+
+def test_error_docs_app():
+ app = TestApp(error_docs_app)
+ res = app.get('')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'requested page returned' in res
+ res = app.get('/error')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'Page not found' in res
+ res = app.get('/not_found', status=404)
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '404 Not found'
+ assert 'Not found' in res
+
+def test_forward():
+ app = forward(error_docs_app, codes={404:'/error'})
+ app = TestApp(RecursiveMiddleware(app))
+ res = app.get('')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'requested page returned' in res
+ res = app.get('/error')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'Page not found' in res
+ res = app.get('/not_found', status=404)
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '404 Not found'
+ # Note changed response
+ assert 'Page not found' in res
+
+def auth_required_app(environ, start_response):
+ start_response('401 Unauthorized', [('content-type', 'text/plain'), ('www-authenticate', 'Basic realm="Foo"')])
+ return ['Sign in!']
+
+def auth_docs_app(environ, start_response):
+ if environ['PATH_INFO'] == '/auth':
+ return auth_required_app(environ, start_response)
+ elif environ['PATH_INFO'] == '/auth_doc':
+ start_response("200 OK", [('Content-type', 'text/html')])
+ return [b'<html>Login!</html>']
+ else:
+ return simple_app(environ, start_response)
+
+def test_auth_docs_app():
+ wsgi_app = forward(auth_docs_app, codes={401: '/auth_doc'})
+ app = TestApp(wsgi_app)
+ res = app.get('/auth_doc')
+ assert res.header('content-type') == 'text/html'
+ res = app.get('/auth', status=401)
+ assert res.header('content-type') == 'text/html'
+ assert res.header('www-authenticate') == 'Basic realm="Foo"'
+ assert res.body == b'<html>Login!</html>'
+
+def test_bad_error():
+ def app(environ, start_response):
+ start_response('404 Not Found', [('content-type', 'text/plain')])
+ return ['not found']
+ app = forward(app, {404: '/404.html'})
+ app = TestApp(app)
+ resp = app.get('/test', expect_errors=True)
+ print(resp)
diff --git a/tests/test_exceptions/__init__.py b/tests/test_exceptions/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/tests/test_exceptions/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/tests/test_exceptions/test_error_middleware.py b/tests/test_exceptions/test_error_middleware.py
new file mode 100644
index 0000000..95ab177
--- /dev/null
+++ b/tests/test_exceptions/test_error_middleware.py
@@ -0,0 +1,109 @@
+from paste.fixture import *
+from paste.exceptions.errormiddleware import ErrorMiddleware
+from paste import lint
+from paste.util.quoting import strip_html
+#
+# For some strange reason, these 4 lines cannot be removed or the regression
+# test breaks; is it counting the number of lines in the file somehow?
+#
+
+def do_request(app, expect_status=500):
+ app = lint.middleware(app)
+ app = ErrorMiddleware(app, {}, debug=True)
+ app = clear_middleware(app)
+ testapp = TestApp(app)
+ res = testapp.get('', status=expect_status,
+ expect_errors=True)
+ return res
+
+def clear_middleware(app):
+ """
+ The fixture sets paste.throw_errors, which suppresses exactly what
+ we want to test in this case. This wrapper also strips exc_info
+ on the *first* call to start_response (but not the second, or
+ subsequent calls.
+ """
+ def clear_throw_errors(environ, start_response):
+ headers_sent = []
+ def replacement(status, headers, exc_info=None):
+ if headers_sent:
+ return start_response(status, headers, exc_info)
+ headers_sent.append(True)
+ return start_response(status, headers)
+ if 'paste.throw_errors' in environ:
+ del environ['paste.throw_errors']
+ return app(environ, replacement)
+ return clear_throw_errors
+
+
+############################################################
+## Applications that raise exceptions
+############################################################
+
+def bad_app():
+ "No argument list!"
+ return None
+
+def unicode_bad_app(environ, start_response):
+ raise ValueError(u"\u1000")
+
+def start_response_app(environ, start_response):
+ "raise error before start_response"
+ raise ValueError("hi")
+
+def after_start_response_app(environ, start_response):
+ start_response("200 OK", [('Content-type', 'text/plain')])
+ raise ValueError('error2')
+
+def iter_app(environ, start_response):
+ start_response("200 OK", [('Content-type', 'text/plain')])
+ return yielder([b'this', b' is ', b' a', None])
+
+def yielder(args):
+ for arg in args:
+ if arg is None:
+ raise ValueError("None raises error")
+ yield arg
+
+############################################################
+## Tests
+############################################################
+
+def test_makes_exception():
+ res = do_request(bad_app)
+ assert '<html' in res
+ res = strip_html(str(res))
+ if six.PY3:
+ assert 'bad_app() takes 0 positional arguments but 2 were given' in res
+ else:
+ assert 'bad_app() takes no arguments (2 given' in res, repr(res)
+ assert 'iterator = application(environ, start_response_wrapper)' in res
+ assert 'paste.lint' in res
+ assert 'paste.exceptions.errormiddleware' in res
+
+def test_unicode_exception():
+ res = do_request(unicode_bad_app)
+
+
+def test_start_res():
+ res = do_request(start_response_app)
+ res = strip_html(str(res))
+ assert 'ValueError: hi' in res
+ assert 'test_error_middleware' in res
+ assert ':52 in start_response_app' in res
+
+def test_after_start():
+ res = do_request(after_start_response_app, 200)
+ res = strip_html(str(res))
+ #print res
+ assert 'ValueError: error2' in res
+
+def test_iter_app():
+ res = do_request(lint.middleware(iter_app), 200)
+ #print res
+ assert 'None raises error' in res
+ assert 'yielder' in res
+
+
+
+
diff --git a/tests/test_exceptions/test_formatter.py b/tests/test_exceptions/test_formatter.py
new file mode 100644
index 0000000..9c53a9a
--- /dev/null
+++ b/tests/test_exceptions/test_formatter.py
@@ -0,0 +1,183 @@
+from paste.exceptions import formatter
+from paste.exceptions import collector
+import sys
+import os
+import difflib
+
+class Mock(object):
+ def __init__(self, **kw):
+ for name, value in kw.items():
+ setattr(self, name, value)
+
+class Supplement(Mock):
+
+ object = 'test_object'
+ source_url = 'http://whatever.com'
+ info = 'This is some supplemental information'
+ args = ()
+ def getInfo(self):
+ return self.info
+
+ def __call__(self, *args):
+ self.args = args
+ return self
+
+class BadSupplement(Supplement):
+
+ def getInfo(self):
+ raise ValueError("This supplemental info is buggy")
+
+def call_error(sup):
+ 1 + 2
+ __traceback_supplement__ = (sup, ())
+ assert 0, "I am an error"
+
+def raise_error(sup='default'):
+ if sup == 'default':
+ sup = Supplement()
+ for i in range(10):
+ __traceback_info__ = i
+ if i == 5:
+ call_error(sup=sup)
+
+def hide(t, inner, *args, **kw):
+ __traceback_hide__ = t
+ return inner(*args, **kw)
+
+def pass_through(info, inner, *args, **kw):
+ """
+ To add another frame to the call; detectable because
+ __tracback_info__ is set to `info`
+ """
+ __traceback_info__ = info
+ return inner(*args, **kw)
+
+def format(type='html', **ops):
+ data = collector.collect_exception(*sys.exc_info())
+ report = getattr(formatter, 'format_' + type)(data, **ops)
+ return report
+
+formats = ('text', 'html')
+
+def test_excersize():
+ for f in formats:
+ try:
+ raise_error()
+ except:
+ format(f)
+
+def test_content():
+ for f in formats:
+ try:
+ raise_error()
+ except:
+ result = format(f)
+ print(result)
+ assert 'test_object' in result
+ assert 'http://whatever.com' in result
+ assert 'This is some supplemental information' in result
+ assert 'raise_error' in result
+ assert 'call_error' in result
+ assert '5' in result
+ assert 'test_content' in result
+ else:
+ assert 0
+
+def test_trim():
+ current = os.path.abspath(os.getcwd())
+ for f in formats:
+ try:
+ raise_error()
+ except:
+ result = format(f, trim_source_paths=[(current, '.')])
+ assert current not in result
+ assert ('%stest_formatter.py' % os.sep) in result, ValueError(repr(result))
+ else:
+ assert 0
+
+def test_hide():
+ for f in formats:
+ try:
+ hide(True, raise_error)
+ except:
+ result = format(f)
+ print(result)
+ assert 'in hide_inner' not in result
+ assert 'inner(*args, **kw)' not in result
+ else:
+ assert 0
+
+def print_diff(s1, s2):
+ differ = difflib.Differ()
+ result = list(differ.compare(s1.splitlines(), s2.splitlines()))
+ print('\n'.join(result))
+
+def test_hide_supppressed():
+ """
+ When an error occurs and __traceback_stop__ is true for the
+ erroneous frame, then that setting should be ignored.
+ """
+ for f in ['html']: #formats:
+ results = []
+ for hide_value in (False, 'after'):
+ try:
+ pass_through(
+ 'a',
+ hide,
+ hide_value,
+ pass_through,
+ 'b',
+ raise_error)
+ except:
+ results.append(format(f))
+ else:
+ assert 0
+ if results[0] != results[1]:
+ print_diff(results[0], results[1])
+ assert 0
+
+def test_hide_after():
+ for f in formats:
+ try:
+ pass_through(
+ 'AABB',
+ hide, 'after',
+ pass_through, 'CCDD',
+ # A little whitespace to keep this line out of the
+ # content part of the report
+
+
+ hide, 'reset',
+ raise_error)
+ except:
+ result = format(f)
+ assert 'AABB' in result
+ assert 'CCDD' not in result
+ assert 'raise_error' in result
+ else:
+ assert 0
+
+def test_hide_before():
+ for f in formats:
+ try:
+ pass_through(
+ 'AABB',
+ hide, 'before',
+ raise_error)
+ except:
+ result = format(f)
+ print(result)
+ assert 'AABB' not in result
+ assert 'raise_error' in result
+ else:
+ assert 0
+
+def test_make_wrappable():
+ assert '<wbr>' in formatter.make_wrappable('x'*1000)
+ # I'm just going to test that this doesn't excede the stack limit:
+ formatter.make_wrappable(';'*2000)
+ assert (formatter.make_wrappable('this that the other')
+ == 'this that the other')
+ assert (formatter.make_wrappable('this that ' + ('x'*50) + ';' + ('y'*50) + ' and the other')
+ == 'this that '+('x'*50) + ';<wbr>' + ('y'*50) + ' and the other')
+
diff --git a/tests/test_exceptions/test_httpexceptions.py b/tests/test_exceptions/test_httpexceptions.py
new file mode 100644
index 0000000..24e00dd
--- /dev/null
+++ b/tests/test_exceptions/test_httpexceptions.py
@@ -0,0 +1,97 @@
+# (c) 2005 Ian Bicking, Clark C. Evans and contributors
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+"""
+WSGI Exception Middleware
+
+Regression Test Suite
+"""
+from nose.tools import assert_raises
+from paste.httpexceptions import *
+from paste.response import header_value
+import six
+
+
+def test_HTTPMove():
+ """ make sure that location is a mandatory attribute of Redirects """
+ assert_raises(AssertionError, HTTPFound)
+ assert_raises(AssertionError, HTTPTemporaryRedirect,
+ headers=[('l0cation','/bing')])
+ assert isinstance(HTTPMovedPermanently("This is a message",
+ headers=[('Location','/bing')])
+ ,HTTPRedirection)
+ assert isinstance(HTTPUseProxy(headers=[('LOCATION','/bing')])
+ ,HTTPRedirection)
+ assert isinstance(HTTPFound('/foobar'),HTTPRedirection)
+
+def test_badapp():
+ """ verify that the middleware handles previously-started responses """
+ def badapp(environ, start_response):
+ start_response("200 OK",[])
+ raise HTTPBadRequest("Do not do this at home.")
+ newapp = HTTPExceptionHandler(badapp)
+ assert b'Bad Request' in b''.join(newapp({'HTTP_ACCEPT': 'text/html'},
+ (lambda a, b, c=None: None)))
+
+def test_unicode():
+ """ verify unicode output """
+ tstr = u"\0xCAFE"
+ def badapp(environ, start_response):
+ start_response("200 OK",[])
+ raise HTTPBadRequest(tstr)
+ newapp = HTTPExceptionHandler(badapp)
+ assert tstr.encode("utf-8") in b''.join(newapp({'HTTP_ACCEPT':
+ 'text/html'},
+ (lambda a, b, c=None: None)))
+ assert tstr.encode("utf-8") in b''.join(newapp({'HTTP_ACCEPT':
+ 'text/plain'},
+ (lambda a, b, c=None: None)))
+
+def test_template():
+ """ verify that html() and plain() output methods work """
+ e = HTTPInternalServerError()
+ e.template = 'A %(ping)s and <b>%(pong)s</b> message.'
+ assert str(e).startswith("500 Internal Server Error")
+ assert e.plain({'ping': 'fun', 'pong': 'happy'}) == (
+ '500 Internal Server Error\r\n'
+ 'A fun and happy message.\r\n')
+ assert '<p>A fun and <b>happy</b> message.</p>' in \
+ e.html({'ping': 'fun', 'pong': 'happy'})
+
+def test_redapp():
+ """ check that redirect returns the correct, expected results """
+ saved = []
+ def saveit(status, headers, exc_info = None):
+ saved.append((status,headers))
+ def redapp(environ, start_response):
+ raise HTTPFound("/bing/foo")
+ app = HTTPExceptionHandler(redapp)
+ result = list(app({'HTTP_ACCEPT': 'text/html'},saveit))
+ assert b'<a href="/bing/foo">' in result[0]
+ assert "302 Found" == saved[0][0]
+ if six.PY3:
+ assert "text/html; charset=utf8" == header_value(saved[0][1], 'content-type')
+ else:
+ assert "text/html" == header_value(saved[0][1], 'content-type')
+ assert "/bing/foo" == header_value(saved[0][1],'location')
+ result = list(app({'HTTP_ACCEPT': 'text/plain'},saveit))
+ assert "text/plain; charset=utf8" == header_value(saved[1][1],'content-type')
+ assert "/bing/foo" == header_value(saved[1][1],'location')
+
+def test_misc():
+ assert get_exception(301) == HTTPMovedPermanently
+ redirect = HTTPFound("/some/path")
+ assert isinstance(redirect,HTTPException)
+ assert isinstance(redirect,HTTPRedirection)
+ assert not isinstance(redirect,HTTPError)
+ notfound = HTTPNotFound()
+ assert isinstance(notfound,HTTPException)
+ assert isinstance(notfound,HTTPError)
+ assert isinstance(notfound,HTTPClientError)
+ assert not isinstance(notfound,HTTPServerError)
+ notimpl = HTTPNotImplemented()
+ assert isinstance(notimpl,HTTPException)
+ assert isinstance(notimpl,HTTPError)
+ assert isinstance(notimpl,HTTPServerError)
+ assert not isinstance(notimpl,HTTPClientError)
+
diff --git a/tests/test_exceptions/test_reporter.py b/tests/test_exceptions/test_reporter.py
new file mode 100644
index 0000000..a40666e
--- /dev/null
+++ b/tests/test_exceptions/test_reporter.py
@@ -0,0 +1,50 @@
+import sys
+import os
+from paste.exceptions.reporter import *
+from paste.exceptions import collector
+
+def setup_file(fn, content=None):
+ dir = os.path.join(os.path.dirname(__file__), 'reporter_output')
+ fn = os.path.join(dir, fn)
+ if os.path.exists(dir):
+ if os.path.exists(fn):
+ os.unlink(fn)
+ else:
+ os.mkdir(dir)
+ if content is not None:
+ f = open(fn, 'wb')
+ f.write(content)
+ f.close()
+ return fn
+
+def test_logger():
+ fn = setup_file('test_logger.log')
+ rep = LogReporter(
+ filename=fn,
+ show_hidden_frames=False)
+ try:
+ int('a')
+ except:
+ exc_data = collector.collect_exception(*sys.exc_info())
+ else:
+ assert 0
+ rep.report(exc_data)
+ content = open(fn).read()
+ assert len(content.splitlines()) == 4, len(content.splitlines())
+ assert 'ValueError' in content
+ assert 'int' in content
+ assert 'test_reporter.py' in content
+ assert 'test_logger' in content
+
+ try:
+ 1 / 0
+ except:
+ exc_data = collector.collect_exception(*sys.exc_info())
+ else:
+ assert 0
+ rep.report(exc_data)
+ content = open(fn).read()
+ print(content)
+ assert len(content.splitlines()) == 8
+ assert 'ZeroDivisionError' in content
+
diff --git a/tests/test_fileapp.py b/tests/test_fileapp.py
new file mode 100644
index 0000000..ee7da6a
--- /dev/null
+++ b/tests/test_fileapp.py
@@ -0,0 +1,242 @@
+# (c) 2005 Ian Bicking, Clark C. Evans and contributors
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+import time
+import random
+import os
+import tempfile
+try:
+ # Python 3
+ from email.utils import parsedate_tz, mktime_tz
+except ImportError:
+ # Python 2
+ from rfc822 import parsedate_tz, mktime_tz
+import six
+
+from paste import fileapp
+from paste.fileapp import *
+from paste.fixture import *
+
+# NOTE(haypo): don't use string.letters because the order of lower and upper
+# case letters changes when locale.setlocale() is called for the first time
+LETTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+
+def test_data():
+ harness = TestApp(DataApp(b'mycontent'))
+ res = harness.get("/")
+ assert 'application/octet-stream' == res.header('content-type')
+ assert '9' == res.header('content-length')
+ assert "<Response 200 OK 'mycontent'>" == repr(res)
+ harness.app.set_content(b"bingles")
+ assert "<Response 200 OK 'bingles'>" == repr(harness.get("/"))
+
+def test_cache():
+ def build(*args,**kwargs):
+ app = DataApp(b"SomeContent")
+ app.cache_control(*args,**kwargs)
+ return TestApp(app).get("/")
+ res = build()
+ assert 'public' == res.header('cache-control')
+ assert not res.header('expires',None)
+ res = build(private=True)
+ assert 'private' == res.header('cache-control')
+ assert mktime_tz(parsedate_tz(res.header('expires'))) < time.time()
+ res = build(no_cache=True)
+ assert 'no-cache' == res.header('cache-control')
+ assert mktime_tz(parsedate_tz(res.header('expires'))) < time.time()
+ res = build(max_age=60,s_maxage=30)
+ assert 'public, max-age=60, s-maxage=30' == res.header('cache-control')
+ expires = mktime_tz(parsedate_tz(res.header('expires')))
+ assert expires > time.time()+58 and expires < time.time()+61
+ res = build(private=True, max_age=60, no_transform=True, no_store=True)
+ assert 'private, no-store, no-transform, max-age=60' == \
+ res.header('cache-control')
+ expires = mktime_tz(parsedate_tz(res.header('expires')))
+ assert mktime_tz(parsedate_tz(res.header('expires'))) < time.time()
+
+def test_disposition():
+ def build(*args,**kwargs):
+ app = DataApp(b"SomeContent")
+ app.content_disposition(*args,**kwargs)
+ return TestApp(app).get("/")
+ res = build()
+ assert 'attachment' == res.header('content-disposition')
+ assert 'application/octet-stream' == res.header('content-type')
+ res = build(filename="bing.txt")
+ assert 'attachment; filename="bing.txt"' == \
+ res.header('content-disposition')
+ assert 'text/plain' == res.header('content-type')
+ res = build(inline=True)
+ assert 'inline' == res.header('content-disposition')
+ assert 'application/octet-stream' == res.header('content-type')
+ res = build(inline=True, filename="/some/path/bing.txt")
+ assert 'inline; filename="bing.txt"' == \
+ res.header('content-disposition')
+ assert 'text/plain' == res.header('content-type')
+ try:
+ res = build(inline=True,attachment=True)
+ except AssertionError:
+ pass
+ else:
+ assert False, "should be an exception"
+
+def test_modified():
+ harness = TestApp(DataApp(b'mycontent'))
+ res = harness.get("/")
+ assert "<Response 200 OK 'mycontent'>" == repr(res)
+ last_modified = res.header('last-modified')
+ res = harness.get("/",headers={'if-modified-since': last_modified})
+ assert "<Response 304 Not Modified ''>" == repr(res)
+ res = harness.get("/",headers={'if-modified-since': last_modified + \
+ '; length=1506'})
+ assert "<Response 304 Not Modified ''>" == repr(res)
+ res = harness.get("/",status=400,
+ headers={'if-modified-since': 'garbage'})
+ assert 400 == res.status and b"ill-formed timestamp" in res.body
+ res = harness.get("/",status=400,
+ headers={'if-modified-since':
+ 'Thu, 22 Dec 3030 01:01:01 GMT'})
+ assert 400 == res.status and b"check your system clock" in res.body
+
+def test_file():
+ tempfile = "test_fileapp.%s.txt" % (random.random())
+ content = LETTERS * 20
+ if six.PY3:
+ content = content.encode('utf8')
+ with open(tempfile, "wb") as fp:
+ fp.write(content)
+ try:
+ app = fileapp.FileApp(tempfile)
+ res = TestApp(app).get("/")
+ assert len(content) == int(res.header('content-length'))
+ assert 'text/plain' == res.header('content-type')
+ assert content == res.body
+ assert content == app.content # this is cashed
+ lastmod = res.header('last-modified')
+ print("updating", tempfile)
+ file = open(tempfile,"a+")
+ file.write("0123456789")
+ file.close()
+ res = TestApp(app).get("/",headers={'Cache-Control': 'max-age=0'})
+ assert len(content)+10 == int(res.header('content-length'))
+ assert 'text/plain' == res.header('content-type')
+ assert content + b"0123456789" == res.body
+ assert app.content # we are still cached
+ file = open(tempfile,"a+")
+ file.write("X" * fileapp.CACHE_SIZE) # exceed the cashe size
+ file.write("YZ")
+ file.close()
+ res = TestApp(app).get("/",headers={'Cache-Control': 'max-age=0'})
+ newsize = fileapp.CACHE_SIZE + len(content)+12
+ assert newsize == int(res.header('content-length'))
+ assert newsize == len(res.body)
+ assert res.body.startswith(content) and res.body.endswith(b'XYZ')
+ assert not app.content # we are no longer cached
+ finally:
+ os.unlink(tempfile)
+
+def test_dir():
+ tmpdir = tempfile.mkdtemp()
+ try:
+ tmpfile = os.path.join(tmpdir, 'file')
+ tmpsubdir = os.path.join(tmpdir, 'dir')
+ fp = open(tmpfile, 'w')
+ fp.write('abcd')
+ fp.close()
+ os.mkdir(tmpsubdir)
+ try:
+ app = fileapp.DirectoryApp(tmpdir)
+ for path in ['/', '', '//', '/..', '/.', '/../..']:
+ assert TestApp(app).get(path, status=403).status == 403, ValueError(path)
+ for path in ['/~', '/foo', '/dir', '/dir/']:
+ assert TestApp(app).get(path, status=404).status == 404, ValueError(path)
+ assert TestApp(app).get('/file').body == b'abcd'
+ finally:
+ os.remove(tmpfile)
+ os.rmdir(tmpsubdir)
+ finally:
+ os.rmdir(tmpdir)
+
+def _excercize_range(build,content):
+ # full content request, but using ranges'
+ res = build("bytes=0-%d" % (len(content)-1))
+ assert res.header('accept-ranges') == 'bytes'
+ assert res.body == content
+ assert res.header('content-length') == str(len(content))
+ res = build("bytes=-%d" % (len(content)-1))
+ assert res.body == content
+ assert res.header('content-length') == str(len(content))
+ res = build("bytes=0-")
+ assert res.body == content
+ assert res.header('content-length') == str(len(content))
+ # partial content requests
+ res = build("bytes=0-9", status=206)
+ assert res.body == content[:10]
+ assert res.header('content-length') == '10'
+ res = build("bytes=%d-" % (len(content)-1), status=206)
+ assert res.body == b'Z'
+ assert res.header('content-length') == '1'
+ res = build("bytes=%d-%d" % (3,17), status=206)
+ assert res.body == content[3:18]
+ assert res.header('content-length') == '15'
+
+def test_range():
+ content = LETTERS * 5
+ if six.PY3:
+ content = content.encode('utf8')
+ def build(range, status=206):
+ app = DataApp(content)
+ return TestApp(app).get("/",headers={'Range': range}, status=status)
+ _excercize_range(build,content)
+ build('bytes=0-%d' % (len(content)+1), 416)
+
+def test_file_range():
+ tempfile = "test_fileapp.%s.txt" % (random.random())
+ content = LETTERS * (1+(fileapp.CACHE_SIZE // len(LETTERS)))
+ if six.PY3:
+ content = content.encode('utf8')
+ assert len(content) > fileapp.CACHE_SIZE
+ with open(tempfile, "wb") as fp:
+ fp.write(content)
+ try:
+ def build(range, status=206):
+ app = fileapp.FileApp(tempfile)
+ return TestApp(app).get("/",headers={'Range': range},
+ status=status)
+ _excercize_range(build,content)
+ for size in (13,len(LETTERS), len(LETTERS)-1):
+ fileapp.BLOCK_SIZE = size
+ _excercize_range(build,content)
+ finally:
+ os.unlink(tempfile)
+
+def test_file_cache():
+ filename = os.path.join(os.path.dirname(__file__),
+ 'urlparser_data', 'secured.txt')
+ app = TestApp(fileapp.FileApp(filename))
+ res = app.get('/')
+ etag = res.header('ETag')
+ last_mod = res.header('Last-Modified')
+ res = app.get('/', headers={'If-Modified-Since': last_mod},
+ status=304)
+ res = app.get('/', headers={'If-None-Match': etag},
+ status=304)
+ res = app.get('/', headers={'If-None-Match': 'asdf'},
+ status=200)
+ res = app.get('/', headers={'If-Modified-Since': 'Sat, 1 Jan 2005 12:00:00 GMT'},
+ status=200)
+ res = app.get('/', headers={'If-Modified-Since': last_mod + '; length=100'},
+ status=304)
+ res = app.get('/', headers={'If-Modified-Since': 'invalid date'},
+ status=400)
+
+def test_methods():
+ filename = os.path.join(os.path.dirname(__file__),
+ 'urlparser_data', 'secured.txt')
+ app = TestApp(fileapp.FileApp(filename))
+ get_res = app.get('')
+ res = app.get('', extra_environ={'REQUEST_METHOD': 'HEAD'})
+ assert res.headers == get_res.headers
+ assert not res.body
+ app.post('', status=405) # Method Not Allowed
+
diff --git a/tests/test_fixture.py b/tests/test_fixture.py
new file mode 100644
index 0000000..ba56488
--- /dev/null
+++ b/tests/test_fixture.py
@@ -0,0 +1,28 @@
+from paste.debug.debugapp import SimpleApplication
+from paste.fixture import TestApp
+
+def test_fixture():
+ app = TestApp(SimpleApplication())
+ res = app.get('/', params={'a': ['1', '2']})
+ assert (res.request.environ['QUERY_STRING'] ==
+ 'a=1&a=2')
+ res = app.put('/')
+ assert (res.request.environ['REQUEST_METHOD'] ==
+ 'PUT')
+ res = app.delete('/')
+ assert (res.request.environ['REQUEST_METHOD'] ==
+ 'DELETE')
+ class FakeDict(object):
+ def items(self):
+ return [('a', '10'), ('a', '20')]
+ res = app.post('/params', params=FakeDict())
+
+ # test multiple cookies in one request
+ app.cookies['one'] = 'first';
+ app.cookies['two'] = 'second';
+ app.cookies['three'] = '';
+ res = app.get('/')
+ hc = res.request.environ['HTTP_COOKIE'].split('; ');
+ assert ('one=first' in hc)
+ assert ('two=second' in hc)
+ assert ('three=' in hc)
diff --git a/tests/test_grantip.py b/tests/test_grantip.py
new file mode 100644
index 0000000..2ddf7f1
--- /dev/null
+++ b/tests/test_grantip.py
@@ -0,0 +1,37 @@
+from paste.auth import grantip
+from paste.fixture import *
+
+def test_make_app():
+ def application(environ, start_response):
+ start_response('200 OK', [('content-type', 'text/plain')])
+ lines = [
+ str(environ.get('REMOTE_USER')),
+ ':',
+ str(environ.get('REMOTE_USER_TOKENS')),
+ ]
+ if six.PY3:
+ lines = [line.encode('utf8') for line in lines]
+ return lines
+ ip_map = {
+ '127.0.0.1': (None, 'system'),
+ '192.168.0.0/16': (None, 'worker'),
+ '192.168.0.5<->192.168.0.8': ('bob', 'editor'),
+ '192.168.0.8': ('__remove__', '-worker'),
+ }
+ app = grantip.GrantIPMiddleware(application, ip_map)
+ app = TestApp(app)
+ return app
+
+def test_req():
+ app = test_make_app()
+ def doit(remote_addr):
+ res = app.get('/', extra_environ={'REMOTE_ADDR': remote_addr})
+ return res.body
+ assert doit('127.0.0.1') == b'None:system'
+ assert doit('192.168.15.12') == b'None:worker'
+ assert doit('192.168.0.4') == b'None:worker'
+ result = doit('192.168.0.5')
+ assert result.startswith(b'bob:')
+ assert b'editor' in result and b'worker' in result
+ assert result.count(b',') == 1
+ assert doit('192.168.0.8') == b'None:editor'
diff --git a/tests/test_gzipper.py b/tests/test_gzipper.py
new file mode 100644
index 0000000..54b7901
--- /dev/null
+++ b/tests/test_gzipper.py
@@ -0,0 +1,19 @@
+from paste.fixture import TestApp
+from paste.gzipper import middleware
+import gzip
+import six
+
+def simple_app(environ, start_response):
+ start_response('200 OK', [('content-type', 'text/plain')])
+ return [b'this is a test']
+
+wsgi_app = middleware(simple_app)
+app = TestApp(wsgi_app)
+
+def test_gzip():
+ res = app.get(
+ '/', extra_environ=dict(HTTP_ACCEPT_ENCODING='gzip'))
+ assert int(res.header('content-length')) == len(res.body)
+ assert res.body != b'this is a test'
+ actual = gzip.GzipFile(fileobj=six.BytesIO(res.body)).read()
+ assert actual == b'this is a test'
diff --git a/tests/test_httpheaders.py b/tests/test_httpheaders.py
new file mode 100644
index 0000000..8c560d2
--- /dev/null
+++ b/tests/test_httpheaders.py
@@ -0,0 +1,159 @@
+from paste.httpheaders import *
+import time
+
+def _test_generic(collection):
+ assert 'bing' == VIA(collection)
+ REFERER.update(collection,'internal:/some/path')
+ assert 'internal:/some/path' == REFERER(collection)
+ CACHE_CONTROL.update(collection,max_age=1234)
+ CONTENT_DISPOSITION.update(collection,filename="bingles.txt")
+ PRAGMA.update(collection,"test","multi",'valued="items"')
+ assert 'public, max-age=1234' == CACHE_CONTROL(collection)
+ assert 'attachment; filename="bingles.txt"' == \
+ CONTENT_DISPOSITION(collection)
+ assert 'test, multi, valued="items"' == PRAGMA(collection)
+ VIA.delete(collection)
+
+
+def test_environ():
+ collection = {'HTTP_VIA':'bing', 'wsgi.version': '1.0' }
+ _test_generic(collection)
+ assert collection == {'wsgi.version': '1.0',
+ 'HTTP_PRAGMA': 'test, multi, valued="items"',
+ 'HTTP_REFERER': 'internal:/some/path',
+ 'HTTP_CONTENT_DISPOSITION': 'attachment; filename="bingles.txt"',
+ 'HTTP_CACHE_CONTROL': 'public, max-age=1234'
+ }
+
+def test_environ_cgi():
+ environ = {'CONTENT_TYPE': 'text/plain', 'wsgi.version': '1.0',
+ 'HTTP_CONTENT_TYPE': 'ignored/invalid',
+ 'CONTENT_LENGTH': '200'}
+ assert 'text/plain' == CONTENT_TYPE(environ)
+ assert '200' == CONTENT_LENGTH(environ)
+ CONTENT_TYPE.update(environ,'new/type')
+ assert 'new/type' == CONTENT_TYPE(environ)
+ CONTENT_TYPE.delete(environ)
+ assert '' == CONTENT_TYPE(environ)
+ assert 'ignored/invalid' == environ['HTTP_CONTENT_TYPE']
+
+def test_response_headers():
+ collection = [('via', 'bing')]
+ _test_generic(collection)
+ normalize_headers(collection)
+ assert collection == [
+ ('Cache-Control', 'public, max-age=1234'),
+ ('Pragma', 'test, multi, valued="items"'),
+ ('Referer', 'internal:/some/path'),
+ ('Content-Disposition', 'attachment; filename="bingles.txt"')
+ ]
+
+def test_cache_control():
+ assert 'public' == CACHE_CONTROL()
+ assert 'public' == CACHE_CONTROL(public=True)
+ assert 'private' == CACHE_CONTROL(private=True)
+ assert 'no-cache' == CACHE_CONTROL(no_cache=True)
+ assert 'private, no-store' == CACHE_CONTROL(private=True, no_store=True)
+ assert 'public, max-age=60' == CACHE_CONTROL(max_age=60)
+ assert 'public, max-age=86400' == \
+ CACHE_CONTROL(max_age=CACHE_CONTROL.ONE_DAY)
+ CACHE_CONTROL.extensions['community'] = str
+ assert 'public, community="bingles"' == \
+ CACHE_CONTROL(community="bingles")
+ headers = []
+ CACHE_CONTROL.apply(headers,max_age=60)
+ assert 'public, max-age=60' == CACHE_CONTROL(headers)
+ assert EXPIRES.parse(headers) > time.time()
+ assert EXPIRES.parse(headers) < time.time() + 60
+
+def test_content_disposition():
+ assert 'attachment' == CONTENT_DISPOSITION()
+ assert 'attachment' == CONTENT_DISPOSITION(attachment=True)
+ assert 'inline' == CONTENT_DISPOSITION(inline=True)
+ assert 'inline; filename="test.txt"' == \
+ CONTENT_DISPOSITION(inline=True, filename="test.txt")
+ assert 'attachment; filename="test.txt"' == \
+ CONTENT_DISPOSITION(filename="/some/path/test.txt")
+ headers = []
+ CONTENT_DISPOSITION.apply(headers,filename="test.txt")
+ assert 'text/plain' == CONTENT_TYPE(headers)
+ CONTENT_DISPOSITION.apply(headers,filename="test")
+ assert 'text/plain' == CONTENT_TYPE(headers)
+ CONTENT_DISPOSITION.apply(headers,filename="test.html")
+ assert 'text/plain' == CONTENT_TYPE(headers)
+ headers = [('Content-Type', 'application/octet-stream')]
+ CONTENT_DISPOSITION.apply(headers,filename="test.txt")
+ assert 'text/plain' == CONTENT_TYPE(headers)
+ assert headers == [
+ ('Content-Type', 'text/plain'),
+ ('Content-Disposition', 'attachment; filename="test.txt"')
+ ]
+
+def test_range():
+ assert ('bytes',[(0,300)]) == RANGE.parse("bytes=0-300")
+ assert ('bytes',[(0,300)]) == RANGE.parse("bytes = -300")
+ assert ('bytes',[(0,None)]) == RANGE.parse("bytes= -")
+ assert ('bytes',[(0,None)]) == RANGE.parse("bytes=0 - ")
+ assert ('bytes',[(300,None)]) == RANGE.parse(" BYTES=300-")
+ assert ('bytes',[(4,5),(6,7)]) == RANGE.parse(" Bytes = 4 - 5,6 - 07 ")
+ assert ('bytes',[(0,5),(7,None)]) == RANGE.parse(" bytes=-5,7-")
+ assert ('bytes',[(0,5),(7,None)]) == RANGE.parse(" bytes=-5,7-")
+ assert ('bytes',[(0,5),(7,None)]) == RANGE.parse(" bytes=-5,7-")
+ assert None == RANGE.parse("")
+ assert None == RANGE.parse("bytes=0,300")
+ assert None == RANGE.parse("bytes=-7,5-")
+
+def test_copy():
+ environ = {'HTTP_VIA':'bing', 'wsgi.version': '1.0' }
+ response_headers = []
+ VIA.update(response_headers,environ)
+ assert response_headers == [('Via', 'bing')]
+
+def test_sorting():
+ # verify the HTTP_HEADERS are set with their canonical form
+ sample = [WWW_AUTHENTICATE, VIA, ACCEPT, DATE,
+ ACCEPT_CHARSET, AGE, ALLOW, CACHE_CONTROL,
+ CONTENT_ENCODING, ETAG, CONTENT_TYPE, FROM,
+ EXPIRES, RANGE, UPGRADE, VARY, ALLOW]
+ sample.sort()
+ sample = [str(x) for x in sample]
+ assert sample == [
+ # general headers first
+ 'Cache-Control', 'Date', 'Upgrade', 'Via',
+ # request headers next
+ 'Accept', 'Accept-Charset', 'From', 'Range',
+ # response headers following
+ 'Age', 'ETag', 'Vary', 'WWW-Authenticate',
+ # entity headers (/w expected duplicate)
+ 'Allow', 'Allow', 'Content-Encoding', 'Content-Type', 'Expires'
+ ]
+
+def test_normalize():
+ response_headers = [
+ ('www-authenticate','Response AuthMessage'),
+ ('unknown-header','Unknown Sorted Last'),
+ ('Via','General Bingles'),
+ ('aLLoW','Entity Allow Something'),
+ ('ETAG','Response 34234'),
+ ('expires','Entity An-Expiration-Date'),
+ ('date','General A-Date')]
+ normalize_headers(response_headers, strict=False)
+ assert response_headers == [
+ ('Date', 'General A-Date'),
+ ('Via', 'General Bingles'),
+ ('ETag', 'Response 34234'),
+ ('WWW-Authenticate', 'Response AuthMessage'),
+ ('Allow', 'Entity Allow Something'),
+ ('Expires', 'Entity An-Expiration-Date'),
+ ('Unknown-Header', 'Unknown Sorted Last')]
+
+def test_if_modified_since():
+ from paste.httpexceptions import HTTPBadRequest
+ date = 'Thu, 34 Jul 3119 29:34:18 GMT'
+ try:
+ x = IF_MODIFIED_SINCE.parse({'HTTP_IF_MODIFIED_SINCE': date,
+ 'wsgi.version': (1, 0)})
+ except HTTPBadRequest:
+ pass
+ else:
+ assert 0
diff --git a/tests/test_httpserver.py b/tests/test_httpserver.py
new file mode 100644
index 0000000..3d72c79
--- /dev/null
+++ b/tests/test_httpserver.py
@@ -0,0 +1,45 @@
+import email
+
+from paste.httpserver import WSGIHandler
+from six.moves import StringIO
+
+
+class MockServer(object):
+ server_address = ('127.0.0.1', 80)
+
+
+class MockSocket(object):
+ def makefile(self, mode, bufsize):
+ return StringIO()
+
+
+def test_environ():
+ mock_socket = MockSocket()
+ mock_client_address = '1.2.3.4'
+ mock_server = MockServer()
+
+ wsgi_handler = WSGIHandler(mock_socket, mock_client_address, mock_server)
+ wsgi_handler.command = 'GET'
+ wsgi_handler.path = '/path'
+ wsgi_handler.request_version = 'HTTP/1.0'
+ wsgi_handler.headers = email.message_from_string('Host: mywebsite')
+
+ wsgi_handler.wsgi_setup()
+
+ assert wsgi_handler.wsgi_environ['HTTP_HOST'] == 'mywebsite'
+
+
+def test_environ_with_multiple_values():
+ mock_socket = MockSocket()
+ mock_client_address = '1.2.3.4'
+ mock_server = MockServer()
+
+ wsgi_handler = WSGIHandler(mock_socket, mock_client_address, mock_server)
+ wsgi_handler.command = 'GET'
+ wsgi_handler.path = '/path'
+ wsgi_handler.request_version = 'HTTP/1.0'
+ wsgi_handler.headers = email.message_from_string('Host: host1\nHost: host2')
+
+ wsgi_handler.wsgi_setup()
+
+ assert wsgi_handler.wsgi_environ['HTTP_HOST'] == 'host1,host2'
diff --git a/tests/test_import_string.py b/tests/test_import_string.py
new file mode 100644
index 0000000..262cbdd
--- /dev/null
+++ b/tests/test_import_string.py
@@ -0,0 +1,16 @@
+from paste.util.import_string import *
+import sys
+import os
+
+def test_simple():
+ for func in eval_import, simple_import:
+ assert func('sys') is sys
+ assert func('sys.version') is sys.version
+ assert func('os.path.join') is os.path.join
+
+def test_complex():
+ assert eval_import('sys:version') is sys.version
+ assert eval_import('os:getcwd()') == os.getcwd()
+ assert (eval_import('sys:version.split()[0]') ==
+ sys.version.split()[0])
+
diff --git a/tests/test_multidict.py b/tests/test_multidict.py
new file mode 100644
index 0000000..50a746f
--- /dev/null
+++ b/tests/test_multidict.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# (c) 2007 Ian Bicking and Philip Jenvey; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+import cgi
+import six
+from six.moves import StringIO
+
+from nose.tools import assert_raises
+
+from paste.util.multidict import MultiDict, UnicodeMultiDict
+
+def test_dict():
+ d = MultiDict({'a': 1})
+ assert d.items() == [('a', 1)]
+
+ d['b'] = 2
+ d['c'] = 3
+ assert d.items() == [('a', 1), ('b', 2), ('c', 3)]
+
+ d['b'] = 4
+ assert d.items() == [('a', 1), ('c', 3), ('b', 4)]
+
+ d.add('b', 5)
+ assert_raises(KeyError, d.getone, "b")
+ assert d.getall('b') == [4, 5]
+ assert d.items() == [('a', 1), ('c', 3), ('b', 4), ('b', 5)]
+
+ del d['b']
+ assert d.items() == [('a', 1), ('c', 3)]
+ assert d.pop('xxx', 5) == 5
+ assert d.getone('a') == 1
+ assert d.popitem() == ('c', 3)
+ assert d.items() == [('a', 1)]
+
+ item = []
+ assert d.setdefault('z', item) is item
+ assert d.items() == [('a', 1), ('z', item)]
+
+ assert d.setdefault('y', 6) == 6
+
+ assert d.mixed() == {'a': 1, 'y': 6, 'z': item}
+ assert d.dict_of_lists() == {'a': [1], 'y': [6], 'z': [item]}
+
+ assert 'a' in d
+ dcopy = d.copy()
+ assert dcopy is not d
+ assert dcopy == d
+ d['x'] = 'x test'
+ assert dcopy != d
+
+ d[(1, None)] = (None, 1)
+ assert d.items() == [('a', 1), ('z', []), ('y', 6), ('x', 'x test'),
+ ((1, None), (None, 1))]
+
+def test_unicode_dict():
+ _test_unicode_dict()
+ _test_unicode_dict(decode_param_names=True)
+
+def _test_unicode_dict(decode_param_names=False):
+ d = UnicodeMultiDict(MultiDict({b'a': 'a test'}))
+ d.encoding = 'utf-8'
+ d.errors = 'ignore'
+
+ if decode_param_names:
+ key_str = six.text_type
+ k = lambda key: key
+ d.decode_keys = True
+ else:
+ key_str = six.binary_type
+ k = lambda key: key.encode()
+
+ def assert_unicode(obj):
+ assert isinstance(obj, six.text_type)
+
+ def assert_key_str(obj):
+ assert isinstance(obj, key_str)
+
+ def assert_unicode_item(obj):
+ key, value = obj
+ assert isinstance(key, key_str)
+ assert isinstance(value, six.text_type)
+
+ assert d.items() == [(k('a'), u'a test')]
+ map(assert_key_str, d.keys())
+ map(assert_unicode, d.values())
+
+ d[b'b'] = b'2 test'
+ d[b'c'] = b'3 test'
+ assert d.items() == [(k('a'), u'a test'), (k('b'), u'2 test'), (k('c'), u'3 test')]
+ list(map(assert_unicode_item, d.items()))
+
+ d[k('b')] = b'4 test'
+ assert d.items() == [(k('a'), u'a test'), (k('c'), u'3 test'), (k('b'), u'4 test')], d.items()
+ list(map(assert_unicode_item, d.items()))
+
+ d.add(k('b'), b'5 test')
+ assert_raises(KeyError, d.getone, k("b"))
+ assert d.getall(k('b')) == [u'4 test', u'5 test']
+ map(assert_unicode, d.getall('b'))
+ assert d.items() == [(k('a'), u'a test'), (k('c'), u'3 test'), (k('b'), u'4 test'),
+ (k('b'), u'5 test')]
+ list(map(assert_unicode_item, d.items()))
+
+ del d[k('b')]
+ assert d.items() == [(k('a'), u'a test'), (k('c'), u'3 test')]
+ list(map(assert_unicode_item, d.items()))
+ assert d.pop('xxx', u'5 test') == u'5 test'
+ assert isinstance(d.pop('xxx', u'5 test'), six.text_type)
+ assert d.getone(k('a')) == u'a test'
+ assert isinstance(d.getone(k('a')), six.text_type)
+ assert d.popitem() == (k('c'), u'3 test')
+ d[k('c')] = b'3 test'
+ assert_unicode_item(d.popitem())
+ assert d.items() == [(k('a'), u'a test')]
+ list(map(assert_unicode_item, d.items()))
+
+ item = []
+ assert d.setdefault(k('z'), item) is item
+ items = d.items()
+ assert items == [(k('a'), u'a test'), (k('z'), item)]
+ assert isinstance(items[1][0], key_str)
+ assert isinstance(items[1][1], list)
+
+ assert isinstance(d.setdefault(k('y'), b'y test'), six.text_type)
+ assert isinstance(d[k('y')], six.text_type)
+
+ assert d.mixed() == {k('a'): u'a test', k('y'): u'y test', k('z'): item}
+ assert d.dict_of_lists() == {k('a'): [u'a test'], k('y'): [u'y test'],
+ k('z'): [item]}
+ del d[k('z')]
+ list(map(assert_unicode_item, six.iteritems(d.mixed())))
+ list(map(assert_unicode_item, [(key, value[0]) for \
+ key, value in six.iteritems(d.dict_of_lists())]))
+
+ assert k('a') in d
+ dcopy = d.copy()
+ assert dcopy is not d
+ assert dcopy == d
+ d[k('x')] = 'x test'
+ assert dcopy != d
+
+ d[(1, None)] = (None, 1)
+ assert d.items() == [(k('a'), u'a test'), (k('y'), u'y test'), (k('x'), u'x test'),
+ ((1, None), (None, 1))]
+ item = d.items()[-1]
+ assert isinstance(item[0], tuple)
+ assert isinstance(item[1], tuple)
+
+ fs = cgi.FieldStorage()
+ fs.name = 'thefile'
+ fs.filename = 'hello.txt'
+ fs.file = StringIO('hello')
+ d[k('f')] = fs
+ ufs = d[k('f')]
+ assert isinstance(ufs, cgi.FieldStorage)
+ assert ufs is not fs
+ assert ufs.name == fs.name
+ assert isinstance(ufs.name, str if six.PY3 else key_str)
+ assert ufs.filename == fs.filename
+ assert isinstance(ufs.filename, six.text_type)
+ assert isinstance(ufs.value, str)
+ assert ufs.value == 'hello'
diff --git a/tests/test_profilemiddleware.py b/tests/test_profilemiddleware.py
new file mode 100644
index 0000000..4c189f8
--- /dev/null
+++ b/tests/test_profilemiddleware.py
@@ -0,0 +1,29 @@
+from paste.fixture import *
+try:
+ from paste.debug.profile import *
+ disable = False
+except ImportError:
+ disable = True
+
+if not disable:
+ def simple_app(environ, start_response):
+ start_response('200 OK', [('content-type', 'text/html')])
+ return ['all ok']
+
+ def long_func():
+ for i in range(1000):
+ pass
+ return 'test'
+
+ def test_profile():
+ app = TestApp(ProfileMiddleware(simple_app, {}))
+ res = app.get('/')
+ # The original app:
+ res.mustcontain('all ok')
+ # The profile information:
+ res.mustcontain('<pre')
+
+ def test_decorator():
+ value = profile_decorator()(long_func)()
+ assert value == 'test'
+
diff --git a/tests/test_proxy.py b/tests/test_proxy.py
new file mode 100644
index 0000000..44db9f3
--- /dev/null
+++ b/tests/test_proxy.py
@@ -0,0 +1,12 @@
+from paste import proxy
+from paste.fixture import TestApp
+
+def test_paste_website():
+ # Not the most robust test...
+ # need to test things like POSTing to pages, and getting from pages
+ # that don't set content-length.
+ app = proxy.Proxy('http://pythonpaste.org')
+ app = TestApp(app)
+ res = app.get('/')
+ assert 'documentation' in res
+
diff --git a/tests/test_recursive.py b/tests/test_recursive.py
new file mode 100644
index 0000000..1cb1984
--- /dev/null
+++ b/tests/test_recursive.py
@@ -0,0 +1,105 @@
+from .test_errordocument import simple_app
+from paste.fixture import *
+from paste.recursive import RecursiveMiddleware, ForwardRequestException
+
+def error_docs_app(environ, start_response):
+ if environ['PATH_INFO'] == '/not_found':
+ start_response("404 Not found", [('Content-type', 'text/plain')])
+ return [b'Not found']
+ elif environ['PATH_INFO'] == '/error':
+ start_response("200 OK", [('Content-type', 'text/plain')])
+ return [b'Page not found']
+ elif environ['PATH_INFO'] == '/recurse':
+ raise ForwardRequestException('/recurse')
+ else:
+ return simple_app(environ, start_response)
+
+class Middleware(object):
+ def __init__(self, app, url='/error'):
+ self.app = app
+ self.url = url
+ def __call__(self, environ, start_response):
+ raise ForwardRequestException(self.url)
+
+def forward(app):
+ app = TestApp(RecursiveMiddleware(app))
+ res = app.get('')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'requested page returned' in res
+ res = app.get('/error')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'Page not found' in res
+ res = app.get('/not_found')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'Page not found' in res
+ try:
+ res = app.get('/recurse')
+ except AssertionError as e:
+ if str(e).startswith('Forwarding loop detected'):
+ pass
+ else:
+ raise AssertionError('Failed to detect forwarding loop')
+
+def test_ForwardRequest_url():
+ class TestForwardRequestMiddleware(Middleware):
+ def __call__(self, environ, start_response):
+ if environ['PATH_INFO'] != '/not_found':
+ return self.app(environ, start_response)
+ raise ForwardRequestException(self.url)
+ forward(TestForwardRequestMiddleware(error_docs_app))
+
+def test_ForwardRequest_environ():
+ class TestForwardRequestMiddleware(Middleware):
+ def __call__(self, environ, start_response):
+ if environ['PATH_INFO'] != '/not_found':
+ return self.app(environ, start_response)
+ environ['PATH_INFO'] = self.url
+ raise ForwardRequestException(environ=environ)
+ forward(TestForwardRequestMiddleware(error_docs_app))
+
+def test_ForwardRequest_factory():
+
+ from paste.errordocument import StatusKeeper
+
+ class TestForwardRequestMiddleware(Middleware):
+ def __call__(self, environ, start_response):
+ if environ['PATH_INFO'] != '/not_found':
+ return self.app(environ, start_response)
+ environ['PATH_INFO'] = self.url
+ def factory(app):
+ return StatusKeeper(app, status='404 Not Found', url='/error', headers=[])
+ raise ForwardRequestException(factory=factory)
+
+ app = TestForwardRequestMiddleware(error_docs_app)
+ app = TestApp(RecursiveMiddleware(app))
+ res = app.get('')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'requested page returned' in res
+ res = app.get('/error')
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '200 OK'
+ assert 'Page not found' in res
+ res = app.get('/not_found', status=404)
+ assert res.header('content-type') == 'text/plain'
+ assert res.full_status == '404 Not Found' # Different status
+ assert 'Page not found' in res
+ try:
+ res = app.get('/recurse')
+ except AssertionError as e:
+ if str(e).startswith('Forwarding loop detected'):
+ pass
+ else:
+ raise AssertionError('Failed to detect forwarding loop')
+
+# Test Deprecated Code
+def test_ForwardRequestException():
+ class TestForwardRequestExceptionMiddleware(Middleware):
+ def __call__(self, environ, start_response):
+ if environ['PATH_INFO'] != '/not_found':
+ return self.app(environ, start_response)
+ raise ForwardRequestException(path_info=self.url)
+ forward(TestForwardRequestExceptionMiddleware(error_docs_app))
diff --git a/tests/test_registry.py b/tests/test_registry.py
new file mode 100644
index 0000000..23cd9b6
--- /dev/null
+++ b/tests/test_registry.py
@@ -0,0 +1,314 @@
+# (c) 2005 Ben Bangert
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+from nose.tools import assert_raises
+
+from paste.fixture import *
+from paste.registry import *
+from paste.registry import Registry
+from paste.evalexception.middleware import EvalException
+
+regobj = StackedObjectProxy()
+secondobj = StackedObjectProxy(default=dict(hi='people'))
+
+def simpleapp(environ, start_response):
+ status = '200 OK'
+ response_headers = [('Content-type','text/plain')]
+ start_response(status, response_headers)
+ return [b'Hello world!\n']
+
+def simpleapp_withregistry(environ, start_response):
+ status = '200 OK'
+ response_headers = [('Content-type','text/plain')]
+ start_response(status, response_headers)
+ body = 'Hello world!Value is %s\n' % regobj.keys()
+ if six.PY3:
+ body = body.encode('utf8')
+ return [body]
+
+def simpleapp_withregistry_default(environ, start_response):
+ status = '200 OK'
+ response_headers = [('Content-type','text/plain')]
+ start_response(status, response_headers)
+ body = 'Hello world!Value is %s\n' % secondobj
+ if six.PY3:
+ body = body.encode('utf8')
+ return [body]
+
+
+class RegistryUsingApp(object):
+ def __init__(self, var, value, raise_exc=False):
+ self.var = var
+ self.value = value
+ self.raise_exc = raise_exc
+
+ def __call__(self, environ, start_response):
+ if 'paste.registry' in environ:
+ environ['paste.registry'].register(self.var, self.value)
+ if self.raise_exc:
+ raise self.raise_exc
+ status = '200 OK'
+ response_headers = [('Content-type','text/plain')]
+ start_response(status, response_headers)
+ body = 'Hello world!\nThe variable is %s' % str(regobj)
+ if six.PY3:
+ body = body.encode('utf8')
+ return [body]
+
+class RegistryUsingIteratorApp(object):
+ def __init__(self, var, value):
+ self.var = var
+ self.value = value
+
+ def __call__(self, environ, start_response):
+ if 'paste.registry' in environ:
+ environ['paste.registry'].register(self.var, self.value)
+ status = '200 OK'
+ response_headers = [('Content-type','text/plain')]
+ start_response(status, response_headers)
+ body = 'Hello world!\nThe variable is %s' % str(regobj)
+ if six.PY3:
+ body = body.encode('utf8')
+ return iter([body])
+
+class RegistryMiddleMan(object):
+ def __init__(self, app, var, value, depth):
+ self.app = app
+ self.var = var
+ self.value = value
+ self.depth = depth
+
+ def __call__(self, environ, start_response):
+ if 'paste.registry' in environ:
+ environ['paste.registry'].register(self.var, self.value)
+ line = ('\nInserted by middleware!\nInsertValue at depth %s is %s'
+ % (self.depth, str(regobj)))
+ if six.PY3:
+ line = line.encode('utf8')
+ app_response = [line]
+ app_iter = None
+ app_iter = self.app(environ, start_response)
+ if type(app_iter) in (list, tuple):
+ app_response.extend(app_iter)
+ else:
+ response = []
+ for line in app_iter:
+ response.append(line)
+ if hasattr(app_iter, 'close'):
+ app_iter.close()
+ app_response.extend(response)
+ line = ('\nAppended by middleware!\nAppendValue at \
+ depth %s is %s' % (self.depth, str(regobj)))
+ if six.PY3:
+ line = line.encode('utf8')
+ app_response.append(line)
+ return app_response
+
+
+def test_simple():
+ app = TestApp(simpleapp)
+ response = app.get('/')
+ assert 'Hello world' in response
+
+def test_solo_registry():
+ obj = {'hi':'people'}
+ wsgiapp = RegistryUsingApp(regobj, obj)
+ wsgiapp = RegistryManager(wsgiapp)
+ app = TestApp(wsgiapp)
+ res = app.get('/')
+ assert 'Hello world' in res
+ assert 'The variable is' in res
+ assert "{'hi': 'people'}" in res
+
+def test_registry_no_object_error():
+ app = TestApp(simpleapp_withregistry)
+ assert_raises(TypeError, app.get, '/')
+
+def test_with_default_object():
+ app = TestApp(simpleapp_withregistry_default)
+ res = app.get('/')
+ print(res)
+ assert 'Hello world' in res
+ assert "Value is {'hi': 'people'}" in res
+
+def test_double_registry():
+ obj = {'hi':'people'}
+ secondobj = {'bye':'friends'}
+ wsgiapp = RegistryUsingApp(regobj, obj)
+ wsgiapp = RegistryManager(wsgiapp)
+ wsgiapp = RegistryMiddleMan(wsgiapp, regobj, secondobj, 0)
+ wsgiapp = RegistryManager(wsgiapp)
+ app = TestApp(wsgiapp)
+ res = app.get('/')
+ assert 'Hello world' in res
+ assert 'The variable is' in res
+ assert "{'hi': 'people'}" in res
+ assert "InsertValue at depth 0 is {'bye': 'friends'}" in res
+ assert "AppendValue at depth 0 is {'bye': 'friends'}" in res
+
+def test_really_deep_registry():
+ keylist = ['fred', 'wilma', 'barney', 'homer', 'marge', 'bart', 'lisa',
+ 'maggie']
+ valuelist = range(0, len(keylist))
+ obj = {'hi':'people'}
+ wsgiapp = RegistryUsingApp(regobj, obj)
+ wsgiapp = RegistryManager(wsgiapp)
+ for depth in valuelist:
+ newobj = {keylist[depth]: depth}
+ wsgiapp = RegistryMiddleMan(wsgiapp, regobj, newobj, depth)
+ wsgiapp = RegistryManager(wsgiapp)
+ app = TestApp(wsgiapp)
+ res = app.get('/')
+ assert 'Hello world' in res
+ assert 'The variable is' in res
+ assert "{'hi': 'people'}" in res
+ for depth in valuelist:
+ assert "InsertValue at depth %s is {'%s': %s}" % \
+ (depth, keylist[depth], depth) in res
+ for depth in valuelist:
+ assert "AppendValue at depth %s is {'%s': %s}" % \
+ (depth, keylist[depth], depth) in res
+
+def test_iterating_response():
+ obj = {'hi':'people'}
+ secondobj = {'bye':'friends'}
+ wsgiapp = RegistryUsingIteratorApp(regobj, obj)
+ wsgiapp = RegistryManager(wsgiapp)
+ wsgiapp = RegistryMiddleMan(wsgiapp, regobj, secondobj, 0)
+ wsgiapp = RegistryManager(wsgiapp)
+ app = TestApp(wsgiapp)
+ res = app.get('/')
+ assert 'Hello world' in res
+ assert 'The variable is' in res
+ assert "{'hi': 'people'}" in res
+ assert "InsertValue at depth 0 is {'bye': 'friends'}" in res
+ assert "AppendValue at depth 0 is {'bye': 'friends'}" in res
+
+def _test_restorer(stack, data):
+ # We need to test the request's specific Registry. Initialize it here so we
+ # can use it later (RegistryManager will re-use one preexisting in the
+ # environ)
+ registry = Registry()
+ extra_environ={'paste.throw_errors': False,
+ 'paste.registry': registry}
+ request_id = restorer.get_request_id(extra_environ)
+ app = TestApp(stack)
+ res = app.get('/', extra_environ=extra_environ, expect_errors=True)
+
+ # Ensure all the StackedObjectProxies are empty after the RegistryUsingApp
+ # raises an Exception
+ for stacked, proxied_obj, test_cleanup in data:
+ only_key = list(proxied_obj.keys())[0]
+ try:
+ assert only_key not in stacked
+ assert False
+ except TypeError:
+ # Definitely empty
+ pass
+
+ # Ensure the StackedObjectProxies & Registry 'work' in the simulated
+ # EvalException context
+ replace = {'replace': 'dict'}
+ new = {'new': 'object'}
+ restorer.restoration_begin(request_id)
+ try:
+ for stacked, proxied_obj, test_cleanup in data:
+ # Ensure our original data magically re-appears in this context
+ only_key, only_val = list(proxied_obj.items())[0]
+ assert only_key in stacked and stacked[only_key] == only_val
+
+ # Ensure the Registry still works
+ registry.prepare()
+ registry.register(stacked, new)
+ assert 'new' in stacked and stacked['new'] == 'object'
+ registry.cleanup()
+
+ # Back to the original (pre-prepare())
+ assert only_key in stacked and stacked[only_key] == only_val
+
+ registry.replace(stacked, replace)
+ assert 'replace' in stacked and stacked['replace'] == 'dict'
+
+ if test_cleanup:
+ registry.cleanup()
+ try:
+ stacked._current_obj()
+ assert False
+ except TypeError:
+ # Definitely empty
+ pass
+ finally:
+ restorer.restoration_end()
+
+def _restorer_data():
+ S = StackedObjectProxy
+ d = [[S(name='first'), dict(top='of the registry stack'), False],
+ [S(name='second'), dict(middle='of the stack'), False],
+ [S(name='third'), dict(bottom='of the STACK.'), False]]
+ return d
+
+def _set_cleanup_test(data):
+ """Instruct _test_restorer to check registry cleanup at this level of the stack
+ """
+ data[2] = True
+
+def test_restorer_basic():
+ data = _restorer_data()[0]
+ wsgiapp = RegistryUsingApp(data[0], data[1], raise_exc=Exception())
+ wsgiapp = RegistryManager(wsgiapp)
+ _set_cleanup_test(data)
+ wsgiapp = EvalException(wsgiapp)
+ _test_restorer(wsgiapp, [data])
+
+def test_restorer_basic_manager_outside():
+ data = _restorer_data()[0]
+ wsgiapp = RegistryUsingApp(data[0], data[1], raise_exc=Exception())
+ wsgiapp = EvalException(wsgiapp)
+ wsgiapp = RegistryManager(wsgiapp)
+ _set_cleanup_test(data)
+ _test_restorer(wsgiapp, [data])
+
+def test_restorer_middleman_nested_evalexception():
+ data = _restorer_data()[:2]
+ wsgiapp = RegistryUsingApp(data[0][0], data[0][1], raise_exc=Exception())
+ wsgiapp = EvalException(wsgiapp)
+ wsgiapp = RegistryMiddleMan(wsgiapp, data[1][0], data[1][1], 0)
+ wsgiapp = RegistryManager(wsgiapp)
+ _set_cleanup_test(data[1])
+ _test_restorer(wsgiapp, data)
+
+def test_restorer_nested_middleman():
+ data = _restorer_data()[:2]
+ wsgiapp = RegistryUsingApp(data[0][0], data[0][1], raise_exc=Exception())
+ wsgiapp = RegistryManager(wsgiapp)
+ _set_cleanup_test(data[0])
+ wsgiapp = RegistryMiddleMan(wsgiapp, data[1][0], data[1][1], 0)
+ wsgiapp = EvalException(wsgiapp)
+ wsgiapp = RegistryManager(wsgiapp)
+ _set_cleanup_test(data[1])
+ _test_restorer(wsgiapp, data)
+
+def test_restorer_middlemen_nested_evalexception():
+ data = _restorer_data()
+ wsgiapp = RegistryUsingApp(data[0][0], data[0][1], raise_exc=Exception())
+ wsgiapp = RegistryManager(wsgiapp)
+ _set_cleanup_test(data[0])
+ wsgiapp = EvalException(wsgiapp)
+ wsgiapp = RegistryMiddleMan(wsgiapp, data[1][0], data[1][1], 0)
+ wsgiapp = RegistryManager(wsgiapp)
+ _set_cleanup_test(data[1])
+ wsgiapp = RegistryMiddleMan(wsgiapp, data[2][0], data[2][1], 1)
+ wsgiapp = RegistryManager(wsgiapp)
+ _set_cleanup_test(data[2])
+ _test_restorer(wsgiapp, data)
+
+def test_restorer_disabled():
+ # Ensure restoration_begin/end work safely when there's no Registry
+ wsgiapp = TestApp(simpleapp)
+ wsgiapp.get('/')
+ try:
+ restorer.restoration_begin(1)
+ finally:
+ restorer.restoration_end()
+ # A second call should do nothing
+ restorer.restoration_end()
diff --git a/tests/test_request.py b/tests/test_request.py
new file mode 100644
index 0000000..072304d
--- /dev/null
+++ b/tests/test_request.py
@@ -0,0 +1,66 @@
+# (c) 2005 Ben Bangert
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+from paste.fixture import *
+from paste.request import *
+from paste.wsgiwrappers import WSGIRequest
+import six
+
+def simpleapp(environ, start_response):
+ status = '200 OK'
+ response_headers = [('Content-type','text/plain')]
+ start_response(status, response_headers)
+ request = WSGIRequest(environ)
+ body = [
+ 'Hello world!\n', 'The get is %s' % str(request.GET),
+ ' and Val is %s\n' % request.GET.get('name'),
+ 'The languages are: %s\n' % request.languages,
+ 'The accepttypes is: %s\n' % request.match_accept(['text/html', 'application/xml'])]
+ if six.PY3:
+ body = [line.encode('utf8') for line in body]
+ return body
+
+def test_gets():
+ app = TestApp(simpleapp)
+ res = app.get('/')
+ assert 'Hello' in res
+ assert "get is MultiDict([])" in res
+
+ res = app.get('/?name=george')
+ res.mustcontain("get is MultiDict([('name', 'george')])")
+ res.mustcontain("Val is george")
+
+def test_language_parsing():
+ app = TestApp(simpleapp)
+ res = app.get('/')
+ assert "The languages are: ['en-us']" in res
+
+ res = app.get('/', headers={'Accept-Language':'da, en-gb;q=0.8, en;q=0.7'})
+ assert "languages are: ['da', 'en-gb', 'en', 'en-us']" in res
+
+ res = app.get('/', headers={'Accept-Language':'en-gb;q=0.8, da, en;q=0.7'})
+ assert "languages are: ['da', 'en-gb', 'en', 'en-us']" in res
+
+def test_mime_parsing():
+ app = TestApp(simpleapp)
+ res = app.get('/', headers={'Accept':'text/html'})
+ assert "accepttypes is: ['text/html']" in res
+
+ res = app.get('/', headers={'Accept':'application/xml'})
+ assert "accepttypes is: ['application/xml']" in res
+
+ res = app.get('/', headers={'Accept':'application/xml,*/*'})
+ assert "accepttypes is: ['text/html', 'application/xml']" in res
+
+def test_bad_cookie():
+ env = {}
+ env['HTTP_COOKIE'] = '070-it-:><?0'
+ assert get_cookie_dict(env) == {}
+ env['HTTP_COOKIE'] = 'foo=bar'
+ assert get_cookie_dict(env) == {'foo': 'bar'}
+ env['HTTP_COOKIE'] = '...'
+ assert get_cookie_dict(env) == {}
+ env['HTTP_COOKIE'] = '=foo'
+ assert get_cookie_dict(env) == {}
+ env['HTTP_COOKIE'] = '?='
+ assert get_cookie_dict(env) == {}
diff --git a/tests/test_request_form.py b/tests/test_request_form.py
new file mode 100644
index 0000000..cf43721
--- /dev/null
+++ b/tests/test_request_form.py
@@ -0,0 +1,36 @@
+import six
+
+from paste.request import *
+from paste.util.multidict import MultiDict
+
+def test_parse_querystring():
+ e = {'QUERY_STRING': 'a=1&b=2&c=3&b=4'}
+ d = parse_querystring(e)
+ assert d == [('a', '1'), ('b', '2'), ('c', '3'), ('b', '4')]
+ assert e['paste.parsed_querystring'] == (
+ (d, e['QUERY_STRING']))
+ e = {'QUERY_STRING': 'a&b&c=&d=1'}
+ d = parse_querystring(e)
+ assert d == [('a', ''), ('b', ''), ('c', ''), ('d', '1')]
+
+def make_post(body):
+ e = {
+ 'CONTENT_TYPE': 'application/x-www-form-urlencoded',
+ 'CONTENT_LENGTH': str(len(body)),
+ 'REQUEST_METHOD': 'POST',
+ 'wsgi.input': six.BytesIO(body),
+ }
+ return e
+
+def test_parsevars():
+ e = make_post(b'a=1&b=2&c=3&b=4')
+ #cur_input = e['wsgi.input']
+ d = parse_formvars(e)
+ assert isinstance(d, MultiDict)
+ assert d == MultiDict([('a', '1'), ('b', '2'), ('c', '3'), ('b', '4')])
+ assert e['paste.parsed_formvars'] == (
+ (d, e['wsgi.input']))
+ # XXX: http://trac.pythonpaste.org/pythonpaste/ticket/125
+ #assert e['wsgi.input'] is not cur_input
+ #cur_input.seek(0)
+ #assert e['wsgi.input'].read() == cur_input.read()
diff --git a/tests/test_response.py b/tests/test_response.py
new file mode 100644
index 0000000..71f6f97
--- /dev/null
+++ b/tests/test_response.py
@@ -0,0 +1,11 @@
+from paste.response import *
+
+def test_replace_header():
+ h = [('content-type', 'text/plain'),
+ ('x-blah', 'foobar')]
+ replace_header(h, 'content-length', '10')
+ assert h[-1] == ('content-length', '10')
+ replace_header(h, 'Content-Type', 'text/html')
+ assert ('content-type', 'text/html') in h
+ assert ('content-type', 'text/plain') not in h
+
diff --git a/tests/test_session.py b/tests/test_session.py
new file mode 100644
index 0000000..b67bda5
--- /dev/null
+++ b/tests/test_session.py
@@ -0,0 +1,56 @@
+from paste.session import SessionMiddleware
+from paste.fixture import TestApp
+import six
+
+info = []
+
+def wsgi_app(environ, start_response):
+ pi = environ.get('PATH_INFO', '')
+ if pi in ('/get1', '/get2'):
+ if pi == '/get1':
+ sess = environ['paste.session.factory']()
+ start_response('200 OK', [('content-type', 'text/plain')])
+ if pi == '/get2':
+ sess = environ['paste.session.factory']()
+ if 'info' in sess:
+ body = str(sess['info'])
+ if six.PY3:
+ body = body.encode('utf8')
+ return [body]
+ else:
+ return [b'no-info']
+ if pi in ('/put1', '/put2'):
+ if pi == '/put1':
+ sess = environ['paste.session.factory']()
+ sess['info'] = info[0]
+ start_response('200 OK', [('content-type', 'text/plain')])
+ if pi == '/put2':
+ sess = environ['paste.session.factory']()
+ sess['info'] = info[0]
+ return [b'foo']
+
+wsgi_app = SessionMiddleware(wsgi_app)
+
+def test_app1():
+ app = TestApp(wsgi_app)
+ res = app.get('/get1')
+ assert res.body == b'no-info'
+ res = app.get('/get2')
+ assert res.body ==b'no-info'
+ info[:] = ['test']
+ res = app.get('/put1')
+ res = app.get('/get1')
+ assert res.body == b'test'
+ res = app.get('/get2')
+ assert res.body == b'test'
+
+def test_app2():
+ app = TestApp(wsgi_app)
+ info[:] = ['fluff']
+ res = app.get('/put2')
+ res = app.get('/get1')
+ assert res.body == b'fluff'
+ res = app.get('/get2')
+ assert res.body == b'fluff'
+
+
diff --git a/tests/test_template.txt b/tests/test_template.txt
new file mode 100644
index 0000000..1313d34
--- /dev/null
+++ b/tests/test_template.txt
@@ -0,0 +1,136 @@
+The templating language is fairly simple, just {{stuff}}. For
+example::
+
+ >>> from paste.util.template import Template, sub
+ >>> sub('Hi {{name}}', name='Ian')
+ 'Hi Ian'
+ >>> Template('Hi {{repr(name)}}').substitute(name='Ian')
+ "Hi 'Ian'"
+ >>> Template('Hi {{name+1}}').substitute(name='Ian') # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ TypeError: cannot concatenate 'str' and 'int' objects at line 1 column 6
+
+It also has Django-style piping::
+
+ >>> sub('Hi {{name|repr}}', name='Ian')
+ "Hi 'Ian'"
+
+Note that None shows up as an empty string::
+
+ >>> sub('Hi {{name}}', name=None)
+ 'Hi '
+
+And if/elif/else::
+
+ >>> t = Template('{{if x}}{{y}}{{else}}{{z}}{{endif}}')
+ >>> t.substitute(x=1, y=2, z=3)
+ '2'
+ >>> t.substitute(x=0, y=2, z=3)
+ '3'
+ >>> t = Template('{{if x > 0}}positive{{elif x < 0}}negative{{else}}zero{{endif}}')
+ >>> t.substitute(x=1), t.substitute(x=-10), t.substitute(x=0)
+ ('positive', 'negative', 'zero')
+
+Plus a for loop::
+
+ >>> t = Template('{{for i in x}}i={{i}}\n{{endfor}}')
+ >>> t.substitute(x=range(3))
+ 'i=0\ni=1\ni=2\n'
+ >>> t = Template('{{for a, b in sorted(z.items()):}}{{a}}={{b}},{{endfor}}')
+ >>> t.substitute(z={1: 2, 3: 4})
+ '1=2,3=4,'
+ >>> t = Template('{{for i in x}}{{if not i}}{{break}}'
+ ... '{{endif}}{{i}} {{endfor}}')
+ >>> t.substitute(x=[1, 2, 0, 3, 4])
+ '1 2 '
+ >>> t = Template('{{for i in x}}{{if not i}}{{continue}}'
+ ... '{{endif}}{{i}} {{endfor}}')
+ >>> t.substitute(x=[1, 2, 0, 3, 0, 4])
+ '1 2 3 4 '
+
+Also Python blocks::
+
+ >>> sub('{{py:\nx=1\n}}{{x}}')
+ '1'
+
+And some syntax errors::
+
+ >>> t = Template('{{if x}}', name='foo.html')
+ Traceback (most recent call last):
+ ...
+ TemplateError: No {{endif}} at line 1 column 3 in foo.html
+ >>> t = Template('{{for x}}', name='foo2.html')
+ Traceback (most recent call last):
+ ...
+ TemplateError: Bad for (no "in") in 'x' at line 1 column 3 in foo2.html
+
+There's also an HTMLTemplate that uses HTMLisms::
+
+ >>> from paste.util.template import HTMLTemplate, sub_html, html
+ >>> sub_html('hi {{name}}', name='<foo>')
+ 'hi &lt;foo&gt;'
+
+But if you don't want quoting to happen you can do::
+
+ >>> sub_html('hi {{name}}', name=html('<foo>'))
+ 'hi <foo>'
+ >>> sub_html('hi {{name|html}}', name='<foo>')
+ 'hi <foo>'
+
+Also a couple handy functions;:
+
+ >>> t = HTMLTemplate('<a href="article?id={{id|url}}" {{attr(class_=class_)}}>')
+ >>> t.substitute(id=1, class_='foo')
+ '<a href="article?id=1" class="foo">'
+ >>> t.substitute(id='with space', class_=None)
+ '<a href="article?id=with%20space" >'
+
+There's a handyish looper thing you can also use in your templates (or
+in Python, but it's more useful in templates generally)::
+
+ >>> from paste.util.looper import looper
+ >>> seq = ['apple', 'asparagus', 'Banana', 'orange']
+ >>> for loop, item in looper(seq):
+ ... if item == 'apple':
+ ... assert loop.first
+ ... elif item == 'orange':
+ ... assert loop.last
+ ... if loop.first_group(lambda i: i[0].upper()):
+ ... print('%s:' % item[0].upper())
+ ... print("%s %s" % (loop.number, item))
+ A:
+ 1 apple
+ 2 asparagus
+ B:
+ 3 Banana
+ O:
+ 4 orange
+
+It will also strip out empty lines, when there is a line that only
+contains a directive/statement (if/for, etc)::
+
+ >>> sub('{{if 1}}\n{{x}}\n{{endif}}\n', x=0)
+ '0\n'
+ >>> sub('{{if 1}}x={{x}}\n{{endif}}\n', x=1)
+ 'x=1\n'
+ >>> sub('{{if 1}}\nx={{x}}\n{{endif}}\n', x=1)
+ 'x=1\n'
+
+Lastly, there is a special directive that will create a default value
+for a variable, if no value is given::
+
+ >>> sub('{{default x=1}}{{x}}', x=2)
+ '2'
+ >>> sub('{{default x=1}}{{x}}')
+ '1'
+ >>> # The normal case:
+ >>> sub('{{x}}') # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ NameError: name 'x' is not defined at line 1 column 3
+
+And comments work::
+
+ >>> sub('Test=x{{#whatever}}')
+ 'Test=x'
diff --git a/tests/test_urlmap.py b/tests/test_urlmap.py
new file mode 100644
index 0000000..f7ec729
--- /dev/null
+++ b/tests/test_urlmap.py
@@ -0,0 +1,53 @@
+from paste.urlmap import *
+from paste.fixture import *
+import six
+
+def make_app(response_text):
+ def app(environ, start_response):
+ headers = [('Content-type', 'text/html')]
+ start_response('200 OK', headers)
+ body = response_text % environ
+ if six.PY3:
+ body = body.encode('ascii')
+ return [body]
+ return app
+
+def test_map():
+ mapper = URLMap({})
+ app = TestApp(mapper)
+ text = '%s script_name="%%(SCRIPT_NAME)s" path_info="%%(PATH_INFO)s"'
+ mapper[''] = make_app(text % 'root')
+ mapper['/foo'] = make_app(text % 'foo-only')
+ mapper['/foo/bar'] = make_app(text % 'foo:bar')
+ mapper['/f'] = make_app(text % 'f-only')
+ res = app.get('/')
+ res.mustcontain('root')
+ res.mustcontain('script_name=""')
+ res.mustcontain('path_info="/"')
+ res = app.get('/blah')
+ res.mustcontain('root')
+ res.mustcontain('script_name=""')
+ res.mustcontain('path_info="/blah"')
+ res = app.get('/foo/and/more')
+ res.mustcontain('script_name="/foo"')
+ res.mustcontain('path_info="/and/more"')
+ res.mustcontain('foo-only')
+ res = app.get('/foo/bar/baz')
+ res.mustcontain('foo:bar')
+ res.mustcontain('script_name="/foo/bar"')
+ res.mustcontain('path_info="/baz"')
+ res = app.get('/fffzzz')
+ res.mustcontain('root')
+ res.mustcontain('path_info="/fffzzz"')
+ res = app.get('/f/z/y')
+ res.mustcontain('script_name="/f"')
+ res.mustcontain('path_info="/z/y"')
+ res.mustcontain('f-only')
+
+def test_404():
+ mapper = URLMap({})
+ app = TestApp(mapper, extra_environ={'HTTP_ACCEPT': 'text/html'})
+ res = app.get("/-->%0D<script>alert('xss')</script>", status=404)
+ assert b'--><script' not in res.body
+ res = app.get("/--%01><script>", status=404)
+ assert b'--\x01><script>' not in res.body
diff --git a/tests/test_urlparser.py b/tests/test_urlparser.py
new file mode 100644
index 0000000..21c210e
--- /dev/null
+++ b/tests/test_urlparser.py
@@ -0,0 +1,178 @@
+import os
+from paste.urlparser import *
+from paste.fixture import *
+from pkg_resources import get_distribution
+
+def relative_path(name):
+ here = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ 'urlparser_data')
+ f = os.path.join('urlparser_data', '..', 'urlparser_data', name)
+ return os.path.join(here, f)
+
+def path(name):
+ return os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ 'urlparser_data', name)
+
+def make_app(name):
+ app = URLParser({}, path(name), name, index_names=['index', 'Main'])
+ testapp = TestApp(app)
+ return testapp
+
+def test_find_file():
+ app = make_app('find_file')
+ res = app.get('/')
+ assert 'index1' in res
+ assert res.header('content-type') == 'text/plain'
+ res = app.get('/index')
+ assert 'index1' in res
+ assert res.header('content-type') == 'text/plain'
+ res = app.get('/index.txt')
+ assert 'index1' in res
+ assert res.header('content-type') == 'text/plain'
+ res = app.get('/test2.html')
+ assert 'test2' in res
+ assert res.header('content-type') == 'text/html'
+ res = app.get('/test 3.html')
+ assert 'test 3' in res
+ assert res.header('content-type') == 'text/html'
+ res = app.get('/test%203.html')
+ assert 'test 3' in res
+ assert res.header('content-type') == 'text/html'
+ res = app.get('/dir with spaces/test 4.html')
+ assert 'test 4' in res
+ assert res.header('content-type') == 'text/html'
+ res = app.get('/dir%20with%20spaces/test%204.html')
+ assert 'test 4' in res
+ assert res.header('content-type') == 'text/html'
+ # Ensure only data under the app's root directory is accessible
+ res = app.get('/../secured.txt', status=404)
+ res = app.get('/dir with spaces/../../secured.txt', status=404)
+ res = app.get('/%2e%2e/secured.txt', status=404)
+ res = app.get('/%2e%2e%3fsecured.txt', status=404)
+ res = app.get('/..%3fsecured.txt', status=404)
+ res = app.get('/dir%20with%20spaces/%2e%2e/%2e%2e/secured.txt', status=404)
+
+def test_deep():
+ app = make_app('deep')
+ res = app.get('/')
+ assert 'index2' in res
+ res = app.get('/sub')
+ assert res.status == 301
+ print(res)
+ assert res.header('location') == 'http://localhost/sub/'
+ assert 'http://localhost/sub/' in res
+ res = app.get('/sub/')
+ assert 'index3' in res
+
+def test_python():
+ app = make_app('python')
+ res = app.get('/simpleapp')
+ assert 'test1' in res
+ assert res.header('test-header') == 'TEST!'
+ assert res.header('content-type') == 'text/html'
+ res = app.get('/stream')
+ assert 'test2' in res
+ res = app.get('/sub/simpleapp')
+ assert 'subsimple' in res
+
+def test_hook():
+ app = make_app('hook')
+ res = app.get('/bob/app')
+ assert 'user: bob' in res
+ res = app.get('/tim/')
+ assert 'index: tim' in res
+
+def test_not_found_hook():
+ app = make_app('not_found')
+ res = app.get('/simple/notfound')
+ assert res.status == 200
+ assert 'not found' in res
+ res = app.get('/simple/found')
+ assert 'is found' in res
+ res = app.get('/recur/__notfound', status=404)
+ # @@: It's unfortunate that the original path doesn't actually show up
+ assert '/recur/notfound' in res
+ res = app.get('/recur/__isfound')
+ assert res.status == 200
+ assert 'is found' in res
+ res = app.get('/user/list')
+ assert 'user: None' in res
+ res = app.get('/user/bob/list')
+ assert res.status == 200
+ assert 'user: bob' in res
+
+def test_relative_path_in_static_parser():
+ x = relative_path('find_file')
+ app = StaticURLParser(relative_path('find_file'))
+ assert '..' not in app.root_directory
+
+def test_xss():
+ app = TestApp(StaticURLParser(relative_path('find_file')),
+ extra_environ={'HTTP_ACCEPT': 'text/html'})
+ res = app.get("/-->%0D<script>alert('xss')</script>", status=404)
+ assert b'--><script>' not in res.body
+
+def test_static_parser():
+ app = StaticURLParser(path('find_file'))
+ testapp = TestApp(app)
+ res = testapp.get('', status=301)
+ res = testapp.get('/', status=404)
+ res = testapp.get('/index.txt')
+ assert res.body.strip() == b'index1'
+ res = testapp.get('/index.txt/foo', status=404)
+ res = testapp.get('/test 3.html')
+ assert res.body.strip() == b'test 3'
+ res = testapp.get('/test%203.html')
+ assert res.body.strip() == b'test 3'
+ res = testapp.get('/dir with spaces/test 4.html')
+ assert res.body.strip() == b'test 4'
+ res = testapp.get('/dir%20with%20spaces/test%204.html')
+ assert res.body.strip() == b'test 4'
+ # Ensure only data under the app's root directory is accessible
+ res = testapp.get('/../secured.txt', status=404)
+ res = testapp.get('/dir with spaces/../../secured.txt', status=404)
+ res = testapp.get('/%2e%2e/secured.txt', status=404)
+ res = testapp.get('/dir%20with%20spaces/%2e%2e/%2e%2e/secured.txt', status=404)
+ res = testapp.get('/dir%20with%20spaces/', status=404)
+
+def test_egg_parser():
+ app = PkgResourcesParser('Paste', 'paste')
+ testapp = TestApp(app)
+ res = testapp.get('', status=301)
+ res = testapp.get('/', status=404)
+ res = testapp.get('/flup_session', status=404)
+ res = testapp.get('/util/classinit.py')
+ assert 'ClassInitMeta' in res
+ res = testapp.get('/util/classinit', status=404)
+ res = testapp.get('/util', status=301)
+ res = testapp.get('/util/classinit.py/foo', status=404)
+
+ # Find a readable file in the Paste pkg's root directory (or upwards the
+ # directory tree). Ensure it's not accessible via the URLParser
+ unreachable_test_file = None
+ search_path = pkg_root_path = get_distribution('Paste').location
+ level = 0
+ # We might not find any readable files in the pkg's root directory (this
+ # is likely when Paste is installed as a .egg in site-packages). We
+ # (hopefully) can prevent this by traversing up the directory tree until
+ # a usable file is found
+ while unreachable_test_file is None and \
+ os.path.normpath(search_path) != os.path.sep:
+ for file in os.listdir(search_path):
+ full_path = os.path.join(search_path, file)
+ if os.path.isfile(full_path) and os.access(full_path, os.R_OK):
+ unreachable_test_file = file
+ break
+
+ search_path = os.path.dirname(search_path)
+ level += 1
+ assert unreachable_test_file is not None, \
+ 'test_egg_parser requires a readable file in a parent dir of the\n' \
+ 'Paste pkg\'s root dir:\n%s' % pkg_root_path
+
+ unreachable_path = '/' + '../'*level + unreachable_test_file
+ unreachable_path_quoted = '/' + '%2e%2e/'*level + unreachable_test_file
+ res = testapp.get(unreachable_path, status=404)
+ res = testapp.get('/util/..' + unreachable_path, status=404)
+ res = testapp.get(unreachable_path_quoted, status=404)
+ res = testapp.get('/util/%2e%2e' + unreachable_path_quoted, status=404)
diff --git a/tests/test_util/__init__.py b/tests/test_util/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/test_util/__init__.py
diff --git a/tests/test_util/test_datetimeutil.py b/tests/test_util/test_datetimeutil.py
new file mode 100644
index 0000000..45d96c7
--- /dev/null
+++ b/tests/test_util/test_datetimeutil.py
@@ -0,0 +1,135 @@
+# (c) 2005 Clark C. Evans and contributors
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+# Some of this code was funded by: http://prometheusresearch.com
+from time import localtime
+from datetime import date
+from paste.util.datetimeutil import *
+
+def test_timedelta():
+ assert('' == normalize_timedelta(""))
+ assert('0.10' == normalize_timedelta("6m"))
+ assert('0.50' == normalize_timedelta("30m"))
+ assert('0.75' == normalize_timedelta("45m"))
+ assert('1.00' == normalize_timedelta("60 min"))
+ assert('1.50' == normalize_timedelta("90min"))
+ assert('1.50' == normalize_timedelta("1.50"))
+ assert('4.50' == normalize_timedelta("4 : 30"))
+ assert('1.50' == normalize_timedelta("1h 30m"))
+ assert('1.00' == normalize_timedelta("1"))
+ assert('1.00' == normalize_timedelta("1 hour"))
+ assert('8.00' == normalize_timedelta("480 mins"))
+ assert('8.00' == normalize_timedelta("8h"))
+ assert('0.50' == normalize_timedelta("0.5"))
+ assert('0.10' == normalize_timedelta(".1"))
+ assert('0.50' == normalize_timedelta(".50"))
+ assert('0.75' == normalize_timedelta("0.75"))
+
+def test_time():
+ assert('03:00 PM' == normalize_time("3p", ampm=True))
+ assert('03:00 AM' == normalize_time("300", ampm=True))
+ assert('03:22 AM' == normalize_time("322", ampm=True))
+ assert('01:22 PM' == normalize_time("1322", ampm=True))
+ assert('01:00 PM' == normalize_time("13", ampm=True))
+ assert('12:00 PM' == normalize_time("noon", ampm=True))
+ assert("06:00 PM" == normalize_time("6", ampm=True))
+ assert("01:00 PM" == normalize_time("1", ampm=True))
+ assert("07:00 AM" == normalize_time("7", ampm=True))
+ assert("01:00 PM" == normalize_time("1 pm", ampm=True))
+ assert("03:30 PM" == normalize_time("3:30 pm", ampm=True))
+ assert("03:30 PM" == normalize_time("3 30 pm", ampm=True))
+ assert("03:30 PM" == normalize_time("3 30 P.M.", ampm=True))
+ assert("12:00 PM" == normalize_time("0", ampm=True))
+ assert("12:00 AM" == normalize_time("1200 AM", ampm=True))
+
+def test_date():
+ tm = localtime()
+ yr = tm[0]
+ mo = tm[1]
+ assert(date(yr,4,11) == parse_date("411"))
+ assert(date(yr,4,11) == parse_date("APR11"))
+ assert(date(yr,4,11) == parse_date("11APR"))
+ assert(date(yr,4,11) == parse_date("4 11"))
+ assert(date(yr,4,11) == parse_date("11 APR"))
+ assert(date(yr,4,11) == parse_date("APR 11"))
+ assert(date(yr,mo,11) == parse_date("11"))
+ assert(date(yr,4,1) == parse_date("APR"))
+ assert(date(yr,4,11) == parse_date("4/11"))
+ assert(date.today() == parse_date("today"))
+ assert(date.today() == parse_date("now"))
+ assert(None == parse_date(""))
+ assert('' == normalize_date(None))
+
+ assert('2001-02-03' == normalize_date("20010203"))
+ assert('1999-04-11' == normalize_date("1999 4 11"))
+ assert('1999-04-11' == normalize_date("1999 APR 11"))
+ assert('1999-04-11' == normalize_date("APR 11 1999"))
+ assert('1999-04-11' == normalize_date("11 APR 1999"))
+ assert('1999-04-11' == normalize_date("4 11 1999"))
+ assert('1999-04-01' == normalize_date("1999 APR"))
+ assert('1999-04-01' == normalize_date("1999 4"))
+ assert('1999-04-01' == normalize_date("4 1999"))
+ assert('1999-04-01' == normalize_date("APR 1999"))
+ assert('1999-01-01' == normalize_date("1999"))
+
+ assert('1999-04-01' == normalize_date("1APR1999"))
+ assert('2001-04-01' == normalize_date("1APR2001"))
+
+ assert('1999-04-18' == normalize_date("1999-04-11+7"))
+ assert('1999-04-18' == normalize_date("1999-04-11 7"))
+ assert('1999-04-01' == normalize_date("1 apr 1999"))
+ assert('1999-04-11' == normalize_date("11 apr 1999"))
+ assert('1999-04-11' == normalize_date("11 Apr 1999"))
+ assert('1999-04-11' == normalize_date("11-apr-1999"))
+ assert('1999-04-11' == normalize_date("11 April 1999"))
+ assert('1999-04-11' == normalize_date("11 APRIL 1999"))
+ assert('1999-04-11' == normalize_date("11 april 1999"))
+ assert('1999-04-11' == normalize_date("11 aprick 1999"))
+ assert('1999-04-11' == normalize_date("APR 11, 1999"))
+ assert('1999-04-11' == normalize_date("4/11/1999"))
+ assert('1999-04-11' == normalize_date("4-11-1999"))
+ assert('1999-04-11' == normalize_date("1999-4-11"))
+ assert('1999-04-11' == normalize_date("19990411"))
+
+ assert('1999-01-01' == normalize_date("1 Jan 1999"))
+ assert('1999-02-01' == normalize_date("1 Feb 1999"))
+ assert('1999-03-01' == normalize_date("1 Mar 1999"))
+ assert('1999-04-01' == normalize_date("1 Apr 1999"))
+ assert('1999-05-01' == normalize_date("1 May 1999"))
+ assert('1999-06-01' == normalize_date("1 Jun 1999"))
+ assert('1999-07-01' == normalize_date("1 Jul 1999"))
+ assert('1999-08-01' == normalize_date("1 Aug 1999"))
+ assert('1999-09-01' == normalize_date("1 Sep 1999"))
+ assert('1999-10-01' == normalize_date("1 Oct 1999"))
+ assert('1999-11-01' == normalize_date("1 Nov 1999"))
+ assert('1999-12-01' == normalize_date("1 Dec 1999"))
+
+ assert('1999-04-30' == normalize_date("1999-4-30"))
+ assert('2000-02-29' == normalize_date("29 FEB 2000"))
+ assert('2001-02-28' == normalize_date("28 FEB 2001"))
+ assert('2004-02-29' == normalize_date("29 FEB 2004"))
+ assert('2100-02-28' == normalize_date("28 FEB 2100"))
+ assert('1900-02-28' == normalize_date("28 FEB 1900"))
+
+ def assertError(val):
+ try:
+ normalize_date(val)
+ except (TypeError,ValueError):
+ return
+ raise ValueError("type error expected", val)
+
+ assertError("2000-13-11")
+ assertError("APR 99")
+ assertError("29 FEB 1900")
+ assertError("29 FEB 2100")
+ assertError("29 FEB 2001")
+ assertError("1999-4-31")
+ assertError("APR 99")
+ assertError("20301")
+ assertError("020301")
+ assertError("1APR99")
+ assertError("1APR01")
+ assertError("1 APR 99")
+ assertError("1 APR 01")
+ assertError("11/5/01")
+
diff --git a/tests/test_util/test_mimeparse.py b/tests/test_util/test_mimeparse.py
new file mode 100644
index 0000000..9b9b675
--- /dev/null
+++ b/tests/test_util/test_mimeparse.py
@@ -0,0 +1,235 @@
+# (c) 2010 Ch. Zwerschke and contributors
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+from paste.util.mimeparse import *
+
+def test_parse_mime_type():
+ parse = parse_mime_type
+ assert parse('*/*') == ('*', '*', {})
+ assert parse('text/html') == ('text', 'html', {})
+ assert parse('audio/*; q=0.2') == ('audio', '*', {'q': '0.2'})
+ assert parse('text/x-dvi;level=1') == ('text', 'x-dvi', {'level': '1'})
+ assert parse('image/gif; level=2; q=0.4') == (
+ 'image', 'gif', {'level': '2', 'q': '0.4'})
+ assert parse('application/xhtml;level=3;q=0.5') == (
+ 'application', 'xhtml', {'level': '3', 'q': '0.5'})
+ assert parse('application/xml') == ('application', 'xml', {})
+ assert parse('application/xml;q=1') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml ; q=1;b=other') == (
+ 'application', 'xml', {'q': '1', 'b': 'other'})
+ assert parse('application/xml ; q=2;b=other') == (
+ 'application', 'xml', {'q': '2', 'b': 'other'})
+ assert parse('application/xhtml;q=0.5') == (
+ 'application', 'xhtml', {'q': '0.5'})
+ assert parse('application/xhtml;q=0.5;ver=1.2') == (
+ 'application', 'xhtml', {'q': '0.5', 'ver': '1.2'})
+
+def test_parse_illformed_mime_type():
+ parse = parse_mime_type
+ assert parse('*') == ('*', '*', {})
+ assert parse('text') == ('text', '*', {})
+ assert parse('text/') == ('text', '*', {})
+ assert parse('/plain') == ('*', 'plain', {})
+ assert parse('/') == ('*', '*', {})
+ assert parse('text/plain;') == ('text', 'plain', {})
+ assert parse(';q=0.5') == ('*', '*', {'q': '0.5'})
+ assert parse('*; q=.2') == ('*', '*', {'q': '.2'})
+ assert parse('image; q=.7; level=3') == (
+ 'image', '*', {'q': '.7', 'level': '3'})
+ assert parse('*;q=1') == ('*', '*', {'q': '1'})
+ assert parse('*;q=') == ('*', '*', {})
+ assert parse('*;=0.5') == ('*', '*', {})
+ assert parse('*;q=foobar') == ('*', '*', {'q': 'foobar'})
+ assert parse('image/gif; level=2; q=2') == (
+ 'image', 'gif', {'level': '2', 'q': '2'})
+ assert parse('application/xml;q=') == ('application', 'xml', {})
+ assert parse('application/xml ;q=') == ('application', 'xml', {})
+ assert parse(' *; q =;') == ('*', '*', {})
+ assert parse(' *; q=.2') == ('*', '*', {'q': '.2'})
+
+def test_parse_media_range():
+ parse = parse_media_range
+ assert parse('application/*;q=0.5') == ('application', '*', {'q': '0.5'})
+ assert parse('text/plain') == ('text', 'plain', {'q': '1'})
+ assert parse('*') == ('*', '*', {'q': '1'})
+ assert parse(';q=0.5') == ('*', '*', {'q': '0.5'})
+ assert parse('*;q=0.5') == ('*', '*', {'q': '0.5'})
+ assert parse('*;q=1') == ('*', '*', {'q': '1'})
+ assert parse('*;q=') == ('*', '*', {'q': '1'})
+ assert parse('*;q=-1') == ('*', '*', {'q': '1'})
+ assert parse('*;q=foobar') == ('*', '*', {'q': '1'})
+ assert parse('*;q=0.0001') == ('*', '*', {'q': '0.0001'})
+ assert parse('*;q=1000.0') == ('*', '*', {'q': '1'})
+ assert parse('*;q=0') == ('*', '*', {'q': '0'})
+ assert parse('*;q=0.0000') == ('*', '*', {'q': '0.0000'})
+ assert parse('*;q=1.0001') == ('*', '*', {'q': '1'})
+ assert parse('*;q=2') == ('*', '*', {'q': '1'})
+ assert parse('*;q=1e3') == ('*', '*', {'q': '1'})
+ assert parse('image/gif; level=2') == (
+ 'image', 'gif', {'level': '2', 'q': '1'})
+ assert parse('image/gif; level=2; q=0.5') == (
+ 'image', 'gif', {'level': '2', 'q': '0.5'})
+ assert parse('image/gif; level=2; q=2') == (
+ 'image', 'gif', {'level': '2', 'q': '1'})
+ assert parse('application/xml') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml;q=1') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml;q=') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml ;q=') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml ; q=1;b=other') == (
+ 'application', 'xml', {'q': '1', 'b': 'other'})
+ assert parse('application/xml ; q=2;b=other') == (
+ 'application', 'xml', {'q': '1', 'b': 'other'})
+ assert parse(' *; q =;') == ('*', '*', {'q': '1'})
+ assert parse(' *; q=.2') == ('*', '*', {'q': '.2'})
+
+def test_fitness_and_quality_parsed():
+ faq = fitness_and_quality_parsed
+ assert faq('*/*;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (0, 0.5)
+ assert faq('foo/*;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (100, 0.5)
+ assert faq('*/bar;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (10, 0.5)
+ assert faq('foo/bar;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (110, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (-1, 0)
+ assert faq('text/html;q=0.7', [
+ ('text', 'bar', {'q': '0.5'})]) == (-1, 0)
+ assert faq('text/html;q=0.7', [
+ ('foo', 'html', {'q': '0.5'})]) == (-1, 0)
+ assert faq('text/html;q=0.7', [
+ ('text', '*', {'q': '0.5'})]) == (100, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('*', 'html', {'q': '0.5'})]) == (10, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('*', '*', {'q': '0'}), ('text', 'html', {'q': '0.5'})]) == (110, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('*', '*', {'q': '0.5'}), ('audio', '*', {'q': '0'})]) == (0, 0.5)
+ assert faq('audio/mp3;q=0.7', [
+ ('*', '*', {'q': '0'}), ('audio', '*', {'q': '0.5'})]) == (100, 0.5)
+ assert faq('*/mp3;q=0.7', [
+ ('foo', 'mp3', {'q': '0.5'}), ('audio', '*', {'q': '0'})]) == (10, 0.5)
+ assert faq('audio/mp3;q=0.7', [
+ ('audio', 'ogg', {'q': '0'}), ('*', 'mp3', {'q': '0.5'})]) == (10, 0.5)
+ assert faq('audio/mp3;q=0.7', [
+ ('*', 'ogg', {'q': '0'}), ('*', 'mp3', {'q': '0.5'})]) == (10, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('text', 'plain', {'q': '0'}),
+ ('plain', 'html', {'q': '0'}),
+ ('text', 'html', {'q': '0.5'}),
+ ('html', 'text', {'q': '0'})]) == (110, 0.5)
+ assert faq('text/html;q=0.7;level=2', [
+ ('plain', 'html', {'q': '0', 'level': '2'}),
+ ('text', '*', {'q': '0.5', 'level': '3'}),
+ ('*', 'html', {'q': '0.5', 'level': '2'}),
+ ('image', 'gif', {'q': '0.5', 'level': '2'})]) == (100, 0.5)
+ assert faq('text/html;q=0.7;level=2', [
+ ('text', 'plain', {'q': '0'}), ('text', 'html', {'q': '0'}),
+ ('text', 'plain', {'q': '0', 'level': '2'}),
+ ('text', 'html', {'q': '0.5', 'level': '2'}),
+ ('*', '*', {'q': '0', 'level': '2'}),
+ ('text', 'html', {'q': '0', 'level': '3'})]) == (111, 0.5)
+ assert faq('text/html;q=0.7;level=2;opt=3', [
+ ('text', 'html', {'q': '0'}),
+ ('text', 'html', {'q': '0', 'level': '2'}),
+ ('text', 'html', {'q': '0', 'opt': '3'}),
+ ('*', '*', {'q': '0', 'level': '2', 'opt': '3'}),
+ ('text', 'html', {'q': '0', 'level': '3', 'opt': '3'}),
+ ('text', 'html', {'q': '0.5', 'level': '2', 'opt': '3'}),
+ ('*', '*', {'q': '0', 'level': '3', 'opt': '3'})]) == (112, 0.5)
+
+def test_quality_parsed():
+ qp = quality_parsed
+ assert qp('image/gif;q=0.7', [('image', 'jpg', {'q': '0.5'})]) == 0
+ assert qp('image/gif;q=0.7', [('image', '*', {'q': '0.5'})]) == 0.5
+ assert qp('audio/mp3;q=0.7;quality=100', [
+ ('*', '*', {'q': '0', 'quality': '100'}),
+ ('audio', '*', {'q': '0', 'quality': '100'}),
+ ('*', 'mp3', {'q': '0', 'quality': '100'}),
+ ('audio', 'mp3', {'q': '0', 'quality': '50'}),
+ ('audio', 'mp3', {'q': '0.5', 'quality': '100'}),
+ ('audio', 'mp3', {'q': '0.5'})]) == 0.5
+
+def test_quality():
+ assert quality('text/html',
+ 'text/*;q=0.3, text/html;q=0.75, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.75
+ assert quality('text/html;level=2',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.4
+ assert quality('text/plain',
+ 'text/*;q=0.25, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.25
+ assert quality('plain/text',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.5
+ assert quality('text/html;level=1',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 1
+ assert quality('image/jpeg',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.5
+ assert quality('text/html;level=2',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.375, */*;q=0.5') == 0.375
+ assert quality('text/html;level=3',
+ 'text/*;q=0.3, text/html;q=0.75, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.75
+
+def test_best_match():
+ bm = best_match
+ assert bm([], '*/*') == ''
+ assert bm(['application/xbel+xml', 'text/xml'],
+ 'text/*;q=0.5,*/*; q=0.1') == 'text/xml'
+ assert bm(['application/xbel+xml', 'audio/mp3'],
+ 'text/*;q=0.5,*/*; q=0.1') == 'application/xbel+xml'
+ assert bm(['application/xbel+xml', 'audio/mp3'],
+ 'text/*;q=0.5,*/mp3; q=0.1') == 'audio/mp3'
+ assert bm(['application/xbel+xml', 'text/plain', 'text/html'],
+ 'text/*;q=0.5,*/plain; q=0.1') == 'text/plain'
+ assert bm(['application/xbel+xml', 'text/html', 'text/xhtml'],
+ 'text/*;q=0.1,*/xhtml; q=0.5') == 'text/html'
+ assert bm(['application/xbel+xml', 'text/html', 'text/xhtml'],
+ '*/html;q=0.1,*/xhtml; q=0.5') == 'text/xhtml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ 'application/xbel+xml') == 'application/xbel+xml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ 'application/xbel+xml; q=1') == 'application/xbel+xml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ 'application/xml; q=1') == 'application/xml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ 'application/*; q=1') == 'application/xbel+xml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ '*/*, application/xml') == 'application/xml'
+ assert bm(['application/xbel+xml', 'text/xml'],
+ 'text/*;q=0.5,*/*; q=0.1') == 'text/xml'
+ assert bm(['application/xbel+xml', 'text/xml'],
+ 'text/html,application/atom+xml; q=0.9') == ''
+ assert bm(['application/json', 'text/html'],
+ 'application/json, text/javascript, */*') == 'application/json'
+ assert bm(['application/json', 'text/html'],
+ 'application/json, text/html;q=0.9') == 'application/json'
+ assert bm(['image/*', 'application/xml'], 'image/png') == 'image/*'
+ assert bm(['image/*', 'application/xml'], 'image/*') == 'image/*'
+
+def test_illformed_best_match():
+ bm = best_match
+ assert bm(['image/png', 'image/jpeg', 'image/gif', 'text/html'],
+ 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2') == 'image/jpeg'
+ assert bm(['image/png', 'image/jpg', 'image/tif', 'text/html'],
+ 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2') == 'text/html'
+ assert bm(['image/png', 'image/jpg', 'image/tif', 'audio/mp3'],
+ 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2') == 'image/png'
+
+def test_sorted_match():
+ dm = desired_matches
+ assert dm(['text/html', 'application/xml'],
+ 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,'
+ 'text/plain;q=0.8,image/png') == ['text/html', 'application/xml']
+ assert dm(['text/html', 'application/xml'],
+ 'application/xml,application/json') == ['application/xml']
+ assert dm(['text/xhtml', 'text/plain', 'application/xhtml'],
+ 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,'
+ 'text/plain;q=0.8,image/png') == ['text/plain']
diff --git a/tests/test_util/test_quoting.py b/tests/test_util/test_quoting.py
new file mode 100644
index 0000000..5f5e0a8
--- /dev/null
+++ b/tests/test_util/test_quoting.py
@@ -0,0 +1,28 @@
+from paste.util import quoting
+import six
+import unittest
+
+class TestQuoting(unittest.TestCase):
+ def test_html_unquote(self):
+ self.assertEqual(quoting.html_unquote(b'&lt;hey&nbsp;you&gt;'),
+ u'<hey\xa0you>')
+ self.assertEqual(quoting.html_unquote(b''),
+ u'')
+ self.assertEqual(quoting.html_unquote(b'&blahblah;'),
+ u'&blahblah;')
+ self.assertEqual(quoting.html_unquote(b'\xe1\x80\xa9'),
+ u'\u1029')
+
+ def test_html_quote(self):
+ self.assertEqual(quoting.html_quote(1),
+ '1')
+ self.assertEqual(quoting.html_quote(None),
+ '')
+ self.assertEqual(quoting.html_quote('<hey!>'),
+ '&lt;hey!&gt;')
+ if six.PY3:
+ self.assertEqual(quoting.html_quote(u'<\u1029>'),
+ u'&lt;\u1029&gt;')
+ else:
+ self.assertEqual(quoting.html_quote(u'<\u1029>'),
+ '&lt;\xe1\x80\xa9&gt;')
diff --git a/tests/test_wsgilib.py b/tests/test_wsgilib.py
new file mode 100644
index 0000000..72573cf
--- /dev/null
+++ b/tests/test_wsgilib.py
@@ -0,0 +1,52 @@
+from paste.wsgilib import add_close
+
+
+def app_iterable_func_bytes():
+ yield b'a'
+ yield b'b'
+ yield b'c'
+
+
+def app_iterable_func_unicode():
+ yield b'a'.decode('ascii')
+ yield b'b'.decode('ascii')
+ yield b'c'.decode('ascii')
+
+
+def close_func():
+ global close_func_called
+ close_func_called = True
+
+
+def test_add_close_bytes():
+ global close_func_called
+
+ close_func_called = False
+ lst = []
+ app_iterable = app_iterable_func_bytes()
+
+ obj = add_close(app_iterable, close_func)
+ for x in obj:
+ lst.append(x)
+ obj.close()
+
+ assert lst == [b'a', b'b', b'c']
+ assert close_func_called
+ assert obj._closed
+
+
+def test_add_close_unicode():
+ global close_func_called
+
+ close_func_called = False
+ lst = []
+ app_iterable = app_iterable_func_unicode()
+
+ obj = add_close(app_iterable, close_func)
+ for x in obj:
+ lst.append(x)
+ obj.close()
+
+ assert lst == ['a', 'b', 'c']
+ assert close_func_called
+ assert obj._closed
diff --git a/tests/test_wsgiwrappers.py b/tests/test_wsgiwrappers.py
new file mode 100644
index 0000000..75d03ed
--- /dev/null
+++ b/tests/test_wsgiwrappers.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+# (c) 2007 Philip Jenvey; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+import cgi
+from paste.fixture import TestApp
+from paste.wsgiwrappers import WSGIRequest, WSGIResponse
+import six
+
+class AssertApp(object):
+ def __init__(self, assertfunc):
+ self.assertfunc = assertfunc
+
+ def __call__(self, environ, start_response):
+ start_response('200 OK', [('Content-type','text/plain')])
+ self.assertfunc(environ)
+ return [b'Passed']
+
+no_encoding = object()
+def valid_name(name, encoding=no_encoding, post=False):
+ def assert_valid_name(environ):
+ if encoding is not no_encoding:
+ WSGIRequest.defaults._push_object(dict(content_type='text/html',
+ charset=encoding))
+ try:
+ request = WSGIRequest(environ)
+ if post:
+ params = request.POST
+ else:
+ params = request.GET
+ assert params['name'] == name
+ assert request.params['name'] == name
+ finally:
+ if encoding is not no_encoding:
+ WSGIRequest.defaults._pop_object()
+ return assert_valid_name
+
+def test_wsgirequest_charset():
+ # Jose, 'José'
+ app = TestApp(AssertApp(assertfunc=valid_name(u'José', encoding='UTF-8')))
+ res = app.get('/?name=Jos%C3%A9')
+
+ # Tanaka, '田中'
+ app = TestApp(AssertApp(assertfunc=valid_name(u'田中', encoding='UTF-8')))
+ res = app.get('/?name=%E7%94%B0%E4%B8%AD')
+
+ # Nippon (Japan), '日本'
+ app = TestApp(AssertApp(assertfunc=valid_name(u'日本', encoding='UTF-8',
+ post=True)))
+ res = app.post('/', params=dict(name='日本'))
+
+ # WSGIRequest will determine the charset from the Content-Type header when
+ # unicode is expected.
+ # No encoding specified: not expecting unicode
+ app = TestApp(AssertApp(assertfunc=valid_name('日本', post=True)))
+ content_type = 'application/x-www-form-urlencoded; charset=%s'
+ res = app.post('/', params=dict(name='日本'),
+ headers={'content-type': content_type % 'UTF-8'})
+
+ # Encoding specified: expect unicode. Shiftjis is the default encoding, but
+ # params become UTF-8 because the browser specified so
+ app = TestApp(AssertApp(assertfunc=valid_name(u'日本', post=True,
+ encoding='shiftjis')))
+ res = app.post('/', params=dict(name='日本'),
+ headers={'content-type': content_type % 'UTF-8'})
+
+ # Browser did not specify: parse params as the fallback shiftjis
+ app = TestApp(AssertApp(assertfunc=valid_name(u'日本', post=True,
+ encoding='shiftjis')))
+ res = app.post('/', params=dict(name=u'日本'.encode('shiftjis')))
+
+def test_wsgirequest_charset_fileupload():
+ def handle_fileupload(environ, start_response):
+ start_response('200 OK', [('Content-type','text/plain')])
+ request = WSGIRequest(environ)
+
+ assert len(request.POST) == 1
+ assert isinstance(request.POST.keys()[0], str)
+ fs = request.POST['thefile']
+ assert isinstance(fs, cgi.FieldStorage)
+ assert isinstance(fs.filename, str)
+ assert fs.filename == '寿司.txt'
+ assert fs.value == b'Sushi'
+
+ request.charset = 'UTF-8'
+ assert len(request.POST) == 1
+ assert isinstance(request.POST.keys()[0], str)
+ fs = request.POST['thefile']
+ assert isinstance(fs, cgi.FieldStorage)
+ assert isinstance(fs.filename, six.text_type)
+ assert fs.filename == u'寿司.txt'
+ assert fs.value == b'Sushi'
+
+ request.charset = None
+ assert fs.value == b'Sushi'
+ return []
+
+ app = TestApp(handle_fileupload)
+ res = app.post('/', upload_files=[('thefile', '寿司.txt', b'Sushi')])
+
+def test_wsgiresponse_charset():
+ response = WSGIResponse(mimetype='text/html; charset=UTF-8')
+ assert response.content_type == 'text/html'
+ assert response.charset == 'UTF-8'
+ response.write(u'test')
+ response.write(u'test2')
+ response.write('test3')
+ status, headers, content = response.wsgi_response()
+ for data in content:
+ assert isinstance(data, six.binary_type)
+
+ WSGIResponse.defaults._push_object(dict(content_type='text/html',
+ charset='iso-8859-1'))
+ try:
+ response = WSGIResponse()
+ response.write(u'test')
+ response.write(u'test2')
+ response.write('test3')
+ status, headers, content = response.wsgi_response()
+ for data in content:
+ assert isinstance(data, six.binary_type)
+ finally:
+ WSGIResponse.defaults._pop_object()
+
+ # WSGIResponse will allow unicode to pass through when no charset is
+ # set
+ WSGIResponse.defaults._push_object(dict(content_type='text/html',
+ charset=None))
+ try:
+ response = WSGIResponse(u'test')
+ response.write(u'test1')
+ status, headers, content = response.wsgi_response()
+ for data in content:
+ assert isinstance(data, six.text_type)
+ finally:
+ WSGIResponse.defaults._pop_object()
+
+ WSGIResponse.defaults._push_object(dict(content_type='text/html',
+ charset=''))
+ try:
+ response = WSGIResponse(u'test')
+ response.write(u'test1')
+ status, headers, content = response.wsgi_response()
+ for data in content:
+ assert isinstance(data, six.text_type)
+ finally:
+ WSGIResponse.defaults._pop_object()
diff --git a/tests/urlparser_data/__init__.py b/tests/urlparser_data/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/tests/urlparser_data/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/tests/urlparser_data/deep/index.html b/tests/urlparser_data/deep/index.html
new file mode 100644
index 0000000..8913442
--- /dev/null
+++ b/tests/urlparser_data/deep/index.html
@@ -0,0 +1 @@
+index2
diff --git a/tests/urlparser_data/deep/sub/Main.txt b/tests/urlparser_data/deep/sub/Main.txt
new file mode 100644
index 0000000..ec42756
--- /dev/null
+++ b/tests/urlparser_data/deep/sub/Main.txt
@@ -0,0 +1 @@
+index3
diff --git a/tests/urlparser_data/find_file/dir with spaces/test 4.html b/tests/urlparser_data/find_file/dir with spaces/test 4.html
new file mode 100644
index 0000000..1121e31
--- /dev/null
+++ b/tests/urlparser_data/find_file/dir with spaces/test 4.html
@@ -0,0 +1 @@
+test 4
diff --git a/tests/urlparser_data/find_file/index.txt b/tests/urlparser_data/find_file/index.txt
new file mode 100644
index 0000000..6be29bc
--- /dev/null
+++ b/tests/urlparser_data/find_file/index.txt
@@ -0,0 +1 @@
+index1
diff --git a/tests/urlparser_data/find_file/test 3.html b/tests/urlparser_data/find_file/test 3.html
new file mode 100644
index 0000000..954a536
--- /dev/null
+++ b/tests/urlparser_data/find_file/test 3.html
@@ -0,0 +1 @@
+test 3
diff --git a/tests/urlparser_data/find_file/test2.html b/tests/urlparser_data/find_file/test2.html
new file mode 100644
index 0000000..180cf83
--- /dev/null
+++ b/tests/urlparser_data/find_file/test2.html
@@ -0,0 +1 @@
+test2
diff --git a/tests/urlparser_data/hook/__init__.py b/tests/urlparser_data/hook/__init__.py
new file mode 100644
index 0000000..985a930
--- /dev/null
+++ b/tests/urlparser_data/hook/__init__.py
@@ -0,0 +1,10 @@
+from paste import request
+
+def urlparser_hook(environ):
+ first, rest = request.path_info_split(environ.get('PATH_INFO', ''))
+ if not first:
+ # No username
+ return
+ environ['app.user'] = first
+ environ['SCRIPT_NAME'] += '/' + first
+ environ['PATH_INFO'] = rest
diff --git a/tests/urlparser_data/hook/app.py b/tests/urlparser_data/hook/app.py
new file mode 100644
index 0000000..1a98013
--- /dev/null
+++ b/tests/urlparser_data/hook/app.py
@@ -0,0 +1,9 @@
+import six
+
+def application(environ, start_response):
+ start_response('200 OK', [('Content-type', 'text/html')])
+ body = 'user: %s' % environ['app.user']
+ if six.PY3:
+ body = body.encode('ascii')
+ return [body]
+
diff --git a/tests/urlparser_data/hook/index.py b/tests/urlparser_data/hook/index.py
new file mode 100644
index 0000000..92f3d66
--- /dev/null
+++ b/tests/urlparser_data/hook/index.py
@@ -0,0 +1,9 @@
+import six
+
+def application(environ, start_response):
+ start_response('200 OK', [('Content-type', 'text/html')])
+ body = 'index: %s' % environ['app.user']
+ if six.PY3:
+ body = body.encode('ascii')
+ return [body]
+
diff --git a/tests/urlparser_data/not_found/__init__.py b/tests/urlparser_data/not_found/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/tests/urlparser_data/not_found/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/tests/urlparser_data/not_found/recur/__init__.py b/tests/urlparser_data/not_found/recur/__init__.py
new file mode 100644
index 0000000..48205a5
--- /dev/null
+++ b/tests/urlparser_data/not_found/recur/__init__.py
@@ -0,0 +1,9 @@
+def not_found_hook(environ, start_response):
+ urlparser = environ['paste.urlparser.not_found_parser']
+ path = environ.get('PATH_INFO', '')
+ if not path:
+ return urlparser.not_found(environ, start_response)
+ # Strip off leading _'s
+ path = '/' + path.lstrip('/').lstrip('_')
+ environ['PATH_INFO'] = path
+ return urlparser(environ, start_response)
diff --git a/tests/urlparser_data/not_found/recur/isfound.txt b/tests/urlparser_data/not_found/recur/isfound.txt
new file mode 100644
index 0000000..c8b8fab
--- /dev/null
+++ b/tests/urlparser_data/not_found/recur/isfound.txt
@@ -0,0 +1 @@
+is found
diff --git a/tests/urlparser_data/not_found/simple/__init__.py b/tests/urlparser_data/not_found/simple/__init__.py
new file mode 100644
index 0000000..7186daa
--- /dev/null
+++ b/tests/urlparser_data/not_found/simple/__init__.py
@@ -0,0 +1,3 @@
+def not_found_hook(environ, start_response):
+ start_response('200 OK', [('Content-type', 'text/plain')])
+ return [b'not found']
diff --git a/tests/urlparser_data/not_found/simple/found.txt b/tests/urlparser_data/not_found/simple/found.txt
new file mode 100644
index 0000000..c8b8fab
--- /dev/null
+++ b/tests/urlparser_data/not_found/simple/found.txt
@@ -0,0 +1 @@
+is found
diff --git a/tests/urlparser_data/not_found/user/__init__.py b/tests/urlparser_data/not_found/user/__init__.py
new file mode 100644
index 0000000..4126c04
--- /dev/null
+++ b/tests/urlparser_data/not_found/user/__init__.py
@@ -0,0 +1,12 @@
+from paste import request
+
+def not_found_hook(environ, start_response):
+ urlparser = environ['paste.urlparser.not_found_parser']
+ first, rest = request.path_info_split(environ.get('PATH_INFO', ''))
+ if not first:
+ # No username
+ return
+ environ['app.user'] = first
+ environ['SCRIPT_NAME'] += '/' + first
+ environ['PATH_INFO'] = rest
+ return urlparser(environ, start_response)
diff --git a/tests/urlparser_data/not_found/user/list.py b/tests/urlparser_data/not_found/user/list.py
new file mode 100644
index 0000000..fd7482f
--- /dev/null
+++ b/tests/urlparser_data/not_found/user/list.py
@@ -0,0 +1,8 @@
+import six
+
+def application(environ, start_response):
+ start_response('200 OK', [('Content-type', 'text/plain')])
+ body = 'user: %s' % environ.get('app.user')
+ if six.PY3:
+ body = body.encode('ascii')
+ return [body]
diff --git a/tests/urlparser_data/python/__init__.py b/tests/urlparser_data/python/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/tests/urlparser_data/python/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/tests/urlparser_data/python/simpleapp.py b/tests/urlparser_data/python/simpleapp.py
new file mode 100644
index 0000000..7a36ce9
--- /dev/null
+++ b/tests/urlparser_data/python/simpleapp.py
@@ -0,0 +1,5 @@
+def application(environ, start_response):
+ start_response('200 OK', [('Content-type', 'text/html'),
+ ('test-header', 'TEST!')])
+ return [b'test1']
+
diff --git a/tests/urlparser_data/python/stream.py b/tests/urlparser_data/python/stream.py
new file mode 100644
index 0000000..e81fd1c
--- /dev/null
+++ b/tests/urlparser_data/python/stream.py
@@ -0,0 +1,7 @@
+def stream():
+ def app(environ, start_response):
+ writer = start_response('200 OK', [('Content-type', 'text/html')])
+ writer(b'te')
+ writer(b'st')
+ return [b'2']
+ return app
diff --git a/tests/urlparser_data/python/sub/__init__.py b/tests/urlparser_data/python/sub/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/tests/urlparser_data/python/sub/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/tests/urlparser_data/python/sub/simpleapp.py b/tests/urlparser_data/python/sub/simpleapp.py
new file mode 100644
index 0000000..88bd975
--- /dev/null
+++ b/tests/urlparser_data/python/sub/simpleapp.py
@@ -0,0 +1,4 @@
+def application(environ, start_response):
+ start_response('200 OK', [('Content-type', 'text/html'),
+ ('test-header', 'TEST!')])
+ return [b'subsimple']
diff --git a/tests/urlparser_data/secured.txt b/tests/urlparser_data/secured.txt
new file mode 100644
index 0000000..72b11b0
--- /dev/null
+++ b/tests/urlparser_data/secured.txt
@@ -0,0 +1 @@
+secured