Files
pecan/tests/test_base.py
2011-03-13 11:39:30 -04:00

827 lines
26 KiB
Python

from formencode import Schema, validators
from paste.recursive import ForwardRequestException
from unittest import TestCase
from webtest import TestApp
from pecan import Pecan, expose, request, response, redirect, abort, make_app, override_template, render
from pecan.templating import _builtin_renderers as builtin_renderers, error_formatters
from pecan.decorators import accept_noncanonical
import os
class TestBase(TestCase):
def test_simple_app(self):
class RootController(object):
@expose()
def index(self):
return 'Hello, World!'
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
assert r.body == 'Hello, World!'
r = app.get('/index')
assert r.status_int == 200
assert r.body == 'Hello, World!'
r = app.get('/index.html')
assert r.status_int == 200
assert r.body == 'Hello, World!'
def test_object_dispatch(self):
class SubSubController(object):
@expose()
def index(self):
return '/sub/sub/'
@expose()
def deeper(self):
return '/sub/sub/deeper'
class SubController(object):
@expose()
def index(self):
return '/sub/'
@expose()
def deeper(self):
return '/sub/deeper'
sub = SubSubController()
class RootController(object):
@expose()
def index(self):
return '/'
@expose()
def deeper(self):
return '/deeper'
sub = SubController()
app = TestApp(Pecan(RootController()))
for path in ('/', '/deeper', '/sub/', '/sub/deeper', '/sub/sub/', '/sub/sub/deeper'):
r = app.get(path)
assert r.status_int == 200
assert r.body == path
def test_lookup(self):
class LookupController(object):
def __init__(self, someID):
self.someID = someID
@expose()
def index(self):
return '/%s' % self.someID
@expose()
def name(self):
return '/%s/name' % self.someID
class RootController(object):
@expose()
def index(self):
return '/'
@expose()
def _lookup(self, someID, *remainder):
return LookupController(someID), remainder
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
assert r.body == '/'
r = app.get('/100/')
assert r.status_int == 200
assert r.body == '/100'
r = app.get('/100/name')
assert r.status_int == 200
assert r.body == '/100/name'
def test_lookup_with_wrong_argspec(self):
class RootController(object):
@expose()
def _lookup(self, someID):
return 'Bad arg spec'
app = TestApp(Pecan(RootController()))
r = app.get('/foo/bar', expect_errors=True)
r.status_int == 404
def test_controller_args(self):
class RootController(object):
@expose()
def index(self, id):
return 'index: %s' % id
@expose()
def multiple(self, one, two):
return 'multiple: %s, %s' % (one, two)
@expose()
def optional(self, id=None):
return 'optional: %s' % str(id)
@expose()
def multiple_optional(self, one=None, two=None, three=None):
return 'multiple_optional: %s, %s, %s' % (one, two, three)
@expose()
def variable_args(self, *args):
return 'variable_args: %s' % ', '.join(args)
@expose()
def variable_kwargs(self, **kwargs):
data = ['%s=%s' % (key, kwargs[key]) for key in sorted(kwargs.keys())]
return 'variable_kwargs: %s' % ', '.join(data)
@expose()
def variable_all(self, *args, **kwargs):
data = ['%s=%s' % (key, kwargs[key]) for key in sorted(kwargs.keys())]
return 'variable_all: %s' % ', '.join(list(args) + data)
@expose()
def eater(self, id, dummy=None, *args, **kwargs):
data = ['%s=%s' % (key, kwargs[key]) for key in sorted(kwargs.keys())]
return 'eater: %s, %s, %s' % (id, dummy, ', '.join(list(args) + data))
@expose()
def _route(self, args):
if hasattr(self, args[0]):
return getattr(self, args[0]), args[1:]
else:
return self.index, args
app = TestApp(Pecan(RootController()))
# required arg
try:
r = app.get('/')
assert r.status_int != 200
except Exception, ex:
assert type(ex) == TypeError
assert ex.args[0] == 'index() takes exactly 2 arguments (1 given)'
r = app.get('/1')
assert r.status_int == 200
assert r.body == 'index: 1'
r = app.get('/1/dummy', status=404)
assert r.status_int == 404
r = app.get('/?id=2')
assert r.status_int == 200
assert r.body == 'index: 2'
r = app.get('/3?id=three')
assert r.status_int == 200
assert r.body == 'index: 3'
r = app.post('/', {'id': '4'})
assert r.status_int == 200
assert r.body == 'index: 4'
r = app.post('/4', {'id': 'four'})
assert r.status_int == 200
assert r.body == 'index: 4'
r = app.get('/?id=5&dummy=dummy')
assert r.status_int == 200
assert r.body == 'index: 5'
r = app.post('/', {'id': '6', 'dummy': 'dummy'})
assert r.status_int == 200
assert r.body == 'index: 6'
# multiple args
r = app.get('/multiple/one/two')
assert r.status_int == 200
assert r.body == 'multiple: one, two'
r = app.get('/multiple?one=three&two=four')
assert r.status_int == 200
assert r.body == 'multiple: three, four'
r = app.post('/multiple', {'one': 'five', 'two': 'six'})
assert r.status_int == 200
assert r.body == 'multiple: five, six'
# optional arg
r = app.get('/optional')
assert r.status_int == 200
assert r.body == 'optional: None'
r = app.get('/optional/1')
assert r.status_int == 200
assert r.body == 'optional: 1'
r = app.get('/optional/2/dummy', status=404)
assert r.status_int == 404
r = app.get('/optional?id=2')
assert r.status_int == 200
assert r.body == 'optional: 2'
r = app.get('/optional/3?id=three')
assert r.status_int == 200
assert r.body == 'optional: 3'
r = app.post('/optional', {'id': '4'})
assert r.status_int == 200
assert r.body == 'optional: 4'
r = app.post('/optional/5', {'id': 'five'})
assert r.status_int == 200
assert r.body == 'optional: 5'
r = app.get('/optional?id=6&dummy=dummy')
assert r.status_int == 200
assert r.body == 'optional: 6'
r = app.post('/optional', {'id': '7', 'dummy': 'dummy'})
assert r.status_int == 200
assert r.body == 'optional: 7'
# multiple optional args
r = app.get('/multiple_optional')
assert r.status_int == 200
assert r.body == 'multiple_optional: None, None, None'
r = app.get('/multiple_optional/1')
assert r.status_int == 200
assert r.body == 'multiple_optional: 1, None, None'
r = app.get('/multiple_optional/1/2/3')
assert r.status_int == 200
assert r.body == 'multiple_optional: 1, 2, 3'
r = app.get('/multiple_optional/1/2/3/dummy', status=404)
assert r.status_int == 404
r = app.get('/multiple_optional?one=1')
assert r.status_int == 200
assert r.body == 'multiple_optional: 1, None, None'
r = app.get('/multiple_optional/1?one=one')
assert r.status_int == 200
assert r.body == 'multiple_optional: 1, None, None'
r = app.post('/multiple_optional', {'one': '1'})
assert r.status_int == 200
assert r.body == 'multiple_optional: 1, None, None'
r = app.post('/multiple_optional/1', {'one': 'one'})
assert r.status_int == 200
assert r.body == 'multiple_optional: 1, None, None'
r = app.get('/multiple_optional?one=1&two=2&three=3&four=4')
assert r.status_int == 200
assert r.body == 'multiple_optional: 1, 2, 3'
r = app.post('/multiple_optional', {'one': '1', 'two': '2', 'three': '3', 'four': '4'})
assert r.status_int == 200
assert r.body == 'multiple_optional: 1, 2, 3'
r = app.get('/multiple_optional?three=3')
assert r.status_int == 200
assert r.body == 'multiple_optional: None, None, 3'
r = app.get('/multiple_optional', {'two': '2'})
assert r.status_int == 200
assert r.body == 'multiple_optional: None, 2, None'
# variable args
r = app.get('/variable_args')
assert r.status_int == 200
assert r.body == 'variable_args: '
r = app.get('/variable_args/1/dummy')
assert r.status_int == 200
assert r.body == 'variable_args: 1, dummy'
r = app.get('/variable_args?id=2&dummy=dummy')
assert r.status_int == 200
assert r.body == 'variable_args: '
r = app.post('/variable_args', {'id': '3', 'dummy': 'dummy'})
assert r.status_int == 200
assert r.body == 'variable_args: '
# variable keyword args
r = app.get('/variable_kwargs')
assert r.status_int == 200
assert r.body == 'variable_kwargs: '
r = app.get('/variable_kwargs/1/dummy', status=404)
assert r.status_int == 404
r = app.get('/variable_kwargs?id=2&dummy=dummy')
assert r.status_int == 200
assert r.body == 'variable_kwargs: dummy=dummy, id=2'
r = app.post('/variable_kwargs', {'id': '3', 'dummy': 'dummy'})
assert r.status_int == 200
assert r.body == 'variable_kwargs: dummy=dummy, id=3'
# variable args & keyword args
r = app.get('/variable_all')
assert r.status_int == 200
assert r.body == 'variable_all: '
r = app.get('/variable_all/1')
assert r.status_int == 200
assert r.body == 'variable_all: 1'
r = app.get('/variable_all/2/dummy')
assert r.status_int == 200
assert r.body == 'variable_all: 2, dummy'
r = app.get('/variable_all/3?month=1&day=12')
assert r.status_int == 200
assert r.body == 'variable_all: 3, day=12, month=1'
r = app.get('/variable_all/4?id=four&month=1&day=12')
assert r.status_int == 200
assert r.body == 'variable_all: 4, day=12, id=four, month=1'
r = app.post('/variable_all/5/dummy')
assert r.status_int == 200
assert r.body == 'variable_all: 5, dummy'
r = app.post('/variable_all/6', {'month': '1', 'day': '12'})
assert r.status_int == 200
assert r.body == 'variable_all: 6, day=12, month=1'
r = app.post('/variable_all/7', {'id': 'seven', 'month': '1', 'day': '12'})
assert r.status_int == 200
assert r.body == 'variable_all: 7, day=12, id=seven, month=1'
# the "everything" controller
try:
r = app.get('/eater')
assert r.status_int != 200
except Exception, ex:
assert type(ex) == TypeError
assert ex.args[0] == 'eater() takes at least 2 arguments (1 given)'
r = app.get('/eater/1')
assert r.status_int == 200
assert r.body == 'eater: 1, None, '
r = app.get('/eater/2/dummy')
assert r.status_int == 200
assert r.body == 'eater: 2, dummy, '
r = app.get('/eater/3/dummy/foo/bar')
assert r.status_int == 200
assert r.body == 'eater: 3, dummy, foo, bar'
r = app.get('/eater/4?month=1&day=12')
assert r.status_int == 200
assert r.body == 'eater: 4, None, day=12, month=1'
r = app.get('/eater/5?id=five&month=1&day=12&dummy=dummy')
assert r.status_int == 200
assert r.body == 'eater: 5, dummy, day=12, month=1'
r = app.post('/eater/6')
assert r.status_int == 200
assert r.body == 'eater: 6, None, '
r = app.post('/eater/7/dummy')
assert r.status_int == 200
assert r.body == 'eater: 7, dummy, '
r = app.post('/eater/8/dummy/foo/bar')
assert r.status_int == 200
assert r.body == 'eater: 8, dummy, foo, bar'
r = app.post('/eater/9', {'month': '1', 'day': '12'})
assert r.status_int == 200
assert r.body == 'eater: 9, None, day=12, month=1'
r = app.post('/eater/10', {'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'})
assert r.status_int == 200
assert r.body == 'eater: 10, dummy, day=12, month=1'
def test_abort(self):
class RootController(object):
@expose()
def index(self):
abort(404)
app = TestApp(Pecan(RootController()))
r = app.get('/', status=404)
assert r.status_int == 404
def test_redirect(self):
class RootController(object):
@expose()
def index(self):
redirect('/testing')
@expose()
def internal(self):
redirect('/testing', internal=True)
@expose()
def bad_internal(self):
redirect('/testing', internal=True, code=301)
@expose()
def permanent(self):
redirect('/testing', code=301)
@expose()
def testing(self):
return 'it worked!'
app = TestApp(make_app(RootController(), debug=True))
r = app.get('/')
assert r.status_int == 302
r = r.follow()
assert r.status_int == 200
assert r.body == 'it worked!'
r = app.get('/internal')
assert r.status_int == 200
assert r.body == 'it worked!'
self.assertRaises(ValueError, app.get, '/bad_internal')
r = app.get('/permanent')
assert r.status_int == 301
r = r.follow()
assert r.status_int == 200
assert r.body == 'it worked!'
def test_streaming_response(self):
import StringIO
class RootController(object):
@expose(content_type='text/plain')
def test(self, foo):
if foo == 'stream':
# mimic large file
contents = StringIO.StringIO('stream')
response.content_type='application/octet-stream'
contents.seek(0, os.SEEK_END)
response.content_length = contents.tell()
contents.seek(0, os.SEEK_SET)
response.app_iter = contents
return response
else:
return 'plain text'
app = TestApp(Pecan(RootController()))
r = app.get('/test/stream')
assert r.content_type == 'application/octet-stream'
assert r.body == 'stream'
r = app.get('/test/plain')
assert r.content_type == 'text/plain'
assert r.body == 'plain text'
def test_request_state_cleanup(self):
"""
After a request, the state local() should be totally clean
except for state.app (so that objects don't leak between requests)
"""
from pecan.core import state
class RootController(object):
@expose()
def index(self):
return '/'
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
assert r.body == '/'
assert state.__dict__.keys() == ['app']
def test_extension(self):
"""
Test extension splits
"""
class RootController(object):
@expose(content_type=None)
def _default(self, *args):
from pecan.core import request
return request.pecan['extension']
app = TestApp(Pecan(RootController()))
r = app.get('/index.html')
assert r.status_int == 200
assert r.body == '.html'
r = app.get('/image.png')
assert r.status_int == 200
assert r.body == '.png'
r = app.get('/.vimrc')
assert r.status_int == 200
assert r.body == ''
r = app.get('/gradient.js.js')
assert r.status_int == 200
assert r.body == '.js'
def test_app_wrap(self):
class RootController(object):
pass
wrapped_apps = []
def wrap(app):
wrapped_apps.append(app)
return app
app = make_app(RootController(), wrap_app=wrap, debug=True)
assert len(wrapped_apps) == 1
def test_bad_content_type(self):
class RootController(object):
@expose()
def index(self):
return '/'
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
assert r.body == '/'
r = app.get('/index.html', expect_errors=True)
assert r.status_int == 200
assert r.body == '/'
r = app.get('/index.txt', expect_errors=True)
assert r.status_int == 404
def test_canonical_index(self):
class ArgSubController(object):
@expose()
def index(self, arg):
return arg
class AcceptController(object):
@accept_noncanonical
@expose()
def index(self):
return 'accept'
class SubController(object):
@expose()
def index(self):
return 'subindex'
class RootController(object):
@expose()
def index(self):
return 'index'
sub = SubController()
arg = ArgSubController()
accept = AcceptController()
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
assert 'index' in r.body
r = app.get('/index')
assert r.status_int == 200
assert 'index' in r.body
# for broken clients
r = app.get('', status=302)
assert r.status_int == 302
r = app.get('/sub/')
assert r.status_int == 200
assert 'subindex' in r.body
r = app.get('/sub', status=302)
assert r.status_int == 302
try:
r = app.post('/sub', dict(foo=1))
raise Exception, "Post should fail"
except Exception, e:
assert isinstance(e, RuntimeError)
r = app.get('/arg/index/foo')
assert r.status_int == 200
assert r.body == 'foo'
r = app.get('/accept/')
assert r.status_int == 200
assert 'accept' == r.body
r = app.get('/accept')
assert r.status_int == 200
assert 'accept' == r.body
app = TestApp(Pecan(RootController(), force_canonical=False))
r = app.get('/')
assert r.status_int == 200
assert 'index' in r.body
r = app.get('/sub')
assert r.status_int == 200
assert 'subindex' in r.body
r = app.post('/sub', dict(foo=1))
assert r.status_int == 200
assert 'subindex' in r.body
r = app.get('/sub/')
assert r.status_int == 200
assert 'subindex' in r.body
def test_proxy(self):
class RootController(object):
@expose()
def index(self):
request.testing = True
assert request.testing == True
del request.testing
assert hasattr(request, 'testing') == False
return '/'
app = TestApp(make_app(RootController(), debug=True))
r = app.get('/')
assert r.status_int == 200
class TestEngines(object):
template_path = os.path.join(os.path.dirname(__file__), 'templates')
def test_genshi(self):
if 'genshi' not in builtin_renderers:
return
class RootController(object):
@expose('genshi:genshi.html')
def index(self, name='Jonathan'):
return dict(name=name)
@expose('genshi:genshi_bad.html')
def badtemplate(self):
return dict()
app = TestApp(Pecan(RootController(), template_path=self.template_path))
r = app.get('/')
assert r.status_int == 200
assert "<h1>Hello, Jonathan!</h1>" in r.body
r = app.get('/index.html?name=World')
assert r.status_int == 200
assert "<h1>Hello, World!</h1>" in r.body
error_msg = None
try:
r = app.get('/badtemplate.html')
except Exception, e:
for error_f in error_formatters:
error_msg = error_f(e)
if error_msg:
break
assert error_msg is not None
def test_kajiki(self):
if 'kajiki' not in builtin_renderers:
return
class RootController(object):
@expose('kajiki:kajiki.html')
def index(self, name='Jonathan'):
return dict(name=name)
app = TestApp(Pecan(RootController(), template_path=self.template_path))
r = app.get('/')
assert r.status_int == 200
assert "<h1>Hello, Jonathan!</h1>" in r.body
r = app.get('/index.html?name=World')
assert r.status_int == 200
assert "<h1>Hello, World!</h1>" in r.body
def test_jinja(self):
if 'jinja' not in builtin_renderers:
return
class RootController(object):
@expose('jinja:jinja.html')
def index(self, name='Jonathan'):
return dict(name=name)
@expose('jinja:jinja_bad.html')
def badtemplate(self):
return dict()
app = TestApp(Pecan(RootController(), template_path=self.template_path))
r = app.get('/')
assert r.status_int == 200
assert "<h1>Hello, Jonathan!</h1>" in r.body
error_msg = None
try:
r = app.get('/badtemplate.html')
except Exception, e:
for error_f in error_formatters:
error_msg = error_f(e)
if error_msg:
break
assert error_msg is not None
def test_mako(self):
if 'mako' not in builtin_renderers:
return
class RootController(object):
@expose('mako:mako.html')
def index(self, name='Jonathan'):
return dict(name=name)
@expose('mako:mako_bad.html')
def badtemplate(self):
return dict()
app = TestApp(Pecan(RootController(), template_path=self.template_path))
r = app.get('/')
assert r.status_int == 200
assert "<h1>Hello, Jonathan!</h1>" in r.body
r = app.get('/index.html?name=World')
assert r.status_int == 200
assert "<h1>Hello, World!</h1>" in r.body
error_msg = None
try:
r = app.get('/badtemplate.html')
except Exception, e:
for error_f in error_formatters:
error_msg = error_f(e)
if error_msg:
break
assert error_msg is not None
def test_json(self):
try:
from simplejson import loads
except:
from json import loads
expected_result = dict(name='Jonathan', age=30, nested=dict(works=True))
class RootController(object):
@expose('json')
def index(self):
return expected_result
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
result = dict(loads(r.body))
assert result == expected_result
def test_override_template(self):
class RootController(object):
@expose('foo.html')
def index(self):
override_template(None, content_type='text/plain')
return 'Override'
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
assert 'Override' in r.body
assert r.content_type == 'text/plain'
def test_render(self):
#if 'mako' not in builtin_renderers:
# return
class RootController(object):
@expose()
def index(self, name='Jonathan'):
return render('mako.html', dict(name=name))
return dict(name=name)
app = TestApp(Pecan(RootController(), template_path=self.template_path))
r = app.get('/')
assert r.status_int == 200
assert "<h1>Hello, Jonathan!</h1>" in r.body