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