werkzeug

Форк
0
/
test_routing.py 
1553 строки · 52.5 Кб
1
import gc
2
import typing as t
3
import uuid
4

5
import pytest
6

7
from werkzeug import routing as r
8
from werkzeug.datastructures import ImmutableDict
9
from werkzeug.datastructures import MultiDict
10
from werkzeug.exceptions import MethodNotAllowed
11
from werkzeug.exceptions import NotFound
12
from werkzeug.test import create_environ
13
from werkzeug.wrappers import Response
14

15

16
def test_basic_routing():
17
    map = r.Map(
18
        [
19
            r.Rule("/", endpoint="index"),
20
            r.Rule("/foo", endpoint="foo"),
21
            r.Rule("/bar/", endpoint="bar"),
22
            r.Rule("/ws", endpoint="ws", websocket=True),
23
            r.Rule("/", endpoint="indexws", websocket=True),
24
        ]
25
    )
26
    adapter = map.bind("example.org", "/")
27
    assert adapter.match("/") == ("index", {})
28
    assert adapter.match("/foo") == ("foo", {})
29
    assert adapter.match("/bar/") == ("bar", {})
30
    pytest.raises(r.RequestRedirect, lambda: adapter.match("/bar"))
31
    pytest.raises(NotFound, lambda: adapter.match("/blub"))
32

33
    adapter = map.bind("example.org", "/", url_scheme="ws")
34
    assert adapter.match("/") == ("indexws", {})
35

36
    adapter = map.bind("example.org", "/test")
37
    with pytest.raises(r.RequestRedirect) as excinfo:
38
        adapter.match("/bar")
39
    assert excinfo.value.new_url == "http://example.org/test/bar/"
40

41
    adapter = map.bind("example.org", "/")
42
    with pytest.raises(r.RequestRedirect) as excinfo:
43
        adapter.match("/bar")
44
    assert excinfo.value.new_url == "http://example.org/bar/"
45

46
    adapter = map.bind("example.org", "/")
47
    with pytest.raises(r.RequestRedirect) as excinfo:
48
        adapter.match("/bar", query_args={"aha": "muhaha"})
49
    assert excinfo.value.new_url == "http://example.org/bar/?aha=muhaha"
50

51
    adapter = map.bind("example.org", "/")
52
    with pytest.raises(r.RequestRedirect) as excinfo:
53
        adapter.match("/bar", query_args="aha=muhaha")
54
    assert excinfo.value.new_url == "http://example.org/bar/?aha=muhaha"
55

56
    adapter = map.bind_to_environ(create_environ("/bar?foo=bar", "http://example.org/"))
57
    with pytest.raises(r.RequestRedirect) as excinfo:
58
        adapter.match()
59
    assert excinfo.value.new_url == "http://example.org/bar/?foo=bar"
60

61
    adapter = map.bind("example.org", "/ws", url_scheme="wss")
62
    assert adapter.match("/ws", websocket=True) == ("ws", {})
63
    with pytest.raises(r.WebsocketMismatch):
64
        adapter.match("/ws", websocket=False)
65
    with pytest.raises(r.WebsocketMismatch):
66
        adapter.match("/foo", websocket=True)
67

68
    adapter = map.bind_to_environ(
69
        create_environ(
70
            "/ws?foo=bar",
71
            "http://example.org/",
72
            headers=[("Connection", "Upgrade"), ("upgrade", "WebSocket")],
73
        )
74
    )
75
    assert adapter.match("/ws") == ("ws", {})
76
    with pytest.raises(r.WebsocketMismatch):
77
        adapter.match("/ws", websocket=False)
78

79
    adapter = map.bind_to_environ(
80
        create_environ(
81
            "/ws?foo=bar",
82
            "http://example.org/",
83
            headers=[("Connection", "keep-alive, Upgrade"), ("upgrade", "websocket")],
84
        )
85
    )
86
    assert adapter.match("/ws") == ("ws", {})
87
    with pytest.raises(r.WebsocketMismatch):
88
        adapter.match("/ws", websocket=False)
89

90

91
def test_merge_slashes_match():
92
    url_map = r.Map(
93
        [
94
            r.Rule("/no/tail", endpoint="no_tail"),
95
            r.Rule("/yes/tail/", endpoint="yes_tail"),
96
            r.Rule("/with/<path:path>", endpoint="with_path"),
97
            r.Rule("/no//merge", endpoint="no_merge", merge_slashes=False),
98
            r.Rule("/no/merging", endpoint="no_merging", merge_slashes=False),
99
        ]
100
    )
101
    adapter = url_map.bind("localhost", "/")
102

103
    with pytest.raises(r.RequestRedirect) as excinfo:
104
        adapter.match("/no//tail")
105

106
    assert excinfo.value.new_url.endswith("/no/tail")
107

108
    with pytest.raises(r.RequestRedirect) as excinfo:
109
        adapter.match("/yes//tail")
110

111
    assert excinfo.value.new_url.endswith("/yes/tail/")
112

113
    with pytest.raises(r.RequestRedirect) as excinfo:
114
        adapter.match("/yes/tail//")
115

116
    assert excinfo.value.new_url.endswith("/yes/tail/")
117

118
    assert adapter.match("/no/tail")[0] == "no_tail"
119
    assert adapter.match("/yes/tail/")[0] == "yes_tail"
120

121
    _, rv = adapter.match("/with/http://example.com/")
122
    assert rv["path"] == "http://example.com/"
123
    _, rv = adapter.match("/with/x//y")
124
    assert rv["path"] == "x//y"
125

126
    assert adapter.match("/no//merge")[0] == "no_merge"
127

128
    assert adapter.match("/no/merging")[0] == "no_merging"
129
    pytest.raises(NotFound, lambda: adapter.match("/no//merging"))
130

131

132
@pytest.mark.parametrize(
133
    ("path", "expected"),
134
    [("/merge/%//path", "/merge/%25/path"), ("/merge//st/path", "/merge/st/path")],
135
)
136
def test_merge_slash_encoding(path, expected):
137
    """This test is to make sure URLs are not double-encoded
138
    when a redirect is thrown with `merge_slash = True`"""
139
    url_map = r.Map(
140
        [
141
            r.Rule("/merge/<some>/path"),
142
        ]
143
    )
144
    adapter = url_map.bind("localhost", "/")
145

146
    with pytest.raises(r.RequestRedirect) as excinfo:
147
        adapter.match(path)
148

149
    assert excinfo.value.new_url.endswith(expected)
150

151

152
def test_merge_slashes_build():
153
    url_map = r.Map(
154
        [
155
            r.Rule("/yes//merge", endpoint="yes_merge"),
156
            r.Rule("/no//merge", endpoint="no_merge", merge_slashes=False),
157
        ]
158
    )
159
    adapter = url_map.bind("localhost", "/")
160
    assert adapter.build("yes_merge") == "/yes/merge"
161
    assert adapter.build("no_merge") == "/no//merge"
162

163

164
def test_strict_slashes_redirect():
165
    map = r.Map(
166
        [
167
            r.Rule("/bar/", endpoint="get", methods=["GET"]),
168
            r.Rule("/bar", endpoint="post", methods=["POST"]),
169
            r.Rule("/foo/", endpoint="foo", methods=["POST"]),
170
            r.Rule("/<path:var>/", endpoint="path", methods=["GET"]),
171
        ]
172
    )
173
    adapter = map.bind("example.org", "/")
174

175
    # Check if the actual routes works
176
    assert adapter.match("/bar/", method="GET") == ("get", {})
177
    assert adapter.match("/bar", method="POST") == ("post", {})
178
    assert adapter.match("/abc/", method="GET") == ("path", {"var": "abc"})
179

180
    # Check if exceptions are correct
181
    pytest.raises(r.RequestRedirect, adapter.match, "/bar", method="GET")
182
    pytest.raises(MethodNotAllowed, adapter.match, "/bar/", method="POST")
183
    with pytest.raises(r.RequestRedirect) as error_info:
184
        adapter.match("/foo", method="POST")
185
    assert error_info.value.code == 308
186
    with pytest.raises(r.RequestRedirect) as error_info:
187
        adapter.match("/abc", method="GET")
188
    assert error_info.value.new_url == "http://example.org/abc/"
189

190
    # Check differently defined order
191
    map = r.Map(
192
        [
193
            r.Rule("/bar", endpoint="post", methods=["POST"]),
194
            r.Rule("/bar/", endpoint="get", methods=["GET"]),
195
        ]
196
    )
197
    adapter = map.bind("example.org", "/")
198

199
    # Check if the actual routes works
