Michael Merickel
2018-10-26 035f6cf8238211d097c991677fde6b5bc046a57b
commit | author | age
8392d7 1 import unittest
b60bdb 2 from pyramid import testing
0c29cf 3 from pyramid.compat import text_, PY2
MM 4
8392d7 5
05c023 6 class TestRoute(unittest.TestCase):
CM 7     def _getTargetClass(self):
b60bdb 8         from pyramid.urldispatch import Route
0c29cf 9
05c023 10         return Route
CM 11
12     def _makeOne(self, *arg):
13         return self._getTargetClass()(*arg)
14
74409d 15     def test_provides_IRoute(self):
b60bdb 16         from pyramid.interfaces import IRoute
74409d 17         from zope.interface.verify import verifyObject
0c29cf 18
74409d 19         verifyObject(IRoute, self._makeOne('name', 'pattern'))
CM 20
05c023 21     def test_ctor(self):
f646fe 22         import types
0c29cf 23
74409d 24         route = self._makeOne('name', ':path', 'factory')
CM 25         self.assertEqual(route.pattern, ':path')
6b9be1 26         self.assertEqual(route.path, ':path')
05c023 27         self.assertEqual(route.name, 'name')
CM 28         self.assertEqual(route.factory, 'factory')
10ddb6 29         self.assertIsInstance(route.generate, types.FunctionType)
MM 30         self.assertIsInstance(route.match, types.FunctionType)
6b9be1 31
CM 32     def test_ctor_defaults(self):
f646fe 33         import types
0c29cf 34
74409d 35         route = self._makeOne('name', ':path')
CM 36         self.assertEqual(route.pattern, ':path')
6b9be1 37         self.assertEqual(route.path, ':path')
74409d 38         self.assertEqual(route.name, 'name')
6b9be1 39         self.assertEqual(route.factory, None)
10ddb6 40         self.assertIsInstance(route.generate, types.FunctionType)
MM 41         self.assertIsInstance(route.match, types.FunctionType)
05c023 42
CM 43     def test_match(self):
74409d 44         route = self._makeOne('name', ':path')
0c29cf 45         self.assertEqual(route.match('/whatever'), {'path': 'whatever'})
d3c39d 46
05c023 47     def test_generate(self):
74409d 48         route = self._makeOne('name', ':path')
0c29cf 49         self.assertEqual(route.generate({'path': 'abc'}), '/abc')
MM 50
05c023 51
cbfafb 52 class RoutesMapperTests(unittest.TestCase):
def444 53     def setUp(self):
11644e 54         testing.setUp()
def444 55
CM 56     def tearDown(self):
11644e 57         testing.tearDown()
0c29cf 58
11644e 59     def _getRequest(self, **kw):
b60bdb 60         from pyramid.threadlocal import get_current_registry
0c29cf 61
MM 62         environ = {'SERVER_NAME': 'localhost', 'wsgi.url_scheme': 'http'}
62267e 63         environ.update(kw)
11644e 64         request = DummyRequest(environ)
e27974 65         reg = get_current_registry()
CM 66         request.registry = reg
11644e 67         return request
CM 68
8392d7 69     def _getTargetClass(self):
b60bdb 70         from pyramid.urldispatch import RoutesMapper
0c29cf 71
cbfafb 72         return RoutesMapper
8392d7 73
cbfafb 74     def _makeOne(self):
8392d7 75         klass = self._getTargetClass()
cbfafb 76         return klass()
8392d7 77
74409d 78     def test_provides_IRoutesMapper(self):
b60bdb 79         from pyramid.interfaces import IRoutesMapper
74409d 80         from zope.interface.verify import verifyObject
0c29cf 81
74409d 82         verifyObject(IRoutesMapper, self._makeOne())
CM 83
62267e 84     def test_no_route_matches(self):
cbfafb 85         mapper = self._makeOne()
11644e 86         request = self._getRequest(PATH_INFO='/')
CM 87         result = mapper(request)
cbfafb 88         self.assertEqual(result['match'], None)
CM 89         self.assertEqual(result['route'], None)
8392d7 90
0bf286 91     def test_connect_name_exists_removes_old(self):
CM 92         mapper = self._makeOne()
74409d 93         mapper.connect('foo', 'archives/:action/:article')
CM 94         mapper.connect('foo', 'archives/:action/:article2')
0bf286 95         self.assertEqual(len(mapper.routelist), 1)
CM 96         self.assertEqual(len(mapper.routes), 1)
0c29cf 97         self.assertEqual(
MM 98             mapper.routes['foo'].pattern, 'archives/:action/:article2'
99         )
100         self.assertEqual(
101             mapper.routelist[0].pattern, 'archives/:action/:article2'
102         )
0bf286 103
e725cf 104     def test_connect_static(self):
CM 105         mapper = self._makeOne()
106         mapper.connect('foo', 'archives/:action/:article', static=True)
107         self.assertEqual(len(mapper.routelist), 0)
108         self.assertEqual(len(mapper.routes), 1)
0c29cf 109         self.assertEqual(
MM 110             mapper.routes['foo'].pattern, 'archives/:action/:article'
111         )
e725cf 112
CM 113     def test_connect_static_overridden(self):
114         mapper = self._makeOne()
115         mapper.connect('foo', 'archives/:action/:article', static=True)
116         self.assertEqual(len(mapper.routelist), 0)
117         self.assertEqual(len(mapper.routes), 1)
0c29cf 118         self.assertEqual(
MM 119             mapper.routes['foo'].pattern, 'archives/:action/:article'
120         )
e725cf 121         mapper.connect('foo', 'archives/:action/:article2')
CM 122         self.assertEqual(len(mapper.routelist), 1)
123         self.assertEqual(len(mapper.routes), 1)
0c29cf 124         self.assertEqual(
MM 125             mapper.routes['foo'].pattern, 'archives/:action/:article2'
126         )
127         self.assertEqual(
128             mapper.routelist[0].pattern, 'archives/:action/:article2'
129         )
e725cf 130
b78b08 131     def test___call__pathinfo_cant_be_decoded(self):
CM 132         from pyramid.exceptions import URLDecodeError
0c29cf 133
b78b08 134         mapper = self._makeOne()
bc37a5 135         if PY2:
933944 136             path_info = b'\xff\xfe\xe6\x00'
bc37a5 137         else:
MM 138             path_info = b'\xff\xfe\xe6\x00'.decode('latin-1')
933944 139         request = self._getRequest(PATH_INFO=path_info)
b78b08 140         self.assertRaises(URLDecodeError, mapper, request)
CM 141
d3c39d 142     def test___call__route_matches(self):
cbfafb 143         mapper = self._makeOne()
74409d 144         mapper.connect('foo', 'archives/:action/:article')
11644e 145         request = self._getRequest(PATH_INFO='/archives/action1/article1')
CM 146         result = mapper(request)
cbfafb 147         self.assertEqual(result['route'], mapper.routes['foo'])
CM 148         self.assertEqual(result['match']['action'], 'action1')
149         self.assertEqual(result['match']['article'], 'article1')
8392d7 150
d3c39d 151     def test___call__route_matches_with_predicates(self):
cbfafb 152         mapper = self._makeOne()
0c29cf 153         mapper.connect(
MM 154             'foo', 'archives/:action/:article', predicates=[lambda *arg: True]
155         )
65476e 156         request = self._getRequest(PATH_INFO='/archives/action1/article1')
CM 157         result = mapper(request)
cbfafb 158         self.assertEqual(result['route'], mapper.routes['foo'])
CM 159         self.assertEqual(result['match']['action'], 'action1')
160         self.assertEqual(result['match']['article'], 'article1')
65476e 161
d3c39d 162     def test___call__route_fails_to_match_with_predicates(self):
cbfafb 163         mapper = self._makeOne()
0c29cf 164         mapper.connect(
MM 165             'foo',
166             'archives/:action/article1',
167             predicates=[lambda *arg: True, lambda *arg: False],
168         )
74409d 169         mapper.connect('bar', 'archives/:action/:article')
65476e 170         request = self._getRequest(PATH_INFO='/archives/action1/article1')
CM 171         result = mapper(request)
cbfafb 172         self.assertEqual(result['route'], mapper.routes['bar'])
CM 173         self.assertEqual(result['match']['action'], 'action1')
174         self.assertEqual(result['match']['article'], 'article1')
65476e 175
d3c39d 176     def test___call__custom_predicate_gets_info(self):
44bc11 177         mapper = self._makeOne()
0c29cf 178
44bc11 179         def pred(info, request):
0c29cf 180             self.assertEqual(info['match'], {'action': 'action1'})
44bc11 181             self.assertEqual(info['route'], mapper.routes['foo'])
CM 182             return True
0c29cf 183
74409d 184         mapper.connect('foo', 'archives/:action/article1', predicates=[pred])
44bc11 185         request = self._getRequest(PATH_INFO='/archives/action1/article1')
CM 186         mapper(request)
187
74a8f6 188     def test_cc_bug(self):
CM 189         # "unordered" as reported in IRC by author of
190         # http://labs.creativecommons.org/2010/01/13/cc-engine-and-web-non-frameworks/
191         mapper = self._makeOne()
74409d 192         mapper.connect('rdf', 'licenses/:license_code/:license_version/rdf')
0c29cf 193         mapper.connect(
MM 194             'juri', 'licenses/:license_code/:license_version/:jurisdiction'
195         )
74a8f6 196
CM 197         request = self._getRequest(PATH_INFO='/licenses/1/v2/rdf')
198         result = mapper(request)
199         self.assertEqual(result['route'], mapper.routes['rdf'])
200         self.assertEqual(result['match']['license_code'], '1')
201         self.assertEqual(result['match']['license_version'], 'v2')
202
203         request = self._getRequest(PATH_INFO='/licenses/1/v2/usa')
204         result = mapper(request)
205         self.assertEqual(result['route'], mapper.routes['juri'])
206         self.assertEqual(result['match']['license_code'], '1')
207         self.assertEqual(result['match']['license_version'], 'v2')
208         self.assertEqual(result['match']['jurisdiction'], 'usa')
209
d3c39d 210     def test___call__root_route_matches(self):
cbfafb 211         mapper = self._makeOne()
74409d 212         mapper.connect('root', '')
11644e 213         request = self._getRequest(PATH_INFO='/')
CM 214         result = mapper(request)
cbfafb 215         self.assertEqual(result['route'], mapper.routes['root'])
CM 216         self.assertEqual(result['match'], {})
9688ac 217
d3c39d 218     def test___call__root_route_matches2(self):
cbfafb 219         mapper = self._makeOne()
74409d 220         mapper.connect('root', '/')
11644e 221         request = self._getRequest(PATH_INFO='/')
CM 222         result = mapper(request)
cbfafb 223         self.assertEqual(result['route'], mapper.routes['root'])
CM 224         self.assertEqual(result['match'], {})
8392d7 225
d3c39d 226     def test___call__root_route_when_path_info_empty(self):
cbfafb 227         mapper = self._makeOne()
74409d 228         mapper.connect('root', '/')
22e72d 229         request = self._getRequest(PATH_INFO='')
CM 230         result = mapper(request)
cbfafb 231         self.assertEqual(result['route'], mapper.routes['root'])
CM 232         self.assertEqual(result['match'], {})
def444 233
c18e27 234     def test___call__root_route_when_path_info_notempty(self):
CM 235         mapper = self._makeOne()
236         mapper.connect('root', '/')
237         request = self._getRequest(PATH_INFO='/')
238         result = mapper(request)
239         self.assertEqual(result['route'], mapper.routes['root'])
240         self.assertEqual(result['match'], {})
241
d3c39d 242     def test___call__no_path_info(self):
cbfafb 243         mapper = self._makeOne()
74409d 244         mapper.connect('root', '/')
11644e 245         request = self._getRequest()
CM 246         result = mapper(request)
cbfafb 247         self.assertEqual(result['route'], mapper.routes['root'])
CM 248         self.assertEqual(result['match'], {})
dcb656 249
62267e 250     def test_has_routes(self):
cbfafb 251         mapper = self._makeOne()
62267e 252         self.assertEqual(mapper.has_routes(), False)
29e012 253         mapper.connect('whatever', 'archives/:action/:article')
62267e 254         self.assertEqual(mapper.has_routes(), True)
CM 255
750ce4 256     def test_get_routes(self):
b60bdb 257         from pyramid.urldispatch import Route
0c29cf 258
cbfafb 259         mapper = self._makeOne()
750ce4 260         self.assertEqual(mapper.get_routes(), [])
CM 261         mapper.connect('whatever', 'archives/:action/:article')
262         routes = mapper.get_routes()
263         self.assertEqual(len(routes), 1)
264         self.assertEqual(routes[0].__class__, Route)
265
74409d 266     def test_get_route_matches(self):
CM 267         mapper = self._makeOne()
268         mapper.connect('whatever', 'archives/:action/:article')
269         result = mapper.get_route('whatever')
270         self.assertEqual(result.pattern, 'archives/:action/:article')
271
272     def test_get_route_misses(self):
273         mapper = self._makeOne()
274         result = mapper.get_route('whatever')
275         self.assertEqual(result, None)
276
05c023 277     def test_generate(self):
cbfafb 278         mapper = self._makeOne()
0c29cf 279
05c023 280         def generator(kw):
CM 281             return 123
0c29cf 282
05c023 283         route = DummyRoute(generator)
0c29cf 284         mapper.routes['abc'] = route
05c023 285         self.assertEqual(mapper.generate('abc', {}), 123)
0c29cf 286
62267e 287
05c023 288 class TestCompileRoute(unittest.TestCase):
4018ad 289     def _callFUT(self, pattern):
b60bdb 290         from pyramid.urldispatch import _compile_route
0c29cf 291
4018ad 292         return _compile_route(pattern)
05c023 293
CM 294     def test_no_star(self):
295         matcher, generator = self._callFUT('/foo/:baz/biz/:buz/bar')
0c29cf 296         self.assertEqual(
MM 297             matcher('/foo/baz/biz/buz/bar'), {'baz': 'baz', 'buz': 'buz'}
298         )
05c023 299         self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
0c29cf 300         self.assertEqual(generator({'baz': 1, 'buz': 2}), '/foo/1/biz/2/bar')
05c023 301
CM 302     def test_with_star(self):
303         matcher, generator = self._callFUT('/foo/:baz/biz/:buz/bar*traverse')
0c29cf 304         self.assertEqual(
MM 305             matcher('/foo/baz/biz/buz/bar'),
306             {'baz': 'baz', 'buz': 'buz', 'traverse': ()},
307         )
308         self.assertEqual(
309             matcher('/foo/baz/biz/buz/bar/everything/else/here'),
310             {
311                 'baz': 'baz',
312                 'buz': 'buz',
313                 'traverse': ('everything', 'else', 'here'),
314             },
315         )
05c023 316         self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
0c29cf 317         self.assertEqual(
MM 318             generator({'baz': 1, 'buz': 2, 'traverse': '/a/b'}),
319             '/foo/1/biz/2/bar/a/b',
320         )
321
49315b 322     def test_with_bracket_star(self):
3ce731 323         matcher, generator = self._callFUT(
0c29cf 324             '/foo/{baz}/biz/{buz}/bar{remainder:.*}'
MM 325         )
326         self.assertEqual(
327             matcher('/foo/baz/biz/buz/bar'),
328             {'baz': 'baz', 'buz': 'buz', 'remainder': ''},
329         )
330         self.assertEqual(
331             matcher('/foo/baz/biz/buz/bar/everything/else/here'),
332             {'baz': 'baz', 'buz': 'buz', 'remainder': '/everything/else/here'},
333         )
49315b 334         self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
0c29cf 335         self.assertEqual(
MM 336             generator({'baz': 1, 'buz': 2, 'remainder': '/a/b'}),
337             '/foo/1/biz/2/bar/a/b',
338         )
05c023 339
CM 340     def test_no_beginning_slash(self):
341         matcher, generator = self._callFUT('foo/:baz/biz/:buz/bar')
0c29cf 342         self.assertEqual(
MM 343             matcher('/foo/baz/biz/buz/bar'), {'baz': 'baz', 'buz': 'buz'}
344         )
05c023 345         self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
0c29cf 346         self.assertEqual(generator({'baz': 1, 'buz': 2}), '/foo/1/biz/2/bar')
05c023 347
e84116 348     def test_custom_regex(self):
25737f 349         matcher, generator = self._callFUT(
MM 350             'foo/{baz}/biz/{buz:[^/\\.]+}.{bar}'
351         )
0c29cf 352         self.assertEqual(
MM 353             matcher('/foo/baz/biz/buz.bar'),
354             {'baz': 'baz', 'buz': 'buz', 'bar': 'bar'},
355         )
e84116 356         self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
0c29cf 357         self.assertEqual(
MM 358             generator({'baz': 1, 'buz': 2, 'bar': 'html'}), '/foo/1/biz/2.html'
359         )
360
88bbd4 361     def test_custom_regex_with_colons(self):
0c29cf 362         matcher, generator = self._callFUT(
25737f 363             'foo/{baz}/biz/{buz:(?:[^/\\.]+)}.{bar}'
0c29cf 364         )
MM 365         self.assertEqual(
366             matcher('/foo/baz/biz/buz.bar'),
367             {'baz': 'baz', 'buz': 'buz', 'bar': 'bar'},
368         )
88bbd4 369         self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
0c29cf 370         self.assertEqual(
MM 371             generator({'baz': 1, 'buz': 2, 'bar': 'html'}), '/foo/1/biz/2.html'
372         )
959523 373
3ce731 374     def test_mixed_newstyle_oldstyle_pattern_defaults_to_newstyle(self):
CM 375         # pattern: '\\/foo\\/(?P<baz>abc)\\/biz\\/(?P<buz>[^/]+)\\/bar$'
376         # note presence of :abc in pattern (oldstyle match)
377         matcher, generator = self._callFUT('foo/{baz:abc}/biz/{buz}/bar')
0c29cf 378         self.assertEqual(
MM 379             matcher('/foo/abc/biz/buz/bar'), {'baz': 'abc', 'buz': 'buz'}
380         )
381         self.assertEqual(generator({'baz': 1, 'buz': 2}), '/foo/1/biz/2/bar')
3ce731 382
e39ddf 383     def test_custom_regex_with_embedded_squigglies(self):
25737f 384         matcher, generator = self._callFUT('/{buz:\\d{4}}')
0c29cf 385         self.assertEqual(matcher('/2001'), {'buz': '2001'})
e39ddf 386         self.assertEqual(matcher('/200'), None)
0c29cf 387         self.assertEqual(generator({'buz': 2001}), '/2001')
959523 388
e39ddf 389     def test_custom_regex_with_embedded_squigglies2(self):
25737f 390         matcher, generator = self._callFUT('/{buz:\\d{3,4}}')
0c29cf 391         self.assertEqual(matcher('/2001'), {'buz': '2001'})
MM 392         self.assertEqual(matcher('/200'), {'buz': '200'})
e39ddf 393         self.assertEqual(matcher('/20'), None)
0c29cf 394         self.assertEqual(generator({'buz': 2001}), '/2001')
e39ddf 395
JG 396     def test_custom_regex_with_embedded_squigglies3(self):
a511b1 397         matcher, generator = self._callFUT(
25737f 398             '/{buz:(\\d{2}|\\d{4})-[a-zA-Z]{3,4}-\\d{2}}'
0c29cf 399         )
MM 400         self.assertEqual(matcher('/2001-Nov-15'), {'buz': '2001-Nov-15'})
401         self.assertEqual(matcher('/99-June-10'), {'buz': '99-June-10'})
e39ddf 402         self.assertEqual(matcher('/2-Nov-15'), None)
JG 403         self.assertEqual(matcher('/200-Nov-15'), None)
404         self.assertEqual(matcher('/2001-No-15'), None)
0c29cf 405         self.assertEqual(generator({'buz': '2001-Nov-15'}), '/2001-Nov-15')
MM 406         self.assertEqual(generator({'buz': '99-June-10'}), '/99-June-10')
a511b1 407
CM 408     def test_pattern_with_high_order_literal(self):
409         pattern = text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8')
410         matcher, generator = self._callFUT(pattern)
0c29cf 411         self.assertEqual(
MM 412             matcher(text_(b'/La Pe\xc3\xb1a/x', 'utf-8')), {'x': 'x'}
413         )
414         self.assertEqual(generator({'x': '1'}), '/La%20Pe%C3%B1a/1')
a511b1 415
CM 416     def test_pattern_generate_with_high_order_dynamic(self):
417         pattern = '/{x}'
418         _, generator = self._callFUT(pattern)
419         self.assertEqual(
0c29cf 420             generator({'x': text_(b'La Pe\xc3\xb1a', 'utf-8')}),
MM 421             '/La%20Pe%C3%B1a',
422         )
a511b1 423
CM 424     def test_docs_sample_generate(self):
425         # sample from urldispatch.rst
426         pattern = text_(b'/La Pe\xc3\xb1a/{city}', 'utf-8')
427         _, generator = self._callFUT(pattern)
428         self.assertEqual(
0c29cf 429             generator({'city': text_(b'Qu\xc3\xa9bec', 'utf-8')}),
MM 430             '/La%20Pe%C3%B1a/Qu%C3%A9bec',
431         )
a511b1 432
CM 433     def test_generate_with_mixedtype_values(self):
434         pattern = '/{city}/{state}'
435         _, generator = self._callFUT(pattern)
436         result = generator(
0c29cf 437             {
MM 438                 'city': text_(b'Qu\xc3\xa9bec', 'utf-8'),
439                 'state': b'La Pe\xc3\xb1a',
440             }
441         )
a511b1 442         self.assertEqual(result, '/Qu%C3%A9bec/La%20Pe%C3%B1a')
CM 443         # should be a native string
444         self.assertEqual(type(result), str)
0482bd 445
785358 446     def test_highorder_pattern_utf8(self):
CM 447         pattern = b'/La Pe\xc3\xb1a/{city}'
448         self.assertRaises(ValueError, self._callFUT, pattern)
449
450     def test_generate_with_string_remainder_and_unicode_replacement(self):
451         pattern = text_(b'/abc*remainder', 'utf-8')
452         _, generator = self._callFUT(pattern)
453         result = generator(
454             {'remainder': text_(b'/Qu\xc3\xa9bec/La Pe\xc3\xb1a', 'utf-8')}
0c29cf 455         )
785358 456         self.assertEqual(result, '/abc/Qu%C3%A9bec/La%20Pe%C3%B1a')
CM 457         # should be a native string
458         self.assertEqual(type(result), str)
459
460     def test_generate_with_string_remainder_and_nonstring_replacement(self):
461         pattern = text_(b'/abc/*remainder', 'utf-8')
462         _, generator = self._callFUT(pattern)
0c29cf 463         result = generator({'remainder': None})
785358 464         self.assertEqual(result, '/abc/None')
CM 465         # should be a native string
466         self.assertEqual(type(result), str)
467
0c29cf 468
dceff5 469 class TestCompileRouteFunctional(unittest.TestCase):
523132 470     def matches(self, pattern, path, expected):
b60bdb 471         from pyramid.urldispatch import _compile_route
0c29cf 472
523132 473         matcher = _compile_route(pattern)[0]
CM 474         result = matcher(path)
475         self.assertEqual(result, expected)
17358d 476
CM 477     def generates(self, pattern, dict, result):
b60bdb 478         from pyramid.urldispatch import _compile_route
0c29cf 479
17358d 480         self.assertEqual(_compile_route(pattern)[1](dict), result)
CM 481
dceff5 482     def test_matcher_functional_notdynamic(self):
17358d 483         self.matches('/', '', None)
CM 484         self.matches('', '', None)
485         self.matches('/', '/foo', None)
486         self.matches('/foo/', '/foo', None)
dceff5 487         self.matches('', '/', {})
CM 488         self.matches('/', '/', {})
489
490     def test_matcher_functional_newstyle(self):
491         self.matches('/{x}', '', None)
492         self.matches('/{x}', '/', None)
493         self.matches('/abc/{def}', '/abc/', None)
0c29cf 494         self.matches('/{x}', '/a', {'x': 'a'})
MM 495         self.matches('zzz/{x}', '/zzz/abc', {'x': 'abc'})
496         self.matches(
497             'zzz/{x}*traverse', '/zzz/abc', {'x': 'abc', 'traverse': ()}
498         )
499         self.matches(
500             'zzz/{x}*traverse',
501             '/zzz/abc/def/g',
502             {'x': 'abc', 'traverse': ('def', 'g')},
503         )
504         self.matches('*traverse', '/zzz/abc', {'traverse': ('zzz', 'abc')})
505         self.matches('*traverse', '/zzz/ abc', {'traverse': ('zzz', ' abc')})
10ddb6 506         # '/La%20Pe%C3%B1a'
0c29cf 507         self.matches(
MM 508             '{x}',
509             text_(b'/La Pe\xc3\xb1a', 'utf-8'),
510             {'x': text_(b'La Pe\xc3\xb1a', 'utf-8')},
511         )
f84147 512         # '/La%20Pe%C3%B1a/x'
0c29cf 513         self.matches(
MM 514             '*traverse',
515             text_(b'/La Pe\xc3\xb1a/x'),
516             {'traverse': (text_(b'La Pe\xc3\xb1a'), 'x')},
517         )
518         self.matches('/foo/{id}.html', '/foo/bar.html', {'id': 'bar'})
519         self.matches(
520             '/{num:[0-9]+}/*traverse',
521             '/555/abc/def',
522             {'num': '555', 'traverse': ('abc', 'def')},
523         )
524         self.matches(
525             '/{num:[0-9]*}/*traverse',
526             '/555/abc/def',
527             {'num': '555', 'traverse': ('abc', 'def')},
528         )
529         self.matches('zzz/{_}', '/zzz/abc', {'_': 'abc'})
530         self.matches('zzz/{_abc}', '/zzz/abc', {'_abc': 'abc'})
531         self.matches('zzz/{abc_def}', '/zzz/abc', {'abc_def': 'abc'})
dceff5 532
CM 533     def test_matcher_functional_oldstyle(self):
17358d 534         self.matches('/:x', '', None)
523132 535         self.matches('/:x', '/', None)
CM 536         self.matches('/abc/:def', '/abc/', None)
0c29cf 537         self.matches('/:x', '/a', {'x': 'a'})
MM 538         self.matches('zzz/:x', '/zzz/abc', {'x': 'abc'})
539         self.matches(
540             'zzz/:x*traverse', '/zzz/abc', {'x': 'abc', 'traverse': ()}
541         )
542         self.matches(
543             'zzz/:x*traverse',
544             '/zzz/abc/def/g',
545             {'x': 'abc', 'traverse': ('def', 'g')},
546         )
547         self.matches('*traverse', '/zzz/abc', {'traverse': ('zzz', 'abc')})
548         self.matches('*traverse', '/zzz/ abc', {'traverse': ('zzz', ' abc')})
10ddb6 549         # '/La%20Pe%C3%B1a'
a511b1 550         # pattern, path, expected
0c29cf 551         self.matches(
MM 552             ':x',
553             text_(b'/La Pe\xc3\xb1a', 'utf-8'),
554             {'x': text_(b'La Pe\xc3\xb1a', 'utf-8')},
555         )
f84147 556         # '/La%20Pe%C3%B1a/x'
0c29cf 557         self.matches(
MM 558             '*traverse',
559             text_(b'/La Pe\xc3\xb1a/x', 'utf-8'),
560             {'traverse': (text_(b'La Pe\xc3\xb1a', 'utf-8'), 'x')},
561         )
562         self.matches('/foo/:id.html', '/foo/bar.html', {'id': 'bar'})
563         self.matches('/foo/:id_html', '/foo/bar_html', {'id_html': 'bar_html'})
564         self.matches('zzz/:_', '/zzz/abc', {'_': 'abc'})
565         self.matches('zzz/:_abc', '/zzz/abc', {'_abc': 'abc'})
566         self.matches('zzz/:abc_def', '/zzz/abc', {'abc_def': 'abc'})
dceff5 567
CM 568     def test_generator_functional_notdynamic(self):
17358d 569         self.generates('', {}, '/')
CM 570         self.generates('/', {}, '/')
dceff5 571
CM 572     def test_generator_functional_newstyle(self):
0c29cf 573         self.generates('/{x}', {'x': ''}, '/')
MM 574         self.generates('/{x}', {'x': 'a'}, '/a')
575         self.generates('/{x}', {'x': 'a/b/c'}, '/a/b/c')
576         self.generates('/{x}', {'x': ':@&+$,'}, '/:@&+$,')
577         self.generates('zzz/{x}', {'x': 'abc'}, '/zzz/abc')
578         self.generates(
579             'zzz/{x}*traverse', {'x': 'abc', 'traverse': ''}, '/zzz/abc'
580         )
581         self.generates(
582             'zzz/{x}*traverse',
583             {'x': 'abc', 'traverse': '/def/g'},
584             '/zzz/abc/def/g',
585         )
586         self.generates(
587             'zzz/{x}*traverse',
588             {'x': ':@&+$,', 'traverse': '/:@&+$,'},
589             '/zzz/:@&+$,/:@&+$,',
590         )
591         self.generates(
592             '/{x}',
593             {'x': text_(b'/La Pe\xc3\xb1a', 'utf-8')},
594             '//La%20Pe%C3%B1a',
595         )
596         self.generates(
597             '/{x}*y',
598             {'x': text_(b'/La Pe\xc3\xb1a', 'utf-8'), 'y': '/rest/of/path'},
599             '//La%20Pe%C3%B1a/rest/of/path',
600         )
601         self.generates(
602             '*traverse',
603             {'traverse': ('a', text_(b'La Pe\xf1a'))},
604             '/a/La%20Pe%C3%B1a',
605         )
606         self.generates('/foo/{id}.html', {'id': 'bar'}, '/foo/bar.html')
607         self.generates('/foo/{_}', {'_': '20'}, '/foo/20')
608         self.generates('/foo/{_abc}', {'_abc': '20'}, '/foo/20')
609         self.generates('/foo/{abc_def}', {'abc_def': '20'}, '/foo/20')
610
dceff5 611     def test_generator_functional_oldstyle(self):
0c29cf 612         self.generates('/:x', {'x': ''}, '/')
MM 613         self.generates('/:x', {'x': 'a'}, '/a')
614         self.generates('zzz/:x', {'x': 'abc'}, '/zzz/abc')
615         self.generates(
616             'zzz/:x*traverse', {'x': 'abc', 'traverse': ''}, '/zzz/abc'
617         )
618         self.generates(
619             'zzz/:x*traverse',
620             {'x': 'abc', 'traverse': '/def/g'},
621             '/zzz/abc/def/g',
622         )
623         self.generates(
624             '/:x',
625             {'x': text_(b'/La Pe\xc3\xb1a', 'utf-8')},
626             '//La%20Pe%C3%B1a',
627         )
628         self.generates(
629             '/:x*y',
630             {'x': text_(b'/La Pe\xc3\xb1a', 'utf-8'), 'y': '/rest/of/path'},
631             '//La%20Pe%C3%B1a/rest/of/path',
632         )
633         self.generates(
634             '*traverse',
635             {'traverse': ('a', text_(b'La Pe\xf1a'))},
636             '/a/La%20Pe%C3%B1a',
637         )
638         self.generates('/foo/:id.html', {'id': 'bar'}, '/foo/bar.html')
639         self.generates('/foo/:_', {'_': '20'}, '/foo/20')
640         self.generates('/foo/:_abc', {'_abc': '20'}, '/foo/20')
641         self.generates('/foo/:abc_def', {'abc_def': '20'}, '/foo/20')
642
05c023 643
e62e47 644 class DummyContext(object):
CM 645     """ """
0c29cf 646
MM 647
e62e47 648 class DummyRequest(object):
11644e 649     def __init__(self, environ):
CM 650         self.environ = environ
0c29cf 651
MM 652
05c023 653 class DummyRoute(object):
CM 654     def __init__(self, generator):
655         self.generate = generator