diff options
Diffstat (limited to 'tests')
65 files changed, 3385 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..bdd7510 --- /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 2030 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 <foo>' + +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'<hey you>'), + 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!>'), + '<hey!>') + if six.PY3: + self.assertEqual(quoting.html_quote(u'<\u1029>'), + u'<\u1029>') + else: + self.assertEqual(quoting.html_quote(u'<\u1029>'), + '<\xe1\x80\xa9>') 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 |