200
    assert adapter.match("/bar/", method="GET") == ("get", {})
201
    assert adapter.match("/bar", method="POST") == ("post", {})
202

203
    # Check if exceptions are correct
204
    pytest.raises(r.RequestRedirect, adapter.match, "/bar", method="GET")
205
    pytest.raises(MethodNotAllowed, adapter.match, "/bar/", method="POST")
206

207
    # Check what happens when only slash route is defined
208
    map = r.Map([r.Rule("/bar/", endpoint="get", methods=["GET"])])
209
    adapter = map.bind("example.org", "/")
210

211
    # Check if the actual routes works
212
    assert adapter.match("/bar/", method="GET") == ("get", {})
213

214
    # Check if exceptions are correct
215
    pytest.raises(r.RequestRedirect, adapter.match, "/bar", method="GET")
216
    pytest.raises(MethodNotAllowed, adapter.match, "/bar/", method="POST")
217

218

219
def test_strict_slashes_leaves_dont_consume():
220
    # See issue #1074
221
    map = r.Map(
222
        [
223
            r.Rule("/path1", endpoint="leaf"),
224
            r.Rule("/path1/", endpoint="branch"),
225
            r.Rule("/path2", endpoint="leaf", strict_slashes=False),
226
            r.Rule("/path2/", endpoint="branch"),
227
            r.Rule("/path3", endpoint="leaf"),
228
            r.Rule("/path3/", endpoint="branch", strict_slashes=False),
229
            r.Rule("/path4", endpoint="leaf", strict_slashes=False),
230
            r.Rule("/path4/", endpoint="branch", strict_slashes=False),
231
            r.Rule("/path5", endpoint="leaf"),
232
        ],
233
        strict_slashes=False,
234
    )
235

236
    adapter = map.bind("example.org", "/")
237

238
    assert adapter.match("/path1", method="GET") == ("leaf", {})
239
    assert adapter.match("/path1/", method="GET") == ("branch", {})
240
    assert adapter.match("/path2", method="GET") == ("leaf", {})
241
    assert adapter.match("/path2/", method="GET") == ("branch", {})
242
    assert adapter.match("/path3", method="GET") == ("leaf", {})
243
    assert adapter.match("/path3/", method="GET") == ("branch", {})
244
    assert adapter.match("/path4", method="GET") == ("leaf", {})
245
    assert adapter.match("/path4/", method="GET") == ("branch", {})
246
    assert adapter.match("/path5/", method="GET") == ("leaf", {})
247

248

249
def test_environ_defaults():
250
    environ = create_environ("/foo")
251
    assert environ["PATH_INFO"] == "/foo"
252
    m = r.Map([r.Rule("/foo", endpoint="foo"), r.Rule("/bar", endpoint="bar")])
253
    a = m.bind_to_environ(environ)
254
    assert a.match("/foo") == ("foo", {})
255
    assert a.match() == ("foo", {})
256
    assert a.match("/bar") == ("bar", {})
257
    pytest.raises(NotFound, a.match, "/bars")
258

259

260
def test_environ_nonascii_pathinfo():
261
    environ = create_environ("/лошадь")
262
    m = r.Map([r.Rule("/", endpoint="index"), r.Rule("/лошадь", endpoint="horse")])
263
    a = m.bind_to_environ(environ)
264
    assert a.match("/") == ("index", {})
265
    assert a.match("/лошадь") == ("horse", {})
266
    pytest.raises(NotFound, a.match, "/барсук")
267

268

269
def test_basic_building():
270
    map = r.Map(
271
        [
272
            r.Rule("/", endpoint="index"),
273
            r.Rule("/foo", endpoint="foo"),
274
            r.Rule("/bar/<baz>", endpoint="bar"),
275
            r.Rule("/bar/<int:bazi>", endpoint="bari"),
276
            r.Rule("/bar/<float:bazf>", endpoint="barf"),
277
            r.Rule("/bar/<path:bazp>", endpoint="barp"),
278
            r.Rule("/hehe", endpoint="blah", subdomain="blah"),
279
            r.Rule("/ws", endpoint="ws", websocket=True),
280
        ]
281
    )
282
    adapter = map.bind("example.org", "/", subdomain="blah")
283

284
    assert adapter.build("index", {}) == "http://example.org/"
285
    assert adapter.build("foo", {}) == "http://example.org/foo"
286
    assert adapter.build("bar", {"baz": "blub"}) == "http://example.org/bar/blub"
287
    assert adapter.build("bari", {"bazi": 50}) == "http://example.org/bar/50"
288
    assert adapter.build("barf", {"bazf": 0.815}) == "http://example.org/bar/0.815"
289
    assert adapter.build("barp", {"bazp": "la/di"}) == "http://example.org/bar/la/di"
290
    assert adapter.build("blah", {}) == "/hehe"
291
    pytest.raises(r.BuildError, lambda: adapter.build("urks"))
292

293
    adapter = map.bind("example.org", "/test", subdomain="blah")
294
    assert adapter.build("index", {}) == "http://example.org/test/"
295
    assert adapter.build("foo", {}) == "http://example.org/test/foo"
296
    assert adapter.build("bar", {"baz": "blub"}) == "http://example.org/test/bar/blub"
297
    assert adapter.build("bari", {"bazi": 50}) == "http://example.org/test/bar/50"
298
    assert adapter.build("barf", {"bazf": 0.815}) == "http://example.org/test/bar/0.815"
299
    assert (
300
        adapter.build("barp", {"bazp": "la/di"}) == "http://example.org/test/bar/la/di"
301
    )
302
    assert adapter.build("blah", {}) == "/test/hehe"
303

304
    adapter = map.bind("example.org")
305
    assert adapter.build("foo", {}) == "/foo"
306
    assert adapter.build("foo", {}, force_external=True) == "http://example.org/foo"
307
    adapter = map.bind("example.org", url_scheme="")
308
    assert adapter.build("foo", {}) == "/foo"
309
    assert adapter.build("foo", {}, force_external=True) == "//example.org/foo"
310
    assert (
311
        adapter.build("foo", {}, url_scheme="https", force_external=True)
312
        == "https://example.org/foo"
313
    )
314

315
    adapter = map.bind("example.org", url_scheme="ws")
316
    assert adapter.build("ws", {}) == "ws://example.org/ws"
317
    assert adapter.build("foo", {}, force_external=True) == "http://example.org/foo"
318
    assert adapter.build("foo", {}) == "/foo"
319
    assert adapter.build("ws", {}, url_scheme="https") == "wss://example.org/ws"
320

321

322
def test_long_build():
323
    long_args = {f"v{x}": x for x in range(10000)}
324
    map = r.Map(
325
        [
326
            r.Rule(
327
                "".join(f"/<{k}>" for k in long_args.keys()),
328
                endpoint="bleep",
329
                build_only=True,
330
            )
331
        ]
332
    )
333
    adapter = map.bind("localhost", "/")
334
    url = f"{adapter.build('bleep', long_args)}/"
335
    for v in long_args.values():
336
        assert f"/{v}" in url
337

338

339
def test_defaults():
340
    map = r.Map(
341
        [
342
            r.Rule("/foo/", defaults={"page": 1}, endpoint="foo"),
343
            r.Rule("/foo/<int:page>", endpoint="foo"),
344
        ]
345
    )
346
    adapter = map.bind("example.org", "/")
347

348
    assert adapter.match("/foo/") == ("foo", {"page": 1})
349
    pytest.raises(r.RequestRedirect, lambda: adapter.match("/foo/1"))
350
    assert adapter.match("/foo/2") == ("foo", {"page": 2})
351
    assert adapter.build("foo", {}) == "/foo/"
352
    assert adapter.build("foo", {"page": 1}) == "/foo/"
353
    assert adapter.build("foo", {"page": 2}) == "/foo/2"
354

355

356
def test_negative():
357
    map = r.Map(
358
        [
359
            r.Rule("/foos/<int(signed=True):page>", endpoint="foos"),
360
            r.Rule("/bars/<float(signed=True):page>", endpoint="bars"),
361
            r.Rule("/foo/<int:page>", endpoint="foo"),
362
            r.Rule("/bar/<float:page>", endpoint="bar"),
363
        ]
364
    )
365
    adapter = map.bind("example.org", "/")
366

367
    assert adapter.match("/foos/-2") == ("foos", {"page": -2})
368
    assert adapter.match("/foos/-50") == ("foos", {"page": -50})
369
    assert adapter.match("/bars/-2.0") == ("bars", {"page": -2.0})
370
    assert adapter.match("/bars/-0.185") == ("bars", {"page": -0.185})
