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