Michael Merickel
2018-10-18 f28dbb0ba8d276fad10a3cd25e4d60b298702d83
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):
4018ad 349         matcher, generator = self._callFUT('foo/{baz}/biz/{buz:[^/\.]+}.{bar}')
0c29cf 350         self.assertEqual(
MM 351             matcher('/foo/baz/biz/buz.bar'),
352             {'baz': 'baz', 'buz': 'buz', 'bar': 'bar'},
353         )
e84116 354         self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
0c29cf 355         self.assertEqual(
MM 356             generator({'baz': 1, 'buz': 2, 'bar': 'html'}), '/foo/1/biz/2.html'
357         )
358
88bbd4 359     def test_custom_regex_with_colons(self):
0c29cf 360         matcher, generator = self._callFUT(
MM 361             'foo/{baz}/biz/{buz:(?:[^/\.]+)}.{bar}'
362         )
363         self.assertEqual(
364             matcher('/foo/baz/biz/buz.bar'),
365             {'baz': 'baz', 'buz': 'buz', 'bar': 'bar'},
366         )
88bbd4 367         self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
0c29cf 368         self.assertEqual(
MM 369             generator({'baz': 1, 'buz': 2, 'bar': 'html'}), '/foo/1/biz/2.html'
370         )
959523 371
3ce731 372     def test_mixed_newstyle_oldstyle_pattern_defaults_to_newstyle(self):
CM 373         # pattern: '\\/foo\\/(?P<baz>abc)\\/biz\\/(?P<buz>[^/]+)\\/bar$'
374         # note presence of :abc in pattern (oldstyle match)
375         matcher, generator = self._callFUT('foo/{baz:abc}/biz/{buz}/bar')
0c29cf 376         self.assertEqual(
MM 377             matcher('/foo/abc/biz/buz/bar'), {'baz': 'abc', 'buz': 'buz'}
378         )
379         self.assertEqual(generator({'baz': 1, 'buz': 2}), '/foo/1/biz/2/bar')
3ce731 380
e39ddf 381     def test_custom_regex_with_embedded_squigglies(self):
JG 382         matcher, generator = self._callFUT('/{buz:\d{4}}')
0c29cf 383         self.assertEqual(matcher('/2001'), {'buz': '2001'})
e39ddf 384         self.assertEqual(matcher('/200'), None)
0c29cf 385         self.assertEqual(generator({'buz': 2001}), '/2001')
959523 386
e39ddf 387     def test_custom_regex_with_embedded_squigglies2(self):
JG 388         matcher, generator = self._callFUT('/{buz:\d{3,4}}')
0c29cf 389         self.assertEqual(matcher('/2001'), {'buz': '2001'})
MM 390         self.assertEqual(matcher('/200'), {'buz': '200'})
e39ddf 391         self.assertEqual(matcher('/20'), None)
0c29cf 392         self.assertEqual(generator({'buz': 2001}), '/2001')
e39ddf 393
JG 394     def test_custom_regex_with_embedded_squigglies3(self):
a511b1 395         matcher, generator = self._callFUT(
0c29cf 396             '/{buz:(\d{2}|\d{4})-[a-zA-Z]{3,4}-\d{2}}'
MM 397         )
398         self.assertEqual(matcher('/2001-Nov-15'), {'buz': '2001-Nov-15'})
399         self.assertEqual(matcher('/99-June-10'), {'buz': '99-June-10'})
e39ddf 400         self.assertEqual(matcher('/2-Nov-15'), None)
JG 401         self.assertEqual(matcher('/200-Nov-15'), None)
402         self.assertEqual(matcher('/2001-No-15'), None)
0c29cf 403         self.assertEqual(generator({'buz': '2001-Nov-15'}), '/2001-Nov-15')
MM 404         self.assertEqual(generator({'buz': '99-June-10'}), '/99-June-10')
a511b1 405
CM 406     def test_pattern_with_high_order_literal(self):
407         pattern = text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8')
408         matcher, generator = self._callFUT(pattern)
0c29cf 409         self.assertEqual(
MM 410             matcher(text_(b'/La Pe\xc3\xb1a/x', 'utf-8')), {'x': 'x'}
411         )
412         self.assertEqual(generator({'x': '1'}), '/La%20Pe%C3%B1a/1')
a511b1 413
CM 414     def test_pattern_generate_with_high_order_dynamic(self):
415         pattern = '/{x}'
416         _, generator = self._callFUT(pattern)
417         self.assertEqual(
0c29cf 418             generator({'x': text_(b'La Pe\xc3\xb1a', 'utf-8')}),
MM 419             '/La%20Pe%C3%B1a',
420         )
a511b1 421
CM 422     def test_docs_sample_generate(self):
423         # sample from urldispatch.rst
424         pattern = text_(b'/La Pe\xc3\xb1a/{city}', 'utf-8')
425         _, generator = self._callFUT(pattern)
426         self.assertEqual(
0c29cf 427             generator({'city': text_(b'Qu\xc3\xa9bec', 'utf-8')}),
MM 428             '/La%20Pe%C3%B1a/Qu%C3%A9bec',
429         )
a511b1 430
CM 431     def test_generate_with_mixedtype_values(self):
432         pattern = '/{city}/{state}'
433         _, generator = self._callFUT(pattern)
434         result = generator(
0c29cf 435             {
MM 436                 'city': text_(b'Qu\xc3\xa9bec', 'utf-8'),
437                 'state': b'La Pe\xc3\xb1a',
438             }
439         )
a511b1 440         self.assertEqual(result, '/Qu%C3%A9bec/La%20Pe%C3%B1a')
CM 441         # should be a native string
442         self.assertEqual(type(result), str)
0482bd 443
785358 444     def test_highorder_pattern_utf8(self):
CM 445         pattern = b'/La Pe\xc3\xb1a/{city}'
446         self.assertRaises(ValueError, self._callFUT, pattern)
447
448     def test_generate_with_string_remainder_and_unicode_replacement(self):
449         pattern = text_(b'/abc*remainder', 'utf-8')
450         _, generator = self._callFUT(pattern)
451         result = generator(
452             {'remainder': text_(b'/Qu\xc3\xa9bec/La Pe\xc3\xb1a', 'utf-8')}
0c29cf 453         )
785358 454         self.assertEqual(result, '/abc/Qu%C3%A9bec/La%20Pe%C3%B1a')
CM 455         # should be a native string
456         self.assertEqual(type(result), str)
457
458     def test_generate_with_string_remainder_and_nonstring_replacement(self):
459         pattern = text_(b'/abc/*remainder', 'utf-8')
460         _, generator = self._callFUT(pattern)
0c29cf 461         result = generator({'remainder': None})
785358 462         self.assertEqual(result, '/abc/None')
CM 463         # should be a native string
464         self.assertEqual(type(result), str)
465
0c29cf 466
dceff5 467 class TestCompileRouteFunctional(unittest.TestCase):
523132 468     def matches(self, pattern, path, expected):
b60bdb 469         from pyramid.urldispatch import _compile_route
0c29cf 470
523132 471         matcher = _compile_route(pattern)[0]
CM 472         result = matcher(path)
473         self.assertEqual(result, expected)
17358d 474
CM 475     def generates(self, pattern, dict, result):
b60bdb 476         from pyramid.urldispatch import _compile_route
0c29cf 477
17358d 478         self.assertEqual(_compile_route(pattern)[1](dict), result)
CM 479
dceff5 480     def test_matcher_functional_notdynamic(self):
17358d 481         self.matches('/', '', None)
CM 482         self.matches('', '', None)
483         self.matches('/', '/foo', None)
484         self.matches('/foo/', '/foo', None)
dceff5 485         self.matches('', '/', {})
CM 486         self.matches('/', '/', {})
487
488     def test_matcher_functional_newstyle(self):
489         self.matches('/{x}', '', None)
490         self.matches('/{x}', '/', None)
491         self.matches('/abc/{def}', '/abc/', None)
0c29cf 492         self.matches('/{x}', '/a', {'x': 'a'})
MM 493         self.matches('zzz/{x}', '/zzz/abc', {'x': 'abc'})
494         self.matches(
495             'zzz/{x}*traverse', '/zzz/abc', {'x': 'abc', 'traverse': ()}
496         )
497         self.matches(
498             'zzz/{x}*traverse',
499             '/zzz/abc/def/g',
500             {'x': 'abc', 'traverse': ('def', 'g')},
501         )
502         self.matches('*traverse', '/zzz/abc', {'traverse': ('zzz', 'abc')})
503         self.matches('*traverse', '/zzz/ abc', {'traverse': ('zzz', ' abc')})
10ddb6 504         # '/La%20Pe%C3%B1a'
0c29cf 505         self.matches(
MM 506             '{x}',
507             text_(b'/La Pe\xc3\xb1a', 'utf-8'),
508             {'x': text_(b'La Pe\xc3\xb1a', 'utf-8')},
509         )
f84147 510         # '/La%20Pe%C3%B1a/x'
0c29cf 511         self.matches(
MM 512             '*traverse',
513             text_(b'/La Pe\xc3\xb1a/x'),
514             {'traverse': (text_(b'La Pe\xc3\xb1a'), 'x')},
515         )
516         self.matches('/foo/{id}.html', '/foo/bar.html', {'id': 'bar'})
517         self.matches(
518             '/{num:[0-9]+}/*traverse',
519             '/555/abc/def',
520             {'num': '555', 'traverse': ('abc', 'def')},
521         )
522         self.matches(
523             '/{num:[0-9]*}/*traverse',
524             '/555/abc/def',
525             {'num': '555', 'traverse': ('abc', 'def')},
526         )
527         self.matches('zzz/{_}', '/zzz/abc', {'_': 'abc'})
528         self.matches('zzz/{_abc}', '/zzz/abc', {'_abc': 'abc'})
529         self.matches('zzz/{abc_def}', '/zzz/abc', {'abc_def': 'abc'})
dceff5 530
CM 531     def test_matcher_functional_oldstyle(self):
17358d 532         self.matches('/:x', '', None)
523132 533         self.matches('/:x', '/', None)
CM 534         self.matches('/abc/:def', '/abc/', None)
0c29cf 535         self.matches('/:x', '/a', {'x': 'a'})
MM 536         self.matches('zzz/:x', '/zzz/abc', {'x': 'abc'})
537         self.matches(
538             'zzz/:x*traverse', '/zzz/abc', {'x': 'abc', 'traverse': ()}
539         )
540         self.matches(
541             'zzz/:x*traverse',
542             '/zzz/abc/def/g',
543             {'x': 'abc', 'traverse': ('def', 'g')},
544         )
545         self.matches('*traverse', '/zzz/abc', {'traverse': ('zzz', 'abc')})
546         self.matches('*traverse', '/zzz/ abc', {'traverse': ('zzz', ' abc')})
10ddb6 547         # '/La%20Pe%C3%B1a'
a511b1 548         # pattern, path, expected
0c29cf 549         self.matches(
MM 550             ':x',
551             text_(b'/La Pe\xc3\xb1a', 'utf-8'),
552             {'x': text_(b'La Pe\xc3\xb1a', 'utf-8')},
553         )
f84147 554         # '/La%20Pe%C3%B1a/x'
0c29cf 555         self.matches(
MM 556             '*traverse',
557             text_(b'/La Pe\xc3\xb1a/x', 'utf-8'),
558             {'traverse': (text_(b'La Pe\xc3\xb1a', 'utf-8'), 'x')},
559         )
560         self.matches('/foo/:id.html', '/foo/bar.html', {'id': 'bar'})
561         self.matches('/foo/:id_html', '/foo/bar_html', {'id_html': 'bar_html'})
562         self.matches('zzz/:_', '/zzz/abc', {'_': 'abc'})
563         self.matches('zzz/:_abc', '/zzz/abc', {'_abc': 'abc'})
564         self.matches('zzz/:abc_def', '/zzz/abc', {'abc_def': 'abc'})
dceff5 565
CM 566     def test_generator_functional_notdynamic(self):
17358d 567         self.generates('', {}, '/')
CM 568         self.generates('/', {}, '/')
dceff5 569
CM 570     def test_generator_functional_newstyle(self):
0c29cf 571         self.generates('/{x}', {'x': ''}, '/')
MM 572         self.generates('/{x}', {'x': 'a'}, '/a')
573         self.generates('/{x}', {'x': 'a/b/c'}, '/a/b/c')
574         self.generates('/{x}', {'x': ':@&+$,'}, '/:@&+$,')
575         self.generates('zzz/{x}', {'x': 'abc'}, '/zzz/abc')
576         self.generates(
577             'zzz/{x}*traverse', {'x': 'abc', 'traverse': ''}, '/zzz/abc'
578         )
579         self.generates(
580             'zzz/{x}*traverse',
581             {'x': 'abc', 'traverse': '/def/g'},
582             '/zzz/abc/def/g',
583         )
584         self.generates(
585             'zzz/{x}*traverse',
586             {'x': ':@&+$,', 'traverse': '/:@&+$,'},
587             '/zzz/:@&+$,/:@&+$,',
588         )
589         self.generates(
590             '/{x}',
591             {'x': text_(b'/La Pe\xc3\xb1a', 'utf-8')},
592             '//La%20Pe%C3%B1a',
593         )
594         self.generates(
595             '/{x}*y',
596             {'x': text_(b'/La Pe\xc3\xb1a', 'utf-8'), 'y': '/rest/of/path'},
597             '//La%20Pe%C3%B1a/rest/of/path',
598         )
599         self.generates(
600             '*traverse',
601             {'traverse': ('a', text_(b'La Pe\xf1a'))},
602             '/a/La%20Pe%C3%B1a',
603         )
604         self.generates('/foo/{id}.html', {'id': 'bar'}, '/foo/bar.html')
605         self.generates('/foo/{_}', {'_': '20'}, '/foo/20')
606         self.generates('/foo/{_abc}', {'_abc': '20'}, '/foo/20')
607         self.generates('/foo/{abc_def}', {'abc_def': '20'}, '/foo/20')
608
dceff5 609     def test_generator_functional_oldstyle(self):
0c29cf 610         self.generates('/:x', {'x': ''}, '/')
MM 611         self.generates('/:x', {'x': 'a'}, '/a')
612         self.generates('zzz/:x', {'x': 'abc'}, '/zzz/abc')
613         self.generates(
614             'zzz/:x*traverse', {'x': 'abc', 'traverse': ''}, '/zzz/abc'
615         )
616         self.generates(
617             'zzz/:x*traverse',
618             {'x': 'abc', 'traverse': '/def/g'},
619             '/zzz/abc/def/g',
620         )
621         self.generates(
622             '/:x',
623             {'x': text_(b'/La Pe\xc3\xb1a', 'utf-8')},
624             '//La%20Pe%C3%B1a',
625         )
626         self.generates(
627             '/:x*y',
628             {'x': text_(b'/La Pe\xc3\xb1a', 'utf-8'), 'y': '/rest/of/path'},
629             '//La%20Pe%C3%B1a/rest/of/path',
630         )
631         self.generates(
632             '*traverse',
633             {'traverse': ('a', text_(b'La Pe\xf1a'))},
634             '/a/La%20Pe%C3%B1a',
635         )
636         self.generates('/foo/:id.html', {'id': 'bar'}, '/foo/bar.html')
637         self.generates('/foo/:_', {'_': '20'}, '/foo/20')
638         self.generates('/foo/:_abc', {'_abc': '20'}, '/foo/20')
639         self.generates('/foo/:abc_def', {'abc_def': '20'}, '/foo/20')
640
05c023 641
e62e47 642 class DummyContext(object):
CM 643     """ """
0c29cf 644
MM 645
e62e47 646 class DummyRequest(object):
11644e 647     def __init__(self, environ):
CM 648         self.environ = environ
0c29cf 649
MM 650
05c023 651 class DummyRoute(object):
CM 652     def __init__(self, generator):
653         self.generate = generator