371

372
    # Make sure signed values are rejected in unsigned mode
373
    pytest.raises(NotFound, lambda: adapter.match("/foo/-2"))
374
    pytest.raises(NotFound, lambda: adapter.match("/foo/-50"))
375
    pytest.raises(NotFound, lambda: adapter.match("/bar/-0.185"))
376
    pytest.raises(NotFound, lambda: adapter.match("/bar/-2.0"))
377

378

379
def test_greedy():
380
    map = r.Map(
381
        [
382
            r.Rule("/foo", endpoint="foo"),
383
            r.Rule("/<path:bar>", endpoint="bar"),
384
            r.Rule("/<path:bar>/<path:blub>", endpoint="bar"),
385
        ]
386
    )
387
    adapter = map.bind("example.org", "/")
388

389
    assert adapter.match("/foo") == ("foo", {})
390
    assert adapter.match("/blub") == ("bar", {"bar": "blub"})
391
    assert adapter.match("/he/he") == ("bar", {"bar": "he", "blub": "he"})
392

393
    assert adapter.build("foo", {}) == "/foo"
394
    assert adapter.build("bar", {"bar": "blub"}) == "/blub"
395
    assert adapter.build("bar", {"bar": "blub", "blub": "bar"}) == "/blub/bar"
396

397

398
def test_path():
399
    map = r.Map(
400
        [
401
            r.Rule("/", defaults={"name": "FrontPage"}, endpoint="page"),
402
            r.Rule("/Special", endpoint="special"),
403
            r.Rule("/<int:year>", endpoint="year"),
404
            r.Rule("/<path:name>:foo", endpoint="foopage"),
405
            r.Rule("/<path:name>:<path:name2>", endpoint="twopage"),
406
            r.Rule("/<path:name>", endpoint="page"),
407
            r.Rule("/<path:name>/edit", endpoint="editpage"),
408
            r.Rule("/<path:name>/silly/<path:name2>", endpoint="sillypage"),
409
            r.Rule("/<path:name>/silly/<path:name2>/edit", endpoint="editsillypage"),
410
            r.Rule("/Talk:<path:name>", endpoint="talk"),
411
            r.Rule("/User:<username>", endpoint="user"),
412
            r.Rule("/User:<username>/<path:name>", endpoint="userpage"),
413
            r.Rule(
414
                "/User:<username>/comment/<int:id>-<int:replyId>",
415
                endpoint="usercomment",
416
            ),
417
            r.Rule("/Files/<path:file>", endpoint="files"),
418
            r.Rule("/<admin>/<manage>/<things>", endpoint="admin"),
419
        ]
420
    )
421
    adapter = map.bind("example.org", "/")
422

423
    assert adapter.match("/") == ("page", {"name": "FrontPage"})
424
    pytest.raises(r.RequestRedirect, lambda: adapter.match("/FrontPage"))
425
    assert adapter.match("/Special") == ("special", {})
426
    assert adapter.match("/2007") == ("year", {"year": 2007})
427
    assert adapter.match("/Some:foo") == ("foopage", {"name": "Some"})
428
    assert adapter.match("/Some:bar") == ("twopage", {"name": "Some", "name2": "bar"})
429
    assert adapter.match("/Some/Page") == ("page", {"name": "Some/Page"})
430
    assert adapter.match("/Some/Page/edit") == ("editpage", {"name": "Some/Page"})
431
    assert adapter.match("/Foo/silly/bar") == (
432
        "sillypage",
433
        {"name": "Foo", "name2": "bar"},
434
    )
435
    assert adapter.match("/Foo/silly/bar/edit") == (
436
        "editsillypage",
437
        {"name": "Foo", "name2": "bar"},
438
    )
439
    assert adapter.match("/Talk:Foo/Bar") == ("talk", {"name": "Foo/Bar"})
440
    assert adapter.match("/User:thomas") == ("user", {"username": "thomas"})
441
    assert adapter.match("/User:thomas/projects/werkzeug") == (
442
        "userpage",
443
        {"username": "thomas", "name": "projects/werkzeug"},
444
    )
445
    assert adapter.match("/User:thomas/comment/123-456") == (
446
        "usercomment",
447
        {"username": "thomas", "id": 123, "replyId": 456},
448
    )
449
    assert adapter.match("/Files/downloads/werkzeug/0.2.zip") == (
450
        "files",
451
        {"file": "downloads/werkzeug/0.2.zip"},
452
    )
453
    assert adapter.match("/Jerry/eats/cheese") == (
454
        "admin",
455
        {"admin": "Jerry", "manage": "eats", "things": "cheese"},
456
    )
457

458

459
def test_dispatch():
460
    env = create_environ("/")
461
    map = r.Map([r.Rule("/", endpoint="root"), r.Rule("/foo/", endpoint="foo")])
462
    adapter = map.bind_to_environ(env)
463

464
    raise_this = None
465

466
    def view_func(endpoint, values):
467
        if raise_this is not None:
468
            raise raise_this
469
        return Response(repr((endpoint, values)))
470

471
    def dispatch(path, quiet=False):
472
        return Response.force_type(
473
            adapter.dispatch(view_func, path, catch_http_exceptions=quiet), env
474
        )
475

476
    assert dispatch("/").data == b"('root', {})"
477
    assert dispatch("/foo").status_code == 308
478
    raise_this = NotFound()
479
    pytest.raises(NotFound, lambda: dispatch("/bar"))
480
    assert dispatch("/bar", True).status_code == 404
481

482

483
def test_http_host_before_server_name():
484
    env = {
485
        "HTTP_HOST": "wiki.example.com",
486
        "SERVER_NAME": "web0.example.com",
487
        "SERVER_PORT": "80",
488
        "SCRIPT_NAME": "",
489
        "PATH_INFO": "",
490
        "REQUEST_METHOD": "GET",
491
        "wsgi.url_scheme": "http",
492
    }
493
    map = r.Map([r.Rule("/", endpoint="index", subdomain="wiki")])
494
    adapter = map.bind_to_environ(env, server_name="example.com")
495
    assert adapter.match("/") == ("index", {})
496
    assert adapter.build("index", force_external=True) == "http://wiki.example.com/"
497
    assert adapter.build("index") == "/"
498

499
    env["HTTP_HOST"] = "admin.example.com"
500
    adapter = map.bind_to_environ(env, server_name="example.com")
501
    assert adapter.build("index") == "http://wiki.example.com/"
502

503

504
def test_invalid_subdomain_warning():
505
    env = create_environ("/foo")
506
    env["SERVER_NAME"] = env["HTTP_HOST"] = "foo.example.com"
507
    m = r.Map([r.Rule("/foo", endpoint="foo")])
508
    with pytest.warns(UserWarning) as record:
509
        a = m.bind_to_environ(env, server_name="bar.example.com")
510
    assert a.subdomain == "<invalid>"
511
    assert len(record) == 1
512

513

514
@pytest.mark.parametrize(
515
    ("base", "name"),
516
    (("http://localhost", "localhost:80"), ("https://localhost", "localhost:443")),
517
)
518
def test_server_name_match_default_port(base, name):
519
    environ = create_environ("/foo", base_url=base)
520
    map = r.Map([r.Rule("/foo", endpoint="foo")])
521
    adapter = map.bind_to_environ(environ, server_name=name)
522
    assert adapter.match() == ("foo", {})
523

524

525
def test_adapter_url_parameter_sorting():
526
    map = r.Map(
527
        [r.Rule("/", endpoint="index")], sort_parameters=True, sort_key=lambda x: x[1]
528
    )
529
    adapter = map.bind("localhost", "/")
530
    assert (
531
        adapter.build("index", {"x": 20, "y": 10, "z": 30}, force_external=True)
532
        == "http://localhost/?y=10&x=20&z=30"
533
    )
534

535

536
def test_request_direct_charset_bug():
537
    map = r.Map([r.Rule("/öäü/")])
538
    adapter = map.bind("localhost", "/")
539

540
    with pytest.raises(r.RequestRedirect) as excinfo:
541
        adapter.match("/öäü")
542
    assert excinfo.value.new_url == "http://localhost/%C3%B6%C3%A4%C3%BC/"
543

544

545
def test_request_redirect_default():
546
    map = r.Map([r.Rule("/foo", defaults={"bar": 42}), r.Rule("/foo/<int:bar>")])
547
    adapter = map.bind("localhost", "/")
548

549
    with pytest.raises(r.RequestRedirect) as excinfo:
550
        adapter.match("/foo/42")
551
    assert excinfo.value.new_url == "http://localhost/foo"
552

553

