You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
14 KiB
431 lines
14 KiB
6 years ago
|
import sys
|
||
|
import cherrypy
|
||
|
from cherrypy._cpcompat import ntou
|
||
|
from cherrypy._cptree import Application
|
||
|
from cherrypy.test import helper
|
||
|
|
||
|
script_names = ['', '/foo', '/users/fred/blog', '/corp/blog']
|
||
|
|
||
|
|
||
|
class ObjectMappingTest(helper.CPWebCase):
|
||
|
|
||
|
@staticmethod
|
||
|
def setup_server():
|
||
|
class Root:
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def index(self, name='world'):
|
||
|
return name
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def foobar(self):
|
||
|
return 'bar'
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def default(self, *params, **kwargs):
|
||
|
return 'default:' + repr(params)
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def other(self):
|
||
|
return 'other'
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def extra(self, *p):
|
||
|
return repr(p)
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def redirect(self):
|
||
|
raise cherrypy.HTTPRedirect('dir1/', 302)
|
||
|
|
||
|
def notExposed(self):
|
||
|
return 'not exposed'
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def confvalue(self):
|
||
|
return cherrypy.request.config.get('user')
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def redirect_via_url(self, path):
|
||
|
raise cherrypy.HTTPRedirect(cherrypy.url(path))
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def translate_html(self):
|
||
|
return 'OK'
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def mapped_func(self, ID=None):
|
||
|
return 'ID is %s' % ID
|
||
|
setattr(Root, 'Von B\xfclow', mapped_func)
|
||
|
|
||
|
class Exposing:
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def base(self):
|
||
|
return 'expose works!'
|
||
|
cherrypy.expose(base, '1')
|
||
|
cherrypy.expose(base, '2')
|
||
|
|
||
|
class ExposingNewStyle(object):
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def base(self):
|
||
|
return 'expose works!'
|
||
|
cherrypy.expose(base, '1')
|
||
|
cherrypy.expose(base, '2')
|
||
|
|
||
|
class Dir1:
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def index(self):
|
||
|
return 'index for dir1'
|
||
|
|
||
|
@cherrypy.expose
|
||
|
@cherrypy.config(**{'tools.trailing_slash.extra': True})
|
||
|
def myMethod(self):
|
||
|
return 'myMethod from dir1, path_info is:' + repr(
|
||
|
cherrypy.request.path_info)
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def default(self, *params):
|
||
|
return 'default for dir1, param is:' + repr(params)
|
||
|
|
||
|
class Dir2:
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def index(self):
|
||
|
return 'index for dir2, path is:' + cherrypy.request.path_info
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def script_name(self):
|
||
|
return cherrypy.tree.script_name()
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def cherrypy_url(self):
|
||
|
return cherrypy.url('/extra')
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def posparam(self, *vpath):
|
||
|
return '/'.join(vpath)
|
||
|
|
||
|
class Dir3:
|
||
|
|
||
|
def default(self):
|
||
|
return 'default for dir3, not exposed'
|
||
|
|
||
|
class Dir4:
|
||
|
|
||
|
def index(self):
|
||
|
return 'index for dir4, not exposed'
|
||
|
|
||
|
class DefNoIndex:
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def default(self, *args):
|
||
|
raise cherrypy.HTTPRedirect('contact')
|
||
|
|
||
|
# MethodDispatcher code
|
||
|
@cherrypy.expose
|
||
|
class ByMethod:
|
||
|
|
||
|
def __init__(self, *things):
|
||
|
self.things = list(things)
|
||
|
|
||
|
def GET(self):
|
||
|
return repr(self.things)
|
||
|
|
||
|
def POST(self, thing):
|
||
|
self.things.append(thing)
|
||
|
|
||
|
class Collection:
|
||
|
default = ByMethod('a', 'bit')
|
||
|
|
||
|
Root.exposing = Exposing()
|
||
|
Root.exposingnew = ExposingNewStyle()
|
||
|
Root.dir1 = Dir1()
|
||
|
Root.dir1.dir2 = Dir2()
|
||
|
Root.dir1.dir2.dir3 = Dir3()
|
||
|
Root.dir1.dir2.dir3.dir4 = Dir4()
|
||
|
Root.defnoindex = DefNoIndex()
|
||
|
Root.bymethod = ByMethod('another')
|
||
|
Root.collection = Collection()
|
||
|
|
||
|
d = cherrypy.dispatch.MethodDispatcher()
|
||
|
for url in script_names:
|
||
|
conf = {'/': {'user': (url or '/').split('/')[-2]},
|
||
|
'/bymethod': {'request.dispatch': d},
|
||
|
'/collection': {'request.dispatch': d},
|
||
|
}
|
||
|
cherrypy.tree.mount(Root(), url, conf)
|
||
|
|
||
|
class Isolated:
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def index(self):
|
||
|
return 'made it!'
|
||
|
|
||
|
cherrypy.tree.mount(Isolated(), '/isolated')
|
||
|
|
||
|
@cherrypy.expose
|
||
|
class AnotherApp:
|
||
|
|
||
|
def GET(self):
|
||
|
return 'milk'
|
||
|
|
||
|
cherrypy.tree.mount(AnotherApp(), '/app',
|
||
|
{'/': {'request.dispatch': d}})
|
||
|
|
||
|
def testObjectMapping(self):
|
||
|
for url in script_names:
|
||
|
prefix = self.script_name = url
|
||
|
|
||
|
self.getPage('/')
|
||
|
self.assertBody('world')
|
||
|
|
||
|
self.getPage('/dir1/myMethod')
|
||
|
self.assertBody(
|
||
|
"myMethod from dir1, path_info is:'/dir1/myMethod'")
|
||
|
|
||
|
self.getPage('/this/method/does/not/exist')
|
||
|
self.assertBody(
|
||
|
"default:('this', 'method', 'does', 'not', 'exist')")
|
||
|
|
||
|
self.getPage('/extra/too/much')
|
||
|
self.assertBody("('too', 'much')")
|
||
|
|
||
|
self.getPage('/other')
|
||
|
self.assertBody('other')
|
||
|
|
||
|
self.getPage('/notExposed')
|
||
|
self.assertBody("default:('notExposed',)")
|
||
|
|
||
|
self.getPage('/dir1/dir2/')
|
||
|
self.assertBody('index for dir2, path is:/dir1/dir2/')
|
||
|
|
||
|
# Test omitted trailing slash (should be redirected by default).
|
||
|
self.getPage('/dir1/dir2')
|
||
|
self.assertStatus(301)
|
||
|
self.assertHeader('Location', '%s/dir1/dir2/' % self.base())
|
||
|
|
||
|
# Test extra trailing slash (should be redirected if configured).
|
||
|
self.getPage('/dir1/myMethod/')
|
||
|
self.assertStatus(301)
|
||
|
self.assertHeader('Location', '%s/dir1/myMethod' % self.base())
|
||
|
|
||
|
# Test that default method must be exposed in order to match.
|
||
|
self.getPage('/dir1/dir2/dir3/dir4/index')
|
||
|
self.assertBody(
|
||
|
"default for dir1, param is:('dir2', 'dir3', 'dir4', 'index')")
|
||
|
|
||
|
# Test *vpath when default() is defined but not index()
|
||
|
# This also tests HTTPRedirect with default.
|
||
|
self.getPage('/defnoindex')
|
||
|
self.assertStatus((302, 303))
|
||
|
self.assertHeader('Location', '%s/contact' % self.base())
|
||
|
self.getPage('/defnoindex/')
|
||
|
self.assertStatus((302, 303))
|
||
|
self.assertHeader('Location', '%s/defnoindex/contact' %
|
||
|
self.base())
|
||
|
self.getPage('/defnoindex/page')
|
||
|
self.assertStatus((302, 303))
|
||
|
self.assertHeader('Location', '%s/defnoindex/contact' %
|
||
|
self.base())
|
||
|
|
||
|
self.getPage('/redirect')
|
||
|
self.assertStatus('302 Found')
|
||
|
self.assertHeader('Location', '%s/dir1/' % self.base())
|
||
|
|
||
|
if not getattr(cherrypy.server, 'using_apache', False):
|
||
|
# Test that we can use URL's which aren't all valid Python
|
||
|
# identifiers
|
||
|
# This should also test the %XX-unquoting of URL's.
|
||
|
self.getPage('/Von%20B%fclow?ID=14')
|
||
|
self.assertBody('ID is 14')
|
||
|
|
||
|
# Test that %2F in the path doesn't get unquoted too early;
|
||
|
# that is, it should not be used to separate path components.
|
||
|
# See ticket #393.
|
||
|
self.getPage('/page%2Fname')
|
||
|
self.assertBody("default:('page/name',)")
|
||
|
|
||
|
self.getPage('/dir1/dir2/script_name')
|
||
|
self.assertBody(url)
|
||
|
self.getPage('/dir1/dir2/cherrypy_url')
|
||
|
self.assertBody('%s/extra' % self.base())
|
||
|
|
||
|
# Test that configs don't overwrite each other from diferent apps
|
||
|
self.getPage('/confvalue')
|
||
|
self.assertBody((url or '/').split('/')[-2])
|
||
|
|
||
|
self.script_name = ''
|
||
|
|
||
|
# Test absoluteURI's in the Request-Line
|
||
|
self.getPage('http://%s:%s/' % (self.interface(), self.PORT))
|
||
|
self.assertBody('world')
|
||
|
|
||
|
self.getPage('http://%s:%s/abs/?service=http://192.168.0.1/x/y/z' %
|
||
|
(self.interface(), self.PORT))
|
||
|
self.assertBody("default:('abs',)")
|
||
|
|
||
|
self.getPage('/rel/?service=http://192.168.120.121:8000/x/y/z')
|
||
|
self.assertBody("default:('rel',)")
|
||
|
|
||
|
# Test that the "isolated" app doesn't leak url's into the root app.
|
||
|
# If it did leak, Root.default() would answer with
|
||
|
# "default:('isolated', 'doesnt', 'exist')".
|
||
|
self.getPage('/isolated/')
|
||
|
self.assertStatus('200 OK')
|
||
|
self.assertBody('made it!')
|
||
|
self.getPage('/isolated/doesnt/exist')
|
||
|
self.assertStatus('404 Not Found')
|
||
|
|
||
|
# Make sure /foobar maps to Root.foobar and not to the app
|
||
|
# mounted at /foo. See
|
||
|
# https://github.com/cherrypy/cherrypy/issues/573
|
||
|
self.getPage('/foobar')
|
||
|
self.assertBody('bar')
|
||
|
|
||
|
def test_translate(self):
|
||
|
self.getPage('/translate_html')
|
||
|
self.assertStatus('200 OK')
|
||
|
self.assertBody('OK')
|
||
|
|
||
|
self.getPage('/translate.html')
|
||
|
self.assertStatus('200 OK')
|
||
|
self.assertBody('OK')
|
||
|
|
||
|
self.getPage('/translate-html')
|
||
|
self.assertStatus('200 OK')
|
||
|
self.assertBody('OK')
|
||
|
|
||
|
def test_redir_using_url(self):
|
||
|
for url in script_names:
|
||
|
prefix = self.script_name = url
|
||
|
|
||
|
# Test the absolute path to the parent (leading slash)
|
||
|
self.getPage('/redirect_via_url?path=./')
|
||
|
self.assertStatus(('302 Found', '303 See Other'))
|
||
|
self.assertHeader('Location', '%s/' % self.base())
|
||
|
|
||
|
# Test the relative path to the parent (no leading slash)
|
||
|
self.getPage('/redirect_via_url?path=./')
|
||
|
self.assertStatus(('302 Found', '303 See Other'))
|
||
|
self.assertHeader('Location', '%s/' % self.base())
|
||
|
|
||
|
# Test the absolute path to the parent (leading slash)
|
||
|
self.getPage('/redirect_via_url/?path=./')
|
||
|
self.assertStatus(('302 Found', '303 See Other'))
|
||
|
self.assertHeader('Location', '%s/' % self.base())
|
||
|
|
||
|
# Test the relative path to the parent (no leading slash)
|
||
|
self.getPage('/redirect_via_url/?path=./')
|
||
|
self.assertStatus(('302 Found', '303 See Other'))
|
||
|
self.assertHeader('Location', '%s/' % self.base())
|
||
|
|
||
|
def testPositionalParams(self):
|
||
|
self.getPage('/dir1/dir2/posparam/18/24/hut/hike')
|
||
|
self.assertBody('18/24/hut/hike')
|
||
|
|
||
|
# intermediate index methods should not receive posparams;
|
||
|
# only the "final" index method should do so.
|
||
|
self.getPage('/dir1/dir2/5/3/sir')
|
||
|
self.assertBody("default for dir1, param is:('dir2', '5', '3', 'sir')")
|
||
|
|
||
|
# test that extra positional args raises an 404 Not Found
|
||
|
# See https://github.com/cherrypy/cherrypy/issues/733.
|
||
|
self.getPage('/dir1/dir2/script_name/extra/stuff')
|
||
|
self.assertStatus(404)
|
||
|
|
||
|
def testExpose(self):
|
||
|
# Test the cherrypy.expose function/decorator
|
||
|
self.getPage('/exposing/base')
|
||
|
self.assertBody('expose works!')
|
||
|
|
||
|
self.getPage('/exposing/1')
|
||
|
self.assertBody('expose works!')
|
||
|
|
||
|
self.getPage('/exposing/2')
|
||
|
self.assertBody('expose works!')
|
||
|
|
||
|
self.getPage('/exposingnew/base')
|
||
|
self.assertBody('expose works!')
|
||
|
|
||
|
self.getPage('/exposingnew/1')
|
||
|
self.assertBody('expose works!')
|
||
|
|
||
|
self.getPage('/exposingnew/2')
|
||
|
self.assertBody('expose works!')
|
||
|
|
||
|
def testMethodDispatch(self):
|
||
|
self.getPage('/bymethod')
|
||
|
self.assertBody("['another']")
|
||
|
self.assertHeader('Allow', 'GET, HEAD, POST')
|
||
|
|
||
|
self.getPage('/bymethod', method='HEAD')
|
||
|
self.assertBody('')
|
||
|
self.assertHeader('Allow', 'GET, HEAD, POST')
|
||
|
|
||
|
self.getPage('/bymethod', method='POST', body='thing=one')
|
||
|
self.assertBody('')
|
||
|
self.assertHeader('Allow', 'GET, HEAD, POST')
|
||
|
|
||
|
self.getPage('/bymethod')
|
||
|
self.assertBody(repr(['another', ntou('one')]))
|
||
|
self.assertHeader('Allow', 'GET, HEAD, POST')
|
||
|
|
||
|
self.getPage('/bymethod', method='PUT')
|
||
|
self.assertErrorPage(405)
|
||
|
self.assertHeader('Allow', 'GET, HEAD, POST')
|
||
|
|
||
|
# Test default with posparams
|
||
|
self.getPage('/collection/silly', method='POST')
|
||
|
self.getPage('/collection', method='GET')
|
||
|
self.assertBody("['a', 'bit', 'silly']")
|
||
|
|
||
|
# Test custom dispatcher set on app root (see #737).
|
||
|
self.getPage('/app')
|
||
|
self.assertBody('milk')
|
||
|
|
||
|
def testTreeMounting(self):
|
||
|
class Root(object):
|
||
|
|
||
|
@cherrypy.expose
|
||
|
def hello(self):
|
||
|
return 'Hello world!'
|
||
|
|
||
|
# When mounting an application instance,
|
||
|
# we can't specify a different script name in the call to mount.
|
||
|
a = Application(Root(), '/somewhere')
|
||
|
self.assertRaises(ValueError, cherrypy.tree.mount, a, '/somewhereelse')
|
||
|
|
||
|
# When mounting an application instance...
|
||
|
a = Application(Root(), '/somewhere')
|
||
|
# ...we MUST allow in identical script name in the call to mount...
|
||
|
cherrypy.tree.mount(a, '/somewhere')
|
||
|
self.getPage('/somewhere/hello')
|
||
|
self.assertStatus(200)
|
||
|
# ...and MUST allow a missing script_name.
|
||
|
del cherrypy.tree.apps['/somewhere']
|
||
|
cherrypy.tree.mount(a)
|
||
|
self.getPage('/somewhere/hello')
|
||
|
self.assertStatus(200)
|
||
|
|
||
|
# In addition, we MUST be able to create an Application using
|
||
|
# script_name == None for access to the wsgi_environ.
|
||
|
a = Application(Root(), script_name=None)
|
||
|
# However, this does not apply to tree.mount
|
||
|
self.assertRaises(TypeError, cherrypy.tree.mount, a, None)
|
||
|
|
||
|
def testKeywords(self):
|
||
|
if sys.version_info < (3,):
|
||
|
return self.skip('skipped (Python 3 only)')
|
||
|
exec("""class Root(object):
|
||
|
@cherrypy.expose
|
||
|
def hello(self, *, name='world'):
|
||
|
return 'Hello %s!' % name
|
||
|
cherrypy.tree.mount(Application(Root(), '/keywords'))""")
|
||
|
|
||
|
self.getPage('/keywords/hello')
|
||
|
self.assertStatus(200)
|
||
|
self.getPage('/keywords/hello/extra')
|
||
|
self.assertStatus(404)
|