1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
# -*- coding: utf-8 -*-
import unittest
import bottle
import warnings
class TestRouter(unittest.TestCase):
CGI = False
def setUp(self):
self.r = bottle.Router()
def add(self, path, target, method='GET', **ka):
with warnings.catch_warnings() as r:
warnings.simplefilter("ignore")
self.r.add(path, method, target, **ka)
def match(self, path, method='GET'):
env = {'PATH_INFO': path, 'REQUEST_METHOD': method}
if self.CGI:
env['wsgi.run_once'] = 'true'
return self.r.match(env)
def assertMatches(self, rule, url, method='GET', **args):
self.add(rule, rule, method)
target, urlargs = self.match(url, method)
self.assertEqual(rule, target)
self.assertEqual(args, urlargs)
def testBasic(self):
self.assertMatches('/static', '/static')
self.assertMatches('/\\:its/:#.+#/:test/:name#[a-z]+#/',
'/:its/a/cruel/world/',
test='cruel', name='world')
self.assertMatches('/:test', '/test', test='test') # No tail
self.assertMatches(':test/', 'test/', test='test') # No head
self.assertMatches('/:test/', '/test/', test='test') # Middle
self.assertMatches(':test', 'test', test='test') # Full wildcard
self.assertMatches('/:#anon#/match', '/anon/match') # Anon wildcards
self.assertRaises(bottle.HTTPError, self.match, '//no/m/at/ch/')
def testNewSyntax(self):
self.assertMatches('/static', '/static')
self.assertMatches('/\\<its>/<:re:.+>/<test>/<name:re:[a-z]+>/',
'/<its>/a/cruel/world/',
test='cruel', name='world')
self.assertMatches('/<test>', '/test', test='test') # No tail
self.assertMatches('<test>/', 'test/', test='test') # No head
self.assertMatches('/<test>/', '/test/', test='test') # Middle
self.assertMatches('<test>', 'test', test='test') # Full wildcard
self.assertMatches('/<:re:anon>/match', '/anon/match') # Anon wildcards
self.assertRaises(bottle.HTTPError, self.match, '//no/m/at/ch/')
def testUnicode(self):
self.assertMatches('/uni/<x>', '/uni/瓶', x='瓶')
def testValueErrorInFilter(self):
self.r.add_filter('test', lambda x: ('.*', int, int))
self.assertMatches('/int/<i:test>', '/int/5', i=5) # No tail
self.assertRaises(bottle.HTTPError, self.match, '/int/noint')
def testIntFilter(self):
self.assertMatches('/object/<id:int>', '/object/567', id=567)
self.assertRaises(bottle.HTTPError, self.match, '/object/abc')
def testFloatFilter(self):
self.assertMatches('/object/<id:float>', '/object/1', id=1)
self.assertMatches('/object/<id:float>', '/object/1.1', id=1.1)
self.assertMatches('/object/<id:float>', '/object/.1', id=0.1)
self.assertMatches('/object/<id:float>', '/object/1.', id=1)
self.assertRaises(bottle.HTTPError, self.match, '/object/abc')
self.assertRaises(bottle.HTTPError, self.match, '/object/')
self.assertRaises(bottle.HTTPError, self.match, '/object/.')
def testPathFilter(self):
self.assertMatches('/<id:path>/:f', '/a/b', id='a', f='b')
self.assertMatches('/<id:path>', '/a', id='a')
def testWildcardNames(self):
self.assertMatches('/alpha/:abc', '/alpha/alpha', abc='alpha')
self.assertMatches('/alnum/:md5', '/alnum/sha1', md5='sha1')
def testParentheses(self):
self.assertMatches('/func(:param)', '/func(foo)', param='foo')
self.assertMatches('/func2(:param#(foo|bar)#)', '/func2(foo)', param='foo')
self.assertMatches('/func2(:param#(foo|bar)#)', '/func2(bar)', param='bar')
self.assertRaises(bottle.HTTPError, self.match, '/func2(baz)')
def testErrorInPattern(self):
self.assertRaises(Exception, self.assertMatches, '/:bug#(#/', '/foo/')
self.assertRaises(Exception, self.assertMatches, '/<:re:(>/', '/foo/')
def testBuild(self):
add, build = self.add, self.r.build
add('/:test/:name#[a-z]+#/', 'handler', name='testroute')
url = build('testroute', test='hello', name='world')
self.assertEqual('/hello/world/', url)
url = build('testroute', test='hello', name='world', q='value')
self.assertEqual('/hello/world/?q=value', url)
# RouteBuildError: Missing URL argument: 'test'
self.assertRaises(bottle.RouteBuildError, build, 'test')
def testBuildAnon(self):
add, build = self.add, self.r.build
add('/anon/:#.#', 'handler', name='anonroute')
url = build('anonroute', 'hello')
self.assertEqual('/anon/hello', url)
url = build('anonroute', 'hello', q='value')
self.assertEqual('/anon/hello?q=value', url)
# RouteBuildError: Missing URL argument: anon0.
self.assertRaises(bottle.RouteBuildError, build, 'anonroute')
def testBuildFilter(self):
add, build = self.add, self.r.build
add('/int/<:int>', 'handler', name='introute')
url = build('introute', '5')
self.assertEqual('/int/5', url)
# RouteBuildError: Missing URL argument: anon0.
self.assertRaises(ValueError, build, 'introute', 'hello')
def test_dynamic_before_static_any(self):
''' Static ANY routes have lower priority than dynamic GET routes. '''
self.add('/foo', 'foo', 'ANY')
self.assertEqual(self.match('/foo')[0], 'foo')
self.add('/<:>', 'bar', 'GET')
self.assertEqual(self.match('/foo')[0], 'bar')
def test_any_static_before_dynamic(self):
''' Static ANY routes have higher priority than dynamic ANY routes. '''
self.add('/<:>', 'bar', 'ANY')
self.assertEqual(self.match('/foo')[0], 'bar')
self.add('/foo', 'foo', 'ANY')
self.assertEqual(self.match('/foo')[0], 'foo')
def test_dynamic_any_if_method_exists(self):
''' Check dynamic ANY routes if the matching method is known,
but not matched.'''
self.add('/bar<:>', 'bar', 'GET')
self.assertEqual(self.match('/barx')[0], 'bar')
self.add('/foo<:>', 'foo', 'ANY')
self.assertEqual(self.match('/foox')[0], 'foo')
def test_lots_of_routes(self):
n = bottle.Router._MAX_GROUPS_PER_PATTERN+10
for i in range(n):
self.add('/<:>/'+str(i), str(i), 'GET')
self.assertEqual(self.match('/foo/'+str(n-1))[0], str(n-1))
class TestRouterInCGIMode(TestRouter):
''' Makes no sense since the default route does not optimize CGI anymore.'''
CGI = True
|