554
def test_request_redirect_default_subdomain():
555
    map = r.Map(
556
        [
557
            r.Rule("/foo", defaults={"bar": 42}, subdomain="test"),
558
            r.Rule("/foo/<int:bar>", subdomain="other"),
559
        ]
560
    )
561
    adapter = map.bind("localhost", "/", subdomain="other")
562

563
    with pytest.raises(r.RequestRedirect) as excinfo:
564
        adapter.match("/foo/42")
565
    assert excinfo.value.new_url == "http://test.localhost/foo"
566

567

568
def test_adapter_match_return_rule():
569
    rule = r.Rule("/foo/", endpoint="foo")
570
    map = r.Map([rule])
571
    adapter = map.bind("localhost", "/")
572
    assert adapter.match("/foo/", return_rule=True) == (rule, {})
573

574

575
def test_server_name_interpolation():
576
    server_name = "example.invalid"
577
    map = r.Map(
578
        [r.Rule("/", endpoint="index"), r.Rule("/", endpoint="alt", subdomain="alt")]
579
    )
580

581
    env = create_environ("/", f"http://{server_name}/")
582
    adapter = map.bind_to_environ(env, server_name=server_name)
583
    assert adapter.match() == ("index", {})
584

585
    env = create_environ("/", f"http://alt.{server_name}/")
586
    adapter = map.bind_to_environ(env, server_name=server_name)
587
    assert adapter.match() == ("alt", {})
588

589
    env = create_environ("/", f"http://{server_name}/")
590

591
    with pytest.warns(UserWarning):
592
        adapter = map.bind_to_environ(env, server_name="foo")
593

594
    assert adapter.subdomain == "<invalid>"
595

596

597
def test_rule_emptying():
598
    rule = r.Rule("/foo", {"meh": "muh"}, "x", ["POST"], False, "x", True, None)
599
    rule2 = rule.empty()
600
    assert rule.__dict__ == rule2.__dict__
601
    rule.methods.add("GET")
602
    assert rule.__dict__ != rule2.__dict__
603
    rule.methods.discard("GET")
604
    rule.defaults["meh"] = "aha"
605
    assert rule.__dict__ != rule2.__dict__
606

607

608
def test_rule_unhashable():
609
    rule = r.Rule("/foo", {"meh": "muh"}, "x", ["POST"], False, "x", True, None)
610
    pytest.raises(TypeError, hash, rule)
611

612

613
def test_rule_templates():
614
    testcase = r.RuleTemplate(
615
        [
616
            r.Submount(
617
                "/test/$app",
618
                [
619
                    r.Rule("/foo/", endpoint="handle_foo"),
620
                    r.Rule("/bar/", endpoint="handle_bar"),
621
                    r.Rule("/baz/", endpoint="handle_baz"),
622
                ],
623
            ),
624
            r.EndpointPrefix(
625
                "${app}",
626
                [
627
                    r.Rule("/${app}-blah", endpoint="bar"),
628
                    r.Rule("/${app}-meh", endpoint="baz"),
629
                ],
630
            ),
631
            r.Subdomain(
632
                "$app",
633
                [r.Rule("/blah", endpoint="x_bar"), r.Rule("/meh", endpoint="x_baz")],
634
            ),
635
        ]
636
    )
637

638
    url_map = r.Map(
639
        [
640
            testcase(app="test1"),
641
            testcase(app="test2"),
642
            testcase(app="test3"),
643
            testcase(app="test4"),
644
        ]
645
    )
646

647
    out = sorted((x.rule, x.subdomain, x.endpoint) for x in url_map.iter_rules())
648

649
    assert out == [
650
        ("/blah", "test1", "x_bar"),
651
        ("/blah", "test2", "x_bar"),
652
        ("/blah", "test3", "x_bar"),
653
        ("/blah", "test4", "x_bar"),
654
        ("/meh", "test1", "x_baz"),
655
        ("/meh", "test2", "x_baz"),
656
        ("/meh", "test3", "x_baz"),
657
        ("/meh", "test4", "x_baz"),
658
        ("/test/test1/bar/", "", "handle_bar"),
659
        ("/test/test1/baz/", "", "handle_baz"),
660
        ("/test/test1/foo/", "", "handle_foo"),
661
        ("/test/test2/bar/", "", "handle_bar"),
662
        ("/test/test2/baz/", "", "handle_baz"),
663
        ("/test/test2/foo/", "", "handle_foo"),
664
        ("/test/test3/bar/", "", "handle_bar"),
665
        ("/test/test3/baz/", "", "handle_baz"),
666
        ("/test/test3/foo/", "", "handle_foo"),
667
        ("/test/test4/bar/", "", "handle_bar"),
668
        ("/test/test4/baz/", "", "handle_baz"),
669
        ("/test/test4/foo/", "", "handle_foo"),
670
        ("/test1-blah", "", "test1bar"),
671
        ("/test1-meh", "", "test1baz"),
672
        ("/test2-blah", "", "test2bar"),
673
        ("/test2-meh", "", "test2baz"),
674
        ("/test3-blah", "", "test3bar"),
675
        ("/test3-meh", "", "test3baz"),
676
        ("/test4-blah", "", "test4bar"),
677
        ("/test4-meh", "", "test4baz"),
678
    ]
679

680

681
def test_non_string_parts():
682
    m = r.Map([r.Rule("/<foo>", endpoint="foo")])
683
    a = m.bind("example.com")
684
    assert a.build("foo", {"foo": 42}) == "/42"
685

686

687
def test_complex_routing_rules():
688
    m = r.Map(
689
        [
690
            r.Rule("/", endpoint="index"),
691
            r.Rule("/<int:blub>", endpoint="an_int"),
692
            r.Rule("/<blub>", endpoint="a_string"),
693
            r.Rule("/foo/", endpoint="nested"),
694
            r.Rule("/foobar/", endpoint="nestedbar"),
695
            r.Rule("/foo/<path:testing>/", endpoint="nested_show"),
696
            r.Rule("/foo/<path:testing>/edit", endpoint="nested_edit"),
697
            r.Rule("/users/", endpoint="users", defaults={"page": 1}),
698
            r.Rule("/users/page/<int:page>", endpoint="users"),
699
            r.Rule("/foox", endpoint="foox"),
700
            r.Rule("/<path:bar>/<path:blub>", endpoint="barx_path_path"),
701
        ]
702
    )
703
    a = m.bind("example.com")
704

705
    assert a.match("/") == ("index", {})
706
    assert a.match("/42") == ("an_int", {"blub": 42})
707
    assert a.match("/blub") == ("a_string", {"blub": "blub"})
708
    assert a.match("/foo/") == ("nested", {})
709
    assert a.match("/foobar/") == ("nestedbar", {})
710
    assert a.match("/foo/1/2/3/") == ("nested_show", {"testing": "1/2/3"})
711
    assert a.match("/foo/1/2/3/edit") == ("nested_edit", {"testing": "1/2/3"})
712
    assert a.match("/users/") == ("users", {"page": 1})
713
    assert a.match("/users/page/2") == ("users", {"page": 2})
714
    assert a.match("/foox") == ("foox", {})
715
    assert a.match("/1/2/3") == ("barx_path_path", {"bar": "1", "blub": "2/3"})
716

717
    assert a.build("index") == "/"
718
    assert a.build("an_int", {"blub": 42}) == "/42"
719
    assert a.build("a_string", {"blub": "test"}) == "/test"
720
    assert a.build("nested") == "/foo/"
721
    assert a.build("nestedbar") == "/foobar/"
722
    assert a.build("nested_show", {"testing": "1/2/3"}) == "/foo/1/2/3/"
723
    assert a.build("nested_edit", {"testing": "1/2/3"}) == "/foo/1/2/3/edit"
724
    assert a.build("users", {"page": 1}) == "/users/"
725
    assert a.build("users", {"page": 2}) == "/users/page/2"
726
    assert a.build("foox") == "/foox"
727
    assert a.build("barx_path_path", {"bar": "1", "blub": "2/3"}) == "/1/2/3"
728

729

730
def test_default_converters():
731
    class MyMap(r.Map):
732
        default_converters = r.Map.default_converters.copy()
733
        default_converters["foo"] = r.UnicodeConverter
734

735
    assert isinstance(r.Map.default_converters, ImmutableDict)
736
    m = MyMap(
737
        [
738
            r.Rule("/a/<foo:a>", endpoint="a"),
739
            r.Rule("/b/<foo:b>", endpoint="b"),
740
            r.Rule("/c/<c>", endpoint="c"),
741
        ],
742
        converters={"bar": r.UnicodeConverter},
743
    )
744
    a = m.bind("example.org", "/")
745
    assert a.match("/a/1") == ("a", {"a": "1"})
746
    assert a.match("/b/2") == ("b", {"b": "2"})
747
    assert a.match("/c/3") == ("c", {"c": "3"})
748
    assert "foo" not in r.Map.default_converters
749

750

751
def test_uuid_converter():
752
    m = r.Map([r.Rule("/a/<uuid:a_uuid>", endpoint="a")])
753
    a = m.bind("example.org", "/")
754
    route, kwargs = a.match("/a/a8098c1a-f86e-11da-bd1a-00112444be1e")
755
    assert type(kwargs["a_uuid"]) == uuid.UUID  # noqa: E721
756

757

758
def test_converter_with_tuples():
759
    """
760
    Regression test for https://github.com/pallets/werkzeug/issues/709
761
    """
762

763
    class TwoValueConverter(r.BaseConverter):
764
        part_isolating = False
765

766
        def __init__(self, *args, **kwargs):
767
            super().__init__(*args, **kwargs)
768
            self.regex = r"(\w\w+)/(\w\w+)"
769

770
        def to_python(self, two_values):
771
            one, two = two_values.split("/")
772
            return one, two
773

774
        def to_url(self, values):
775
            return f"{values[0]}/{values[1]}"
776

777
    map = r.Map(
778
        [r.Rule("/<two:foo>/", endpoint="handler")],
779
        converters={"two": TwoValueConverter},
780
    )
781
    a = map.bind("example.org", "/")
782
    route, kwargs = a.match("/qwert/yuiop/")
783
    assert kwargs["foo"] == ("qwert", "yuiop")
784

785

786
def test_nested_regex_groups():
787
    """
788
    Regression test for https://github.com/pallets/werkzeug/issues/2590
789
    """
790

791
    class RegexConverter(r.BaseConverter):
792
        def __init__(self, url_map, *items):
793
            super().__init__(url_map)
794
            self.part_isolating = False
795
            self.regex = items[0]
796

797
    # This is a regex pattern with nested groups
798
    DATE_PATTERN = r"((\d{8}T\d{6}([.,]\d{1,3})?)|(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([.,]\d{1,3})?))Z"  # noqa: E501
799

800
    map = r.Map(
801
        [
802
            r.Rule(
803
                f"/<regex('{DATE_PATTERN}'):start>/<regex('{DATE_PATTERN}'):end>/",
804
                endpoint="handler",
805
            )
806
        ],
807
        converters={"regex": RegexConverter},
808
    )
809
    a = map.bind("example.org", "/")
810
    route, kwargs = a.match("/2023-02-16T23:36:36.266Z/2023-02-16T23:46:36.266Z/")
811
    assert kwargs["start"] == "2023-02-16T23:36:36.266Z"
812
    assert kwargs["end"] == "2023-02-16T23:46:36.266Z"
813

814

815
def test_anyconverter():
816
    m = r.Map(
817
        [
818
            r.Rule("/<any(a1, a2):a>", endpoint="no_dot"),
819
            r.Rule("/<any(a.1, a.2):a>", endpoint="yes_dot"),
820
        ]
821
    )
822
    a = m.bind("example.org", "/")
823
    assert a.match("/a1") == ("no_dot", {"a": "a1"})
824
    assert a.match("/a2") == ("no_dot", {"a": "a2"})
825
    assert a.match("/a.1") == ("yes_dot", {"a": "a.1"})
826
    assert a.match("/a.2") == ("yes_dot", {"a": "a.2"})
827

828

829
def test_any_converter_build_validates_value() -> None:
830
    m = r.Map([r.Rule("/<any(patient, provider):value>", endpoint="actor")])
831
    a = m.bind("localhost")
832

833
    assert a.build("actor", {"value": "patient"}) == "/patient"
834
    assert a.build("actor", {"value": "provider"}) == "/provider"
835

836
    with pytest.raises(ValueError) as exc:
837
        a.build("actor", {"value": "invalid"})
838

839
    assert str(exc.value) == "'invalid' is not one of 'patient', 'provider'"
840

841

842
def test_part_isolating_default() -> None:
843
    class TwoConverter(r.BaseConverter):
844
        regex = r"\w+/\w+"
845

846
        def to_python(self, value: str) -> t.Any:
847
            return value.split("/")
848

849
    m = r.Map(
850
        [r.Rule("/<two:values>/", endpoint="two")], converters={"two": TwoConverter}
851
    )
852
    a = m.bind("localhost")
853
    assert a.match("/a/b/") == ("two", {"values": ["a", "b"]})
854

855

856
@pytest.mark.parametrize(
857
    ("endpoint", "value", "expect"),
858
    [
859
        ("int", 1, "/1"),
860
        ("int", None, r.BuildError),
861
        ("int", [1], TypeError),
862
        ("list", [1], "/1"),
863
        ("list", [1, None, 2], "/1.None.2"),
864
        ("list", 1, TypeError),
865
    ],
866
)
867
def test_build_values_dict(endpoint, value, expect):
868
    class ListConverter(r.BaseConverter):
869
        def to_url(self, value: t.Any) -> str:
870
            return super().to_url(".".join(map(str, value)))
871

872
    url_map = r.Map(
873
        [r.Rule("/<int:v>", endpoint="int"), r.Rule("/<list:v>", endpoint="list")],
874
        converters={"list": ListConverter},
875
    )
876
    adapter = url_map.bind("localhost")
877

878
    if isinstance(expect, str):
879
        assert adapter.build(endpoint, {"v": value}) == expect
880
    else:
881
        with pytest.raises(expect):
882
            adapter.build(endpoint, {"v": value})
883

884

885
@pytest.mark.parametrize(
886
    ("endpoint", "value", "expect"),
887
    [
888
        ("int", 1, "/1"),
889
        ("int", [1], "/1"),
890
        ("int", [], r.BuildError),
891
        ("int", None, TypeError),
892
        ("int", [None], TypeError),
893
        ("list", 1, TypeError),
894
        ("list", [1], TypeError),
895
        ("list", [[1]], "/1"),
896
        ("list", [1, None, 2], "/1.None.2"),
897
    ],
898
)
899
def test_build_values_multidict(endpoint, value, expect):
900
    class ListConverter(r.BaseConverter):
901
        def to_url(self, value: t.Any) -> str:
902
            return super().to_url(".".join(map(str, value)))
903

904
    url_map = r.Map(
905
        [r.Rule("/<int:v>", endpoint="int"), r.Rule("/<list:v>", endpoint="list")],
906
        converters={"list": ListConverter},
907
    )
908
    adapter = url_map.bind("localhost")
909

910
    if isinstance(expect, str):
911
        assert adapter.build(endpoint, MultiDict({"v": value})) == expect
912
    else:
913
        with pytest.raises(expect):
914
            adapter.build(endpoint, MultiDict({"v": value}))
915

916

917
@pytest.mark.parametrize(
918
    ("value", "expect"),
919
    [
920
        (None, ""),
921
        ([None], ""),
922
        ([None, None], ""),
923
        ("", "?v="),
924
        ([""], "?v="),
925
        (0, "?v=0"),
926
        (1.0, "?v=1.0"),
927
        ([1, 2], "?v=1&v=2"),
928
        ([1, None, 2], "?v=1&v=2"),
929
        ([1, "", 2], "?v=1&v=&v=2"),
930
        ("1+2", "?v=1%2B2"),
931
    ],
932
)
933
def test_build_append_unknown_dict(value, expect):
934
    map = r.Map([r.Rule("/", endpoint="a")])
935
    adapter = map.bind("localhost")
936
    assert adapter.build("a", {"v": value}) == f"/{expect}"
937
    assert adapter.build("a", {"v": value}, append_unknown=False) == "/"
938

939

940
@pytest.mark.parametrize(
941
    ("value", "expect"),
942
    [
943
        (None, ""),
944
        ([None], ""),
945
        ([None, None], ""),
946
        ("", "?v="),
947
        ([""], "?v="),
948
        (0, "?v=0"),
949
        (1.0, "?v=1.0"),
950
        ([1, 2], "?v=1&v=2"),
951
        ([1, None, 2], "?v=1&v=2"),
952
        ([1, "", 2], "?v=1&v=&v=2"),
953
    ],
954
)
955
def test_build_append_unknown_multidict(value, expect):
956
    map = r.Map([r.Rule("/", endpoint="a")])
957
    adapter = map.bind("localhost")
958
    assert adapter.build("a", MultiDict({"v": value})) == f"/{expect}"
959
    assert adapter.build("a", MultiDict({"v": value}), append_unknown=False) == "/"
960

961

962
def test_build_drop_none():
963
    map = r.Map([r.Rule("/flob/<flub>", endpoint="endp")])
964
    adapter = map.bind("", "/")
965
    params = {"flub": None, "flop": None}
966
    with pytest.raises(r.BuildError):
967
        adapter.build("endp", params)
968
    params = {"flub": "x", "flop": None}
969
    url = adapter.build("endp", params)
970
    assert "flop" not in url
971

972

973
def test_method_fallback():
974
    map = r.Map(
975
        [
976
            r.Rule("/", endpoint="index", methods=["GET"]),
977
            r.Rule("/<name>", endpoint="hello_name", methods=["GET"]),
978
            r.Rule("/select", endpoint="hello_select", methods=["POST"]),
979
            r.Rule("/search_get", endpoint="search", methods=["GET"]),
980
            r.Rule("/search_post", endpoint="search", methods=["POST"]),
981
        ]
982
    )
983
    adapter = map.bind("example.com")
984
    assert adapter.build("index") == "/"
985
    assert adapter.build("index", method="GET") == "/"
986
    assert adapter.build("hello_name", {"name": "foo"}) == "/foo"
987
    assert adapter.build("hello_select") == "/select"
988
    assert adapter.build("hello_select", method="POST") == "/select"
989
    assert adapter.build("search") == "/search_get"
990
    assert adapter.build("search", method="GET") == "/search_get"
991
    assert adapter.build("search", method="POST") == "/search_post"
992

993

994
def test_implicit_head():
995
    url_map = r.Map(
996
        [
997
            r.Rule("/get", methods=["GET"], endpoint="a"),
998
            r.Rule("/post", methods=["POST"], endpoint="b"),
999
        ]
1000
    )
1001
    adapter = url_map.bind("example.org")
1002
    assert adapter.match("/get", method="HEAD") == ("a", {})
1003
    pytest.raises(MethodNotAllowed, adapter.match, "/post", method="HEAD")
1004

1005

1006
def test_pass_str_as_router_methods():
1007
    with pytest.raises(TypeError):
1008
        r.Rule("/get", methods="GET")
1009

1010

1011
def test_protocol_joining_bug():
1012
    m = r.Map([r.Rule("/<foo>", endpoint="x")])
1013
    a = m.bind("example.org")
1014
    assert a.build("x", {"foo": "x:y"}) == "/x:y"
1015
    assert a.build("x", {"foo": "x:y"}, force_external=True) == "http://example.org/x:y"
1016

1017

1018
def test_allowed_methods_querying():
1019
    m = r.Map(
1020
        [r.Rule("/<foo>", methods=["GET", "HEAD"]), r.Rule("/foo", methods=["POST"])]
1021
    )
1022
    a = m.bind("example.org")
1023
    assert sorted(a.allowed_methods("/foo")) == ["GET", "HEAD", "POST"]
1024

1025

1026
def test_external_building_with_port():
1027
    map = r.Map([r.Rule("/", endpoint="index")])
1028
    adapter = map.bind("example.org:5000", "/")
1029
    built_url = adapter.build("index", {}, force_external=True)
1030
    assert built_url == "http://example.org:5000/", built_url
1031

1032

1033
def test_external_building_with_port_bind_to_environ():
1034
    map = r.Map([r.Rule("/", endpoint="index")])
1035
    adapter = map.bind_to_environ(
1036
        create_environ("/", "http://example.org:5000/"), server_name="example.org:5000"
1037
    )
1038
    built_url = adapter.build("index", {}, force_external=True)
1039
    assert built_url == "http://example.org:5000/", built_url
1040

1041

1042
def test_external_building_with_port_bind_to_environ_wrong_servername():
1043
    map = r.Map([r.Rule("/", endpoint="index")])
1044
    environ = create_environ("/", "http://example.org:5000/")
1045

1046
    with pytest.warns(UserWarning):
1047
        adapter = map.bind_to_environ(environ, server_name="example.org")
1048

1049
    assert adapter.subdomain == "<invalid>"
1050

1051

1052
def test_bind_long_idna_name_with_port():
1053
    map = r.Map([r.Rule("/", endpoint="index")])
1054
    adapter = map.bind("🐍" + "a" * 52 + ":8443")
1055
    name, _, port = adapter.server_name.partition(":")
1056
    assert len(name) == 63
1057
    assert port == "8443"
1058

1059

1060
def test_converter_parser():
1061
    args, kwargs = r.parse_converter_args("test, a=1, b=3.0")
1062

1063
    assert args == ("test",)
1064
    assert kwargs == {"a": 1, "b": 3.0}
1065

1066
    args, kwargs = r.parse_converter_args("")
1067
    assert not args and not kwargs
1068

1069
    args, kwargs = r.parse_converter_args("a, b, c,")
1070
    assert args == ("a", "b", "c")
1071
    assert not kwargs
1072

1073
    args, kwargs = r.parse_converter_args("True, False, None")
1074
    assert args == (True, False, None)
1075

1076
    args, kwargs = r.parse_converter_args('"foo", "bar"')
1077
    assert args == ("foo", "bar")
1078

1079
    with pytest.raises(ValueError):
1080
        r.parse_converter_args("min=0;max=500")
1081

1082

1083
def test_alias_redirects():
1084
    m = r.Map(
1085
        [
1086
            r.Rule("/", endpoint="index"),
1087
            r.Rule("/index.html", endpoint="index", alias=True),
1088
            r.Rule("/users/", defaults={"page": 1}, endpoint="users"),
1089
            r.Rule(
1090
                "/users/index.html", defaults={"page": 1}, alias=True, endpoint="users"
1091
            ),
1092
            r.Rule("/users/page/<int:page>", endpoint="users"),
1093
            r.Rule("/users/page-<int:page>.html", alias=True, endpoint="users"),
1094
        ]
1095
    )
1096
    a = m.bind("example.com")
1097

1098
    def ensure_redirect(path, new_url, args=None):
1099
        with pytest.raises(r.RequestRedirect) as excinfo:
1100
            a.match(path, query_args=args)
1101
        assert excinfo.value.new_url == f"http://example.com{new_url}"
1102

1103
    ensure_redirect("/index.html", "/")
1104
    ensure_redirect("/users/index.html", "/users/")
1105
    ensure_redirect("/users/page-2.html", "/users/page/2")
1106
    ensure_redirect("/users/page-1.html", "/users/")
1107
    ensure_redirect("/users/page-1.html", "/users/?foo=bar", {"foo": "bar"})
1108

1109
    assert a.build("index") == "/"
1110
    assert a.build("users", {"page": 1}) == "/users/"
1111
    assert a.build("users", {"page": 2}) == "/users/page/2"
1112

1113

1114
@pytest.mark.parametrize("prefix", ("", "/aaa"))
1115
def test_double_defaults(prefix):
1116
    m = r.Map(
1117
        [
1118
            r.Rule(f"{prefix}/", defaults={"foo": 1, "bar": False}, endpoint="x"),
1119
            r.Rule(f"{prefix}/<int:foo>", defaults={"bar": False}, endpoint="x"),
1120
            r.Rule(f"{prefix}/bar/", defaults={"foo": 1, "bar": True}, endpoint="x"),
1121
            r.Rule(f"{prefix}/bar/<int:foo>", defaults={"bar": True}, endpoint="x"),
1122
        ]
1123
    )
1124
    a = m.bind("example.com")
1125

1126
    assert a.match(f"{prefix}/") == ("x", {"foo": 1, "bar": False})
1127
    assert a.match(f"{prefix}/2") == ("x", {"foo": 2, "bar": False})
1128
    assert a.match(f"{prefix}/bar/") == ("x", {"foo": 1, "bar": True})
1129
    assert a.match(f"{prefix}/bar/2") == ("x", {"foo": 2, "bar": True})
1130

1131
    assert a.build("x", {"foo": 1, "bar": False}) == f"{prefix}/"
1132
    assert a.build("x", {"foo": 2, "bar": False}) == f"{prefix}/2"
1133
    assert a.build("x", {"bar": False}) == f"{prefix}/"
1134
    assert a.build("x", {"foo": 1, "bar": True}) == f"{prefix}/bar/"
1135
    assert a.build("x", {"foo": 2, "bar": True}) == f"{prefix}/bar/2"
1136
    assert a.build("x", {"bar": True}) == f"{prefix}/bar/"
1137

1138

1139
def test_host_matching():
1140
    m = r.Map(
1141
        [
1142
            r.Rule("/", endpoint="index", host="www.<domain>"),
1143
            r.Rule("/", endpoint="files", host="files.<domain>"),
1144
            r.Rule("/foo/", defaults={"page": 1}, host="www.<domain>", endpoint="x"),
1145
            r.Rule("/<int:page>", host="files.<domain>", endpoint="x"),
1146
        ],
1147
        host_matching=True,
1148
    )
1149

1150
    a = m.bind("www.example.com")
1151
    assert a.match("/") == ("index", {"domain": "example.com"})
1152
    assert a.match("/foo/") == ("x", {"domain": "example.com", "page": 1})
1153

1154
    with pytest.raises(r.RequestRedirect) as excinfo:
1155
        a.match("/foo")
1156
    assert excinfo.value.new_url == "http://www.example.com/foo/"
1157

1158
    a = m.bind("files.example.com")
1159
    assert a.match("/") == ("files", {"domain": "example.com"})
1160
    assert a.match("/2") == ("x", {"domain": "example.com", "page": 2})
1161

1162
    with pytest.raises(r.RequestRedirect) as excinfo:
1163
        a.match("/1")
1164
    assert excinfo.value.new_url == "http://www.example.com/foo/"
1165

1166

1167
def test_host_matching_building():
1168
    m = r.Map(
1169
        [
1170
            r.Rule("/", endpoint="index", host="www.domain.com"),
1171
            r.Rule("/", endpoint="foo", host="my.domain.com"),
1172
        ],
1173
        host_matching=True,
1174
    )
1175

1176
    www = m.bind("www.domain.com")
1177
    assert www.match("/") == ("index", {})
1178
    assert www.build("index") == "/"
1179
    assert www.build("foo") == "http://my.domain.com/"
1180

1181
    my = m.bind("my.domain.com")
1182
    assert my.match("/") == ("foo", {})
1183
    assert my.build("foo") == "/"
1184
    assert my.build("index") == "http://www.domain.com/"
1185

1186

1187
def test_server_name_casing():
1188
    m = r.Map([r.Rule("/", endpoint="index", subdomain="foo")])
1189

1190
    env = create_environ()
1191
    env["SERVER_NAME"] = env["HTTP_HOST"] = "FOO.EXAMPLE.COM"
1192
    a = m.bind_to_environ(env, server_name="example.com")
1193
    assert a.match("/") == ("index", {})
1194

1195
    env = create_environ()
1196
    env["SERVER_NAME"] = "127.0.0.1"
1197
    env["SERVER_PORT"] = "5000"
1198
    del env["HTTP_HOST"]
1199

1200
    with pytest.warns(UserWarning):
1201
        a = m.bind_to_environ(env, server_name="example.com")
1202

1203
    with pytest.raises(NotFound):
1204
        a.match()
1205

1206

1207
def test_redirect_request_exception_code():
1208
    exc = r.RequestRedirect("http://www.google.com/")
1209
    exc.code = 307
1210
    env = create_environ()
1211
    assert exc.get_response(env).status_code == exc.code
1212

1213

1214
def test_redirect_path_quoting():
1215
    url_map = r.Map(
1216
        [
1217
            r.Rule("/<category>", defaults={"page": 1}, endpoint="category"),
1218
            r.Rule("/<category>/page/<int:page>", endpoint="category"),
1219
        ]
1220
    )
1221
    adapter = url_map.bind("example.com")
1222

1223
    with pytest.raises(r.RequestRedirect) as excinfo:
1224
        adapter.match("/foo bar/page/1")
1225
    response = excinfo.value.get_response({})
1226
    assert response.headers["location"] == "http://example.com/foo%20bar"
1227

1228

1229
def test_unicode_rules():
1230
    m = r.Map(
1231
        [r.Rule("/войти/", endpoint="enter"), r.Rule("/foo+bar/", endpoint="foobar")]
1232
    )
1233
    a = m.bind("☃.example.com")
1234
    with pytest.raises(r.RequestRedirect) as excinfo:
1235
        a.match("/войти")
1236
    assert (
1237
        excinfo.value.new_url
1238
        == "http://xn--n3h.example.com/%D0%B2%D0%BE%D0%B9%D1%82%D0%B8/"
1239
    )
1240

1241
    endpoint, values = a.match("/войти/")
1242
    assert endpoint == "enter"
1243
    assert values == {}
1244

1245
    with pytest.raises(r.RequestRedirect) as excinfo:
1246
        a.match("/foo+bar")
1247
    assert excinfo.value.new_url == "http://xn--n3h.example.com/foo+bar/"
1248

1249
    endpoint, values = a.match("/foo+bar/")
1250
    assert endpoint == "foobar"
1251
    assert values == {}
1252

1253
    url = a.build("enter", {}, force_external=True)
1254
    assert url == "http://xn--n3h.example.com/%D0%B2%D0%BE%D0%B9%D1%82%D0%B8/"
1255

1256
    url = a.build("foobar", {}, force_external=True)
1257
    assert url == "http://xn--n3h.example.com/foo+bar/"
1258

1259

1260
def test_empty_path_info():
1261
    m = r.Map([r.Rule("/", endpoint="index")])
1262

1263
    b = m.bind("example.com", script_name="/approot")
1264
    with pytest.raises(r.RequestRedirect) as excinfo:
1265
        b.match("")
1266
    assert excinfo.value.new_url == "http://example.com/approot/"
1267

1268
    a = m.bind("example.com")
1269
    with pytest.raises(r.RequestRedirect) as excinfo:
1270
        a.match("")
1271
    assert excinfo.value.new_url == "http://example.com/"
1272

1273

1274
def test_both_bind_and_match_path_info_are_none():
1275
    m = r.Map([r.Rule("/", endpoint="index")])
1276
    ma = m.bind("example.org")
1277
    assert ma.match() == ("index", {})
1278

1279

1280
def test_map_repr():
1281
    m = r.Map([r.Rule("/wat", endpoint="enter"), r.Rule("/woop", endpoint="foobar")])
1282
    rv = repr(m)
1283
    assert rv == "Map([<Rule '/wat' -> enter>, <Rule '/woop' -> foobar>])"
1284

1285

1286
def test_empty_subclass_rules_with_custom_kwargs():
1287
    class CustomRule(r.Rule):
1288
        def __init__(self, string=None, custom=None, *args, **kwargs):
1289
            self.custom = custom
1290
            super().__init__(string, *args, **kwargs)
1291

1292
    rule1 = CustomRule("/foo", endpoint="bar")
1293
    try:
1294
        rule2 = rule1.empty()
1295
        assert rule1.rule == rule2.rule
1296
    except TypeError as e:  # raised without fix in PR #675
1297
        raise e
1298

1299

1300
def test_finding_closest_match_by_endpoint():
1301
    m = r.Map(
1302
        [
1303
            r.Rule("/foo/", endpoint="users.here"),
1304
            r.Rule("/wat/", endpoint="admin.users"),
1305
            r.Rule("/woop", endpoint="foo.users"),
1306
        ]
1307
    )
1308
    adapter = m.bind("example.com")
1309
    assert (
1310
        r.BuildError("admin.user", None, None, adapter).suggested.endpoint
1311
        == "admin.users"
1312
    )
1313

1314

1315
def test_finding_closest_match_by_values():
1316
    rule_id = r.Rule("/user/id/<id>/", endpoint="users")
1317
    rule_slug = r.Rule("/user/<slug>/", endpoint="users")
1318
    rule_random = r.Rule("/user/emails/<email>/", endpoint="users")
1319
    m = r.Map([rule_id, rule_slug, rule_random])
1320
    adapter = m.bind("example.com")
1321
    assert r.BuildError("x", {"slug": ""}, None, adapter).suggested == rule_slug
1322

1323

1324
def test_finding_closest_match_by_method():
1325
    post = r.Rule("/post/", endpoint="foobar", methods=["POST"])
1326
    get = r.Rule("/get/", endpoint="foobar", methods=["GET"])
1327
    put = r.Rule("/put/", endpoint="foobar", methods=["PUT"])
1328
    m = r.Map([post, get, put])
1329
    adapter = m.bind("example.com")
1330
    assert r.BuildError("invalid", {}, "POST", adapter).suggested == post
1331
    assert r.BuildError("invalid", {}, "GET", adapter).suggested == get
1332
    assert r.BuildError("invalid", {}, "PUT", adapter).suggested == put
1333

1334

1335
def test_finding_closest_match_when_none_exist():
1336
    m = r.Map([])
1337
    assert not r.BuildError("invalid", {}, None, m.bind("test.com")).suggested
1338

1339

1340
def test_error_message_without_suggested_rule():
1341
    m = r.Map([r.Rule("/foo/", endpoint="world", methods=["GET"])])
1342
    adapter = m.bind("example.com")
1343

1344
    with pytest.raises(r.BuildError) as excinfo:
1345
        adapter.build("urks")
1346
    assert str(excinfo.value).startswith("Could not build url for endpoint 'urks'.")
1347

1348
    with pytest.raises(r.BuildError) as excinfo:
1349
        adapter.build("world", method="POST")
1350
    assert str(excinfo.value).startswith(
1351
        "Could not build url for endpoint 'world' ('POST')."
1352
    )
1353

1354
    with pytest.raises(r.BuildError) as excinfo:
1355
        adapter.build("urks", values={"user_id": 5})
1356
    assert str(excinfo.value).startswith(
1357
        "Could not build url for endpoint 'urks' with values ['user_id']."
1358
    )
1359

1360

1361
def test_error_message_suggestion():
1362
    m = r.Map([r.Rule("/foo/<id>/", endpoint="world", methods=["GET"])])
1363
    adapter = m.bind("example.com")
1364

1365
    with pytest.raises(r.BuildError) as excinfo:
1366
        adapter.build("helloworld")
1367
    assert "Did you mean 'world' instead?" in str(excinfo.value)
1368

1369
    with pytest.raises(r.BuildError) as excinfo:
1370
        adapter.build("world")
1371
    assert "Did you forget to specify values ['id']?" in str(excinfo.value)
1372
    assert "Did you mean to use methods" not in str(excinfo.value)
1373

1374
    with pytest.raises(r.BuildError) as excinfo:
1375
        adapter.build("world", {"id": 2}, method="POST")
1376
    assert "Did you mean to use methods ['GET', 'HEAD']?" in str(excinfo.value)
1377

1378

1379
def test_no_memory_leak_from_Rule_builder():
1380
    """See #1520"""
1381

1382
    # generate a bunch of objects that *should* get collected
1383
    for _ in range(100):
1384
        r.Map([r.Rule("/a/<string:b>")])
1385

1386
    # ensure that the garbage collection has had a chance to collect cyclic
1387
    # objects
1388
    for _ in range(5):
1389
        gc.collect()
1390

1391
    # assert they got collected!
1392
    count = sum(1 for obj in gc.get_objects() if isinstance(obj, r.Rule))
1393
    assert count == 0
1394

1395

1396
def test_build_url_with_arg_self():
1397
    map = r.Map([r.Rule("/foo/<string:self>", endpoint="foo")])
1398
    adapter = map.bind("example.org", "/", subdomain="blah")
1399

1400
    ret = adapter.build("foo", {"self": "bar"})
1401
    assert ret == "http://example.org/foo/bar"
1402

1403

1404
def test_build_url_with_arg_keyword():
1405
    map = r.Map([r.Rule("/foo/<string:class>", endpoint="foo")])
1406
    adapter = map.bind("example.org", "/", subdomain="blah")
1407

1408
    ret = adapter.build("foo", {"class": "bar"})
1409
    assert ret == "http://example.org/foo/bar"
1410

1411

1412
def test_build_url_same_endpoint_multiple_hosts():
1413
    m = r.Map(
1414
        [
1415
            r.Rule("/", endpoint="index", host="alpha.example.com"),
1416
            r.Rule("/", endpoint="index", host="beta.example.com"),
1417
            r.Rule("/", endpoint="gamma", host="gamma.example.com"),
1418
        ],
1419
        host_matching=True,
1420
    )
1421

1422
    alpha = m.bind("alpha.example.com")
1423
    assert alpha.build("index") == "/"
1424
    assert alpha.build("gamma") == "http://gamma.example.com/"
1425

1426
    alpha_case = m.bind("AlPhA.ExAmPlE.CoM")
1427
    assert alpha_case.build("index") == "/"
1428
    assert alpha_case.build("gamma") == "http://gamma.example.com/"
1429

1430
    beta = m.bind("beta.example.com")
1431
    assert beta.build("index") == "/"
1432

1433
    beta_case = m.bind("BeTa.ExAmPlE.CoM")
1434
    assert beta_case.build("index") == "/"
1435

1436

1437
def test_rule_websocket_methods():
1438
    with pytest.raises(ValueError):
1439
        r.Rule("/ws", endpoint="ws", websocket=True, methods=["post"])
1440
    with pytest.raises(ValueError):
1441
        r.Rule(
1442
            "/ws",
1443
            endpoint="ws",
1444
            websocket=True,
1445
            methods=["get", "head", "options", "post"],
1446
        )
1447
    r.Rule("/ws", endpoint="ws", websocket=True, methods=["get", "head", "options"])
1448

1449

1450
def test_path_weighting():
1451
    m = r.Map(
1452
        [
1453
            r.Rule("/<path:path>/c", endpoint="simple"),
1454
            r.Rule("/<path:path>/<a>/<b>", endpoint="complex"),
1455
        ]
1456
    )
1457
    a = m.bind("localhost", path_info="/a/b/c")
1458

1459
    assert a.match() == ("simple", {"path": "a/b"})
1460

1461

1462
def test_newline_match():
1463
    m = r.Map([r.Rule("/hello", endpoint="hello")])
1464
    a = m.bind("localhost")
1465

1466
    with pytest.raises(NotFound):
1467
        a.match("/hello\n")
1468

1469

1470
def test_weighting():
1471
    m = r.Map(
1472
        [
1473
            r.Rule("/<int:value>", endpoint="int"),
1474
            r.Rule("/<uuid:value>", endpoint="uuid"),
1475
        ]
1476
    )
1477
    a = m.bind("localhost")
1478

1479
    assert a.match("/2b5b0911-fdcf-4dd2-921b-28ace88db8a0") == (
1480
        "uuid",
1481
        {"value": uuid.UUID("2b5b0911-fdcf-4dd2-921b-28ace88db8a0")},
1482
    )
1483

1484

1485
def test_strict_slashes_false():
1486
    map = r.Map(
1487
        [
1488
            r.Rule("/path1", endpoint="leaf_path", strict_slashes=False),
1489
            r.Rule("/path2/", endpoint="branch_path", strict_slashes=False),
1490
            r.Rule(
1491
                "/<path:path>", endpoint="leaf_path_converter", strict_slashes=False
1492
            ),
1493
        ],
1494
    )
1495

1496
    adapter = map.bind("example.org", "/")
1497

1498
    assert adapter.match("/path1", method="GET") == ("leaf_path", {})
1499
    assert adapter.match("/path1/", method="GET") == ("leaf_path", {})
1500
    assert adapter.match("/path2", method="GET") == ("branch_path", {})
1501
    assert adapter.match("/path2/", method="GET") == ("branch_path", {})
1502
    assert adapter.match("/any", method="GET") == (
1503
        "leaf_path_converter",
1504
        {"path": "any"},
1505
    )
1506
    assert adapter.match("/any/", method="GET") == (
1507
        "leaf_path_converter",
1508
        {"path": "any/"},
1509
    )
1510

1511

1512
def test_invalid_rule():
1513
    with pytest.raises(ValueError):
1514
        r.Map([r.Rule("/<int()>", endpoint="test")])
1515

1516

1517
def test_multiple_converters_per_part():
1518
    map_ = r.Map(
1519
        [
1520
            r.Rule("/v<int:major>.<int:minor>", endpoint="version"),
1521
        ],
1522
    )
1523
    adapter = map_.bind("localhost")
1524
    assert adapter.match("/v1.2") == ("version", {"major": 1, "minor": 2})
1525

1526

1527
def test_static_regex_escape():
1528
    map_ = r.Map(
1529
        [
1530
            r.Rule("/.<int:value>", endpoint="dotted"),
1531
        ],
1532
    )
1533
    adapter = map_.bind("localhost")
1534
    assert adapter.match("/.2") == ("dotted", {"value": 2})
1535
    with pytest.raises(NotFound):
1536
        adapter.match("/a2")
1537

1538

1539
class RegexConverter(r.BaseConverter):
1540
    def __init__(self, url_map, *items):
1541
        super().__init__(url_map)
1542
        self.regex = items[0]
1543

1544

1545
def test_regex():
1546
    map_ = r.Map(
1547
        [
1548
            r.Rule(r"/<regex('[^/:]+\.[^/:]+'):value>", endpoint="regex"),
1549
        ],
1550
        converters={"regex": RegexConverter},
1551
    )
1552
    adapter = map_.bind("localhost")
1553
    assert adapter.match("/asdfsa.asdfs") == ("regex", {"value": "asdfsa.asdfs"})
1554

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.