xref: /unit/test/test_routing.py (revision 2616:ab2896c980ab)
1# -*- coding: utf-8 -*-
2import pytest
3
4from unit.applications.lang.python import ApplicationPython
5from unit.option import option
6
7prerequisites = {'modules': {'python': 'any'}}
8
9client = ApplicationPython()
10
11
12@pytest.fixture(autouse=True)
13def setup_method_fixture():
14    assert 'success' in client.conf(
15        {
16            "listeners": {"*:8080": {"pass": "routes"}},
17            "routes": [
18                {
19                    "match": {"method": "GET"},
20                    "action": {"return": 200},
21                }
22            ],
23            "applications": {},
24        }
25    ), 'routing configure'
26
27
28def route(conf_route):
29    return client.conf([conf_route], 'routes')
30
31
32def route_match(match):
33    assert 'success' in route(
34        {"match": match, "action": {"return": 200}}
35    ), 'route match configure'
36
37
38def route_match_invalid(match):
39    assert 'error' in route(
40        {"match": match, "action": {"return": 200}}
41    ), 'route match configure invalid'
42
43
44def host(host_header, status):
45    assert (
46        client.get(headers={'Host': host_header, 'Connection': 'close'})[
47            'status'
48        ]
49        == status
50    ), 'match host'
51
52
53def cookie(cookie_header, status):
54    assert (
55        client.get(
56            headers={
57                'Host': 'localhost',
58                'Cookie': cookie_header,
59                'Connection': 'close',
60            },
61        )['status']
62        == status
63    ), 'match cookie'
64
65
66def test_routes_match_method_positive():
67    assert client.get()['status'] == 200, 'GET'
68    assert client.post()['status'] == 404, 'POST'
69
70
71def test_routes_match_method_positive_many():
72    route_match({"method": ["GET", "POST"]})
73
74    assert client.get()['status'] == 200, 'GET'
75    assert client.post()['status'] == 200, 'POST'
76    assert client.delete()['status'] == 404, 'DELETE'
77
78
79def test_routes_match_method_negative():
80    route_match({"method": "!GET"})
81
82    assert client.get()['status'] == 404, 'GET'
83    assert client.post()['status'] == 200, 'POST'
84
85
86def test_routes_match_method_negative_many():
87    route_match({"method": ["!GET", "!POST"]})
88
89    assert client.get()['status'] == 404, 'GET'
90    assert client.post()['status'] == 404, 'POST'
91    assert client.delete()['status'] == 200, 'DELETE'
92
93
94def test_routes_match_method_wildcard_left():
95    route_match({"method": "*ET"})
96
97    assert client.get()['status'] == 200, 'GET'
98    assert client.post()['status'] == 404, 'POST'
99
100
101def test_routes_match_method_wildcard_right():
102    route_match({"method": "GE*"})
103
104    assert client.get()['status'] == 200, 'GET'
105    assert client.post()['status'] == 404, 'POST'
106
107
108def test_routes_match_method_wildcard_left_right():
109    route_match({"method": "*GET*"})
110
111    assert client.get()['status'] == 200, 'GET'
112    assert client.post()['status'] == 404, 'POST'
113
114
115def test_routes_match_method_wildcard():
116    route_match({"method": "*"})
117
118    assert client.get()['status'] == 200, 'GET'
119
120
121def test_routes_match_invalid():
122    route_match_invalid({"method": "**"})
123
124
125def test_routes_match_valid():
126    route_match({"method": "blah*"})
127    route_match({"host": "*blah*blah"})
128    route_match({"host": "blah*blah*blah"})
129    route_match({"host": "blah*blah*"})
130
131
132def test_routes_match_empty_exact():
133    route_match({"uri": ""})
134    assert client.get()['status'] == 404
135
136    route_match({"uri": "/"})
137    assert client.get()['status'] == 200
138    assert client.get(url='/blah')['status'] == 404
139
140
141def test_routes_match_negative():
142    route_match({"uri": "!"})
143    assert client.get()['status'] == 200
144
145    route_match({"uri": "!*"})
146    assert client.get()['status'] == 404
147
148    route_match({"uri": "!/"})
149    assert client.get()['status'] == 404
150    assert client.get(url='/blah')['status'] == 200
151
152    route_match({"uri": "!*blah"})
153    assert client.get()['status'] == 200
154    assert client.get(url='/bla')['status'] == 200
155    assert client.get(url='/blah')['status'] == 404
156    assert client.get(url='/blah1')['status'] == 200
157
158    route_match({"uri": "!/blah*1*"})
159    assert client.get()['status'] == 200
160    assert client.get(url='/blah')['status'] == 200
161    assert client.get(url='/blah1')['status'] == 404
162    assert client.get(url='/blah12')['status'] == 404
163    assert client.get(url='/blah2')['status'] == 200
164
165
166def test_routes_match_wildcard_middle():
167    route_match({"host": "ex*le"})
168
169    host('example', 200)
170    host('www.example', 404)
171    host('example.com', 404)
172    host('exampl', 404)
173
174
175def test_routes_match_method_case_insensitive():
176    route_match({"method": "get"})
177
178    assert client.get()['status'] == 200, 'GET'
179
180
181def test_routes_match_wildcard_left_case_insensitive():
182    route_match({"method": "*get"})
183    assert client.get()['status'] == 200, 'GET'
184
185    route_match({"method": "*et"})
186    assert client.get()['status'] == 200, 'GET'
187
188
189def test_routes_match_wildcard_middle_case_insensitive():
190    route_match({"method": "g*t"})
191
192    assert client.get()['status'] == 200, 'GET'
193
194
195def test_routes_match_wildcard_right_case_insensitive():
196    route_match({"method": "get*"})
197    assert client.get()['status'] == 200, 'GET'
198
199    route_match({"method": "ge*"})
200    assert client.get()['status'] == 200, 'GET'
201
202
203def test_routes_match_wildcard_substring_case_insensitive():
204    route_match({"method": "*et*"})
205
206    assert client.get()['status'] == 200, 'GET'
207
208
209def test_routes_match_wildcard_left_case_sensitive():
210    route_match({"uri": "*blah"})
211
212    assert client.get(url='/blah')['status'] == 200, '/blah'
213    assert client.get(url='/BLAH')['status'] == 404, '/BLAH'
214
215
216def test_routes_match_wildcard_middle_case_sensitive():
217    route_match({"uri": "/b*h"})
218
219    assert client.get(url='/blah')['status'] == 200, '/blah'
220    assert client.get(url='/BLAH')['status'] == 404, '/BLAH'
221
222
223def test_route_match_wildcards_ordered():
224    route_match({"uri": "/a*x*y*"})
225
226    assert client.get(url='/axy')['status'] == 200, '/axy'
227    assert client.get(url='/ayx')['status'] == 404, '/ayx'
228
229
230def test_route_match_wildcards_adjust_start():
231    route_match({"uri": "/bla*bla*"})
232
233    assert client.get(url='/bla_foo')['status'] == 404, '/bla_foo'
234
235
236def test_route_match_wildcards_adjust_start_substr():
237    route_match({"uri": "*bla*bla*"})
238
239    assert client.get(url='/bla_foo')['status'] == 404, '/bla_foo'
240
241
242def test_route_match_wildcards_adjust_end():
243    route_match({"uri": "/bla*bla"})
244
245    assert client.get(url='/foo_bla')['status'] == 404, '/foo_bla'
246
247
248def test_routes_match_wildcard_right_case_sensitive():
249    route_match({"uri": "/bla*"})
250
251    assert client.get(url='/blah')['status'] == 200, '/blah'
252    assert client.get(url='/BLAH')['status'] == 404, '/BLAH'
253
254
255def test_routes_match_wildcard_substring_case_sensitive():
256    route_match({"uri": "*bla*"})
257
258    assert client.get(url='/blah')['status'] == 200, '/blah'
259    assert client.get(url='/BLAH')['status'] == 404, '/BLAH'
260
261
262def test_routes_match_many_wildcard_substrings_case_sensitive():
263    route_match({"uri": "*a*B*c*"})
264
265    assert client.get(url='/blah-a-B-c-blah')['status'] == 200
266    assert client.get(url='/a-B-c')['status'] == 200
267    assert client.get(url='/aBc')['status'] == 200
268    assert client.get(url='/aBCaBbc')['status'] == 200
269    assert client.get(url='/ABc')['status'] == 404
270
271
272def test_routes_empty_regex(require):
273    require({'modules': {'regex': True}})
274
275    route_match({"uri": "~"})
276    assert client.get(url='/')['status'] == 200, 'empty regexp'
277    assert client.get(url='/anything')['status'] == 200, '/anything'
278
279    route_match({"uri": "!~"})
280    assert client.get(url='/')['status'] == 404, 'empty regexp 2'
281    assert client.get(url='/nothing')['status'] == 404, '/nothing'
282
283
284def test_routes_bad_regex(require):
285    require({'modules': {'regex': True}})
286
287    assert 'error' in route(
288        {"match": {"uri": "~/bl[ah"}, "action": {"return": 200}}
289    ), 'bad regex'
290
291    status = route({"match": {"uri": "~(?R)?z"}, "action": {"return": 200}})
292    if 'error' not in status:
293        assert client.get(url='/nothing_z')['status'] == 500, '/nothing_z'
294
295    status = route({"match": {"uri": "~((?1)?z)"}, "action": {"return": 200}})
296    if 'error' not in status:
297        assert client.get(url='/nothing_z')['status'] == 500, '/nothing_z'
298
299
300def test_routes_match_regex_case_sensitive(require):
301    require({'modules': {'regex': True}})
302
303    route_match({"uri": "~/bl[ah]"})
304
305    assert client.get(url='/rlah')['status'] == 404, '/rlah'
306    assert client.get(url='/blah')['status'] == 200, '/blah'
307    assert client.get(url='/blh')['status'] == 200, '/blh'
308    assert client.get(url='/BLAH')['status'] == 404, '/BLAH'
309
310
311def test_routes_match_regex_negative_case_sensitive(require):
312    require({'modules': {'regex': True}})
313
314    route_match({"uri": "!~/bl[ah]"})
315
316    assert client.get(url='/rlah')['status'] == 200, '/rlah'
317    assert client.get(url='/blah')['status'] == 404, '/blah'
318    assert client.get(url='/blh')['status'] == 404, '/blh'
319    assert client.get(url='/BLAH')['status'] == 200, '/BLAH'
320
321
322def test_routes_pass_encode():
323    python_dir = f'{option.test_dir}/python'
324
325    def check_pass(path, name):
326        assert 'success' in client.conf(
327            {
328                "listeners": {"*:8080": {"pass": f'applications/{path}'}},
329                "applications": {
330                    name: {
331                        "type": client.get_application_type(),
332                        "processes": {"spare": 0},
333                        "path": f'{python_dir}/empty',
334                        "working_directory": f'{python_dir}/empty',
335                        "module": "wsgi",
336                    }
337                },
338            }
339        )
340
341        assert client.get()['status'] == 200
342
343    check_pass("%25", "%")
344    check_pass("blah%2Fblah", "blah/blah")
345    check_pass("%2Fblah%2F%2Fblah%2F", "/blah//blah/")
346    check_pass("%20blah%252Fblah%7E", " blah%2Fblah~")
347
348    def check_pass_error(path, name):
349        assert 'error' in client.conf(
350            {
351                "listeners": {"*:8080": {"pass": f'applications/{path}'}},
352                "applications": {
353                    name: {
354                        "type": client.get_application_type(),
355                        "processes": {"spare": 0},
356                        "path": f'{python_dir}/empty',
357                        "working_directory": f'{python_dir}/empty',
358                        "module": "wsgi",
359                    }
360                },
361            }
362        )
363
364    check_pass_error("%", "%")
365    check_pass_error("%1", "%1")
366
367
368def test_routes_absent():
369    assert 'success' in client.conf(
370        {
371            "listeners": {"*:8081": {"pass": "applications/empty"}},
372            "applications": {
373                "empty": {
374                    "type": client.get_application_type(),
375                    "processes": {"spare": 0},
376                    "path": f'{option.test_dir}/python/empty',
377                    "working_directory": f'{option.test_dir}/python/empty',
378                    "module": "wsgi",
379                }
380            },
381        }
382    )
383
384    assert client.get(port=8081)['status'] == 200, 'routes absent'
385
386
387def test_routes_pass_invalid():
388    assert 'error' in client.conf(
389        {"pass": "routes/blah"}, 'listeners/*:8080'
390    ), 'routes invalid'
391
392
393def test_route_empty():
394    assert 'success' in client.conf(
395        {
396            "listeners": {"*:8080": {"pass": "routes/main"}},
397            "routes": {"main": []},
398            "applications": {},
399        }
400    ), 'route empty configure'
401
402    assert client.get()['status'] == 404, 'route empty'
403
404
405def test_routes_route_empty():
406    assert 'success' in client.conf(
407        {}, 'listeners'
408    ), 'routes empty listeners configure'
409
410    assert 'success' in client.conf({}, 'routes'), 'routes empty configure'
411
412
413def test_routes_route_match_absent():
414    assert 'success' in client.conf(
415        [{"action": {"return": 200}}], 'routes'
416    ), 'route match absent configure'
417
418    assert client.get()['status'] == 200, 'route match absent'
419
420
421def test_routes_route_action_absent(skip_alert):
422    skip_alert(r'failed to apply new conf')
423
424    assert 'error' in client.conf(
425        [{"match": {"method": "GET"}}], 'routes'
426    ), 'route pass absent configure'
427
428
429def test_routes_route_pass():
430    assert 'success' in client.conf(
431        {
432            "applications": {
433                "app": {
434                    "type": client.get_application_type(),
435                    "processes": {"spare": 0},
436                    "path": "/app",
437                    "module": "wsgi",
438                }
439            },
440            "upstreams": {
441                "one": {
442                    "servers": {
443                        "127.0.0.1:8081": {},
444                        "127.0.0.1:8082": {},
445                    },
446                },
447                "two": {
448                    "servers": {
449                        "127.0.0.1:8081": {},
450                        "127.0.0.1:8082": {},
451                    },
452                },
453            },
454        }
455    )
456
457    assert 'success' in client.conf([{"action": {"pass": "routes"}}], 'routes')
458    assert 'success' in client.conf(
459        [{"action": {"pass": "applications/app"}}], 'routes'
460    )
461    assert 'success' in client.conf(
462        [{"action": {"pass": "upstreams/one"}}], 'routes'
463    )
464
465
466def test_routes_route_pass_absent():
467    assert 'error' in client.conf(
468        [{"match": {"method": "GET"}, "action": {}}], 'routes'
469    ), 'route pass absent configure'
470
471
472def test_routes_route_pass_invalid():
473    assert 'success' in client.conf(
474        {
475            "applications": {
476                "app": {
477                    "type": client.get_application_type(),
478                    "processes": {"spare": 0},
479                    "path": "/app",
480                    "module": "wsgi",
481                }
482            },
483            "upstreams": {
484                "one": {
485                    "servers": {
486                        "127.0.0.1:8081": {},
487                        "127.0.0.1:8082": {},
488                    },
489                },
490                "two": {
491                    "servers": {
492                        "127.0.0.1:8081": {},
493                        "127.0.0.1:8082": {},
494                    },
495                },
496            },
497        }
498    )
499
500    assert 'error' in client.conf(
501        [{"action": {"pass": "blah"}}], 'routes'
502    ), 'route pass invalid'
503    assert 'error' in client.conf(
504        [{"action": {"pass": "routes/blah"}}], 'routes'
505    ), 'route pass routes invalid'
506    assert 'error' in client.conf(
507        [{"action": {"pass": "applications/blah"}}], 'routes'
508    ), 'route pass applications invalid'
509    assert 'error' in client.conf(
510        [{"action": {"pass": "upstreams/blah"}}], 'routes'
511    ), 'route pass upstreams invalid'
512
513
514def test_routes_action_unique(temp_dir):
515    assert 'success' in client.conf(
516        {
517            "listeners": {
518                "*:8080": {"pass": "routes"},
519                "*:8081": {"pass": "applications/app"},
520            },
521            "routes": [{"action": {"proxy": "http://127.0.0.1:8081"}}],
522            "applications": {
523                "app": {
524                    "type": client.get_application_type(),
525                    "processes": {"spare": 0},
526                    "path": "/app",
527                    "module": "wsgi",
528                }
529            },
530        }
531    )
532
533    assert 'error' in client.conf(
534        {"proxy": "http://127.0.0.1:8081", "share": temp_dir},
535        'routes/0/action',
536    ), 'proxy share'
537    assert 'error' in client.conf(
538        {
539            "proxy": "http://127.0.0.1:8081",
540            "pass": "applications/app",
541        },
542        'routes/0/action',
543    ), 'proxy pass'
544    assert 'error' in client.conf(
545        {"share": temp_dir, "pass": "applications/app"},
546        'routes/0/action',
547    ), 'share pass'
548
549
550def test_routes_rules_two():
551    assert 'success' in client.conf(
552        [
553            {"match": {"method": "GET"}, "action": {"return": 200}},
554            {"match": {"method": "POST"}, "action": {"return": 201}},
555        ],
556        'routes',
557    ), 'rules two configure'
558
559    assert client.get()['status'] == 200, 'rules two match first'
560    assert client.post()['status'] == 201, 'rules two match second'
561
562
563def test_routes_two():
564    assert 'success' in client.conf(
565        {
566            "listeners": {"*:8080": {"pass": "routes/first"}},
567            "routes": {
568                "first": [
569                    {
570                        "match": {"method": "GET"},
571                        "action": {"pass": "routes/second"},
572                    }
573                ],
574                "second": [
575                    {
576                        "match": {"host": "localhost"},
577                        "action": {"return": 200},
578                    }
579                ],
580            },
581            "applications": {},
582        }
583    ), 'routes two configure'
584
585    assert client.get()['status'] == 200, 'routes two'
586
587
588def test_routes_match_host_positive():
589    route_match({"host": "localhost"})
590
591    assert client.get()['status'] == 200, 'localhost'
592    host('localhost.', 200)
593    host('localhost.', 200)
594    host('.localhost', 404)
595    host('www.localhost', 404)
596    host('localhost1', 404)
597
598
599@pytest.mark.skip('not yet')
600def test_routes_match_host_absent():
601    route_match({"host": "localhost"})
602
603    assert (
604        client.get(headers={'Connection': 'close'})['status'] == 400
605    ), 'match host absent'
606
607
608def test_routes_match_host_ipv4():
609    route_match({"host": "127.0.0.1"})
610
611    host('127.0.0.1', 200)
612    host('127.0.0.1:8080', 200)
613
614
615def test_routes_match_host_ipv6():
616    route_match({"host": "[::1]"})
617
618    host('[::1]', 200)
619    host('[::1]:8080', 200)
620
621
622def test_routes_match_host_positive_many():
623    route_match({"host": ["localhost", "example.com"]})
624
625    assert client.get()['status'] == 200, 'localhost'
626    host('example.com', 200)
627
628
629def test_routes_match_host_positive_and_negative():
630    route_match({"host": ["*example.com", "!www.example.com"]})
631
632    assert client.get()['status'] == 404, 'localhost'
633    host('example.com', 200)
634    host('www.example.com', 404)
635    host('!www.example.com', 200)
636
637
638def test_routes_match_host_positive_and_negative_wildcard():
639    route_match({"host": ["*example*", "!www.example*"]})
640
641    host('example.com', 200)
642    host('www.example.com', 404)
643
644
645def test_routes_match_host_case_insensitive():
646    route_match({"host": "Example.com"})
647
648    host('example.com', 200)
649    host('EXAMPLE.COM', 200)
650
651
652def test_routes_match_host_port():
653    route_match({"host": "example.com"})
654
655    host('example.com:8080', 200)
656
657
658def test_routes_match_host_empty():
659    route_match({"host": ""})
660
661    host('', 200)
662    assert (
663        client.get(http_10=True, headers={})['status'] == 200
664    ), 'match host empty 2'
665    assert client.get()['status'] == 404, 'match host empty 3'
666
667
668def test_routes_match_uri_positive():
669    route_match({"uri": ["/blah", "/slash/"]})
670
671    assert client.get()['status'] == 404, '/'
672    assert client.get(url='/blah')['status'] == 200, '/blah'
673    assert client.get(url='/blah#foo')['status'] == 200, '/blah#foo'
674    assert client.get(url='/blah?var')['status'] == 200, '/blah?var'
675    assert client.get(url='//blah')['status'] == 200, '//blah'
676    assert client.get(url='/slash/foo/../')['status'] == 200, 'relative'
677    assert client.get(url='/slash/./')['status'] == 200, '/slash/./'
678    assert client.get(url='/slash//.//')['status'] == 200, 'adjacent slashes'
679    assert client.get(url='/%')['status'] == 400, 'percent'
680    assert client.get(url='/%1')['status'] == 400, 'percent digit'
681    assert client.get(url='/%A')['status'] == 400, 'percent letter'
682    assert client.get(url='/slash/.?args')['status'] == 200, 'dot args'
683    assert client.get(url='/slash/.#frag')['status'] == 200, 'dot frag'
684    assert client.get(url='/slash/foo/..?args')['status'] == 200, 'dot dot args'
685    assert client.get(url='/slash/foo/..#frag')['status'] == 200, 'dot dot frag'
686    assert client.get(url='/slash/.')['status'] == 200, 'trailing dot'
687    assert client.get(url='/slash/foo/..')['status'] == 200, 'trailing dot dot'
688
689
690def test_routes_match_uri_case_sensitive():
691    route_match({"uri": "/BLAH"})
692
693    assert client.get(url='/blah')['status'] == 404, '/blah'
694    assert client.get(url='/BlaH')['status'] == 404, '/BlaH'
695    assert client.get(url='/BLAH')['status'] == 200, '/BLAH'
696
697
698def test_routes_match_uri_normalize():
699    route_match({"uri": "/blah"})
700
701    assert client.get(url='/%62%6c%61%68')['status'] == 200, 'normalize'
702
703
704def test_routes_match_empty_array():
705    route_match({"uri": []})
706
707    assert client.get(url='/blah')['status'] == 200, 'empty array'
708
709
710def test_routes_reconfigure():
711    assert 'success' in client.conf([], 'routes'), 'redefine'
712    assert client.get()['status'] == 404, 'redefine request'
713
714    assert 'success' in client.conf(
715        [{"action": {"return": 200}}], 'routes'
716    ), 'redefine 2'
717    assert client.get()['status'] == 200, 'redefine request 2'
718
719    assert 'success' in client.conf([], 'routes'), 'redefine 3'
720    assert client.get()['status'] == 404, 'redefine request 3'
721
722    assert 'success' in client.conf(
723        {
724            "listeners": {"*:8080": {"pass": "routes/main"}},
725            "routes": {"main": [{"action": {"return": 200}}]},
726            "applications": {},
727        }
728    ), 'redefine 4'
729    assert client.get()['status'] == 200, 'redefine request 4'
730
731    assert 'success' in client.conf_delete('routes/main/0'), 'redefine 5'
732    assert client.get()['status'] == 404, 'redefine request 5'
733
734    assert 'success' in client.conf_post(
735        {"action": {"return": 200}}, 'routes/main'
736    ), 'redefine 6'
737    assert client.get()['status'] == 200, 'redefine request 6'
738
739    assert 'error' in client.conf(
740        {"action": {"return": 200}}, 'routes/main/2'
741    ), 'redefine 7'
742    assert 'success' in client.conf(
743        {"action": {"return": 201}}, 'routes/main/1'
744    ), 'redefine 8'
745
746    assert len(client.conf_get('routes/main')) == 2, 'redefine conf 8'
747    assert client.get()['status'] == 200, 'redefine request 8'
748
749
750def test_routes_edit():
751    route_match({"method": "GET"})
752
753    assert client.get()['status'] == 200, 'routes edit GET'
754    assert client.post()['status'] == 404, 'routes edit POST'
755
756    assert 'success' in client.conf_post(
757        {"match": {"method": "POST"}, "action": {"return": 200}},
758        'routes',
759    ), 'routes edit configure 2'
760    assert 'GET' == client.conf_get(
761        'routes/0/match/method'
762    ), 'routes edit configure 2 check'
763    assert 'POST' == client.conf_get(
764        'routes/1/match/method'
765    ), 'routes edit configure 2 check 2'
766
767    assert client.get()['status'] == 200, 'routes edit GET 2'
768    assert client.post()['status'] == 200, 'routes edit POST 2'
769
770    assert 'success' in client.conf_delete(
771        'routes/0'
772    ), 'routes edit configure 3'
773
774    assert client.get()['status'] == 404, 'routes edit GET 3'
775    assert client.post()['status'] == 200, 'routes edit POST 3'
776
777    assert 'error' in client.conf_delete(
778        'routes/1'
779    ), 'routes edit configure invalid'
780    assert 'error' in client.conf_delete(
781        'routes/-1'
782    ), 'routes edit configure invalid 2'
783    assert 'error' in client.conf_delete(
784        'routes/blah'
785    ), 'routes edit configure invalid 3'
786
787    assert client.get()['status'] == 404, 'routes edit GET 4'
788    assert client.post()['status'] == 200, 'routes edit POST 4'
789
790    assert 'success' in client.conf_delete(
791        'routes/0'
792    ), 'routes edit configure 5'
793
794    assert client.get()['status'] == 404, 'routes edit GET 5'
795    assert client.post()['status'] == 404, 'routes edit POST 5'
796
797    assert 'success' in client.conf_post(
798        {"match": {"method": "POST"}, "action": {"return": 200}},
799        'routes',
800    ), 'routes edit configure 6'
801
802    assert client.get()['status'] == 404, 'routes edit GET 6'
803    assert client.post()['status'] == 200, 'routes edit POST 6'
804
805    assert 'success' in client.conf(
806        {
807            "listeners": {"*:8080": {"pass": "routes/main"}},
808            "routes": {"main": [{"action": {"return": 200}}]},
809            "applications": {},
810        }
811    ), 'route edit configure 7'
812
813    assert 'error' in client.conf_delete(
814        'routes/0'
815    ), 'routes edit configure invalid 4'
816    assert 'error' in client.conf_delete(
817        'routes/main'
818    ), 'routes edit configure invalid 5'
819
820    assert client.get()['status'] == 200, 'routes edit GET 7'
821
822    assert 'success' in client.conf_delete(
823        'listeners/*:8080'
824    ), 'route edit configure 8'
825    assert 'success' in client.conf_delete(
826        'routes/main'
827    ), 'route edit configure 9'
828
829
830def test_match_edit(skip_alert):
831    skip_alert(r'failed to apply new conf')
832
833    route_match({"method": ["GET", "POST"]})
834
835    assert client.get()['status'] == 200, 'match edit GET'
836    assert client.post()['status'] == 200, 'match edit POST'
837    assert client.put()['status'] == 404, 'match edit PUT'
838
839    assert 'success' in client.conf_post(
840        '\"PUT\"', 'routes/0/match/method'
841    ), 'match edit configure 2'
842    assert ['GET', 'POST', 'PUT'] == client.conf_get(
843        'routes/0/match/method'
844    ), 'match edit configure 2 check'
845
846    assert client.get()['status'] == 200, 'match edit GET 2'
847    assert client.post()['status'] == 200, 'match edit POST 2'
848    assert client.put()['status'] == 200, 'match edit PUT 2'
849
850    assert 'success' in client.conf_delete(
851        'routes/0/match/method/1'
852    ), 'match edit configure 3'
853    assert ['GET', 'PUT'] == client.conf_get(
854        'routes/0/match/method'
855    ), 'match edit configure 3 check'
856
857    assert client.get()['status'] == 200, 'match edit GET 3'
858    assert client.post()['status'] == 404, 'match edit POST 3'
859    assert client.put()['status'] == 200, 'match edit PUT 3'
860
861    assert 'success' in client.conf_delete(
862        'routes/0/match/method/1'
863    ), 'match edit configure 4'
864    assert ['GET'] == client.conf_get(
865        'routes/0/match/method'
866    ), 'match edit configure 4 check'
867
868    assert client.get()['status'] == 200, 'match edit GET 4'
869    assert client.post()['status'] == 404, 'match edit POST 4'
870    assert client.put()['status'] == 404, 'match edit PUT 4'
871
872    assert 'error' in client.conf_delete(
873        'routes/0/match/method/1'
874    ), 'match edit configure invalid'
875    assert 'error' in client.conf_delete(
876        'routes/0/match/method/-1'
877    ), 'match edit configure invalid 2'
878    assert 'error' in client.conf_delete(
879        'routes/0/match/method/blah'
880    ), 'match edit configure invalid 3'
881    assert ['GET'] == client.conf_get(
882        'routes/0/match/method'
883    ), 'match edit configure 5 check'
884
885    assert client.get()['status'] == 200, 'match edit GET 5'
886    assert client.post()['status'] == 404, 'match edit POST 5'
887    assert client.put()['status'] == 404, 'match edit PUT 5'
888
889    assert 'success' in client.conf_delete(
890        'routes/0/match/method/0'
891    ), 'match edit configure 6'
892    assert [] == client.conf_get(
893        'routes/0/match/method'
894    ), 'match edit configure 6 check'
895
896    assert client.get()['status'] == 200, 'match edit GET 6'
897    assert client.post()['status'] == 200, 'match edit POST 6'
898    assert client.put()['status'] == 200, 'match edit PUT 6'
899
900    assert 'success' in client.conf(
901        '"GET"', 'routes/0/match/method'
902    ), 'match edit configure 7'
903
904    assert client.get()['status'] == 200, 'match edit GET 7'
905    assert client.post()['status'] == 404, 'match edit POST 7'
906    assert client.put()['status'] == 404, 'match edit PUT 7'
907
908    assert 'error' in client.conf_delete(
909        'routes/0/match/method/0'
910    ), 'match edit configure invalid 5'
911    assert 'error' in client.conf(
912        {}, 'routes/0/action'
913    ), 'match edit configure invalid 6'
914
915    assert 'success' in client.conf(
916        {}, 'routes/0/match'
917    ), 'match edit configure 8'
918
919    assert client.get()['status'] == 200, 'match edit GET 8'
920
921
922def test_routes_match_rules():
923    route_match({"method": "GET", "host": "localhost", "uri": "/"})
924
925    assert client.get()['status'] == 200, 'routes match rules'
926
927
928def test_routes_loop():
929    assert 'success' in route(
930        {"match": {"uri": "/"}, "action": {"pass": "routes"}}
931    ), 'routes loop configure'
932
933    assert client.get()['status'] == 500, 'routes loop'
934
935
936def test_routes_match_headers():
937    route_match({"headers": {"host": "localhost"}})
938
939    assert client.get()['status'] == 200, 'match headers'
940    host('Localhost', 200)
941    host('localhost.com', 404)
942    host('llocalhost', 404)
943    host('host', 404)
944
945
946def test_routes_match_headers_multiple():
947    route_match({"headers": {"host": "localhost", "x-blah": "test"}})
948
949    assert client.get()['status'] == 404, 'match headers multiple'
950    assert (
951        client.get(
952            headers={
953                "Host": "localhost",
954                "X-blah": "test",
955                "Connection": "close",
956            }
957        )['status']
958        == 200
959    ), 'match headers multiple 2'
960
961    assert (
962        client.get(
963            headers={
964                "Host": "localhost",
965                "X-blah": "",
966                "Connection": "close",
967            }
968        )['status']
969        == 404
970    ), 'match headers multiple 3'
971
972
973def test_routes_match_headers_multiple_values():
974    route_match({"headers": {"x-blah": "test"}})
975
976    assert (
977        client.get(
978            headers={
979                "Host": "localhost",
980                "X-blah": ["test", "test", "test"],
981                "Connection": "close",
982            }
983        )['status']
984        == 200
985    ), 'match headers multiple values'
986    assert (
987        client.get(
988            headers={
989                "Host": "localhost",
990                "X-blah": ["test", "blah", "test"],
991                "Connection": "close",
992            }
993        )['status']
994        == 404
995    ), 'match headers multiple values 2'
996    assert (
997        client.get(
998            headers={
999                "Host": "localhost",
1000                "X-blah": ["test", "", "test"],
1001                "Connection": "close",
1002            }
1003        )['status']
1004        == 404
1005    ), 'match headers multiple values 3'
1006
1007
1008def test_routes_match_headers_multiple_rules():
1009    route_match({"headers": {"x-blah": ["test", "blah"]}})
1010
1011    assert client.get()['status'] == 404, 'match headers multiple rules'
1012    assert (
1013        client.get(
1014            headers={
1015                "Host": "localhost",
1016                "X-blah": "test",
1017                "Connection": "close",
1018            }
1019        )['status']
1020        == 200
1021    ), 'match headers multiple rules 2'
1022    assert (
1023        client.get(
1024            headers={
1025                "Host": "localhost",
1026                "X-blah": "blah",
1027                "Connection": "close",
1028            }
1029        )['status']
1030        == 200
1031    ), 'match headers multiple rules 3'
1032    assert (
1033        client.get(
1034            headers={
1035                "Host": "localhost",
1036                "X-blah": ["test", "blah", "test"],
1037                "Connection": "close",
1038            }
1039        )['status']
1040        == 200
1041    ), 'match headers multiple rules 4'
1042
1043    assert (
1044        client.get(
1045            headers={
1046                "Host": "localhost",
1047                "X-blah": ["blah", ""],
1048                "Connection": "close",
1049            }
1050        )['status']
1051        == 404
1052    ), 'match headers multiple rules 5'
1053
1054
1055def test_routes_match_headers_case_insensitive():
1056    route_match({"headers": {"X-BLAH": "TEST"}})
1057
1058    assert (
1059        client.get(
1060            headers={
1061                "Host": "localhost",
1062                "x-blah": "test",
1063                "Connection": "close",
1064            }
1065        )['status']
1066        == 200
1067    ), 'match headers case insensitive'
1068
1069
1070def test_routes_match_headers_invalid():
1071    route_match_invalid({"headers": ["blah"]})
1072    route_match_invalid({"headers": {"foo": ["bar", {}]}})
1073    route_match_invalid({"headers": {"": "blah"}})
1074
1075
1076def test_routes_match_headers_empty_rule():
1077    route_match({"headers": {"host": ""}})
1078
1079    assert client.get()['status'] == 404, 'localhost'
1080    host('', 200)
1081
1082
1083def test_routes_match_headers_empty():
1084    route_match({"headers": {}})
1085    assert client.get()['status'] == 200, 'empty'
1086
1087    route_match({"headers": []})
1088    assert client.get()['status'] == 200, 'empty 2'
1089
1090
1091def test_routes_match_headers_rule_array_empty():
1092    route_match({"headers": {"blah": []}})
1093
1094    assert client.get()['status'] == 404, 'array empty'
1095    assert (
1096        client.get(
1097            headers={
1098                "Host": "localhost",
1099                "blah": "foo",
1100                "Connection": "close",
1101            }
1102        )['status']
1103        == 200
1104    ), 'match headers rule array empty 2'
1105
1106
1107def test_routes_match_headers_array():
1108    route_match(
1109        {
1110            "headers": [
1111                {"x-header1": "foo*"},
1112                {"x-header2": "bar"},
1113                {"x-header3": ["foo", "bar"]},
1114                {"x-header1": "bar", "x-header4": "foo"},
1115            ]
1116        }
1117    )
1118
1119    def check_headers(hds):
1120        hds = dict({"Host": "localhost", "Connection": "close"}, **hds)
1121        assert client.get(headers=hds)['status'] == 200, 'headers array match'
1122
1123    def check_headers_404(hds):
1124        hds = dict({"Host": "localhost", "Connection": "close"}, **hds)
1125        assert (
1126            client.get(headers=hds)['status'] == 404
1127        ), 'headers array no match'
1128
1129    assert client.get()['status'] == 404, 'match headers array'
1130    check_headers({"x-header1": "foo123"})
1131    check_headers({"x-header2": "bar"})
1132    check_headers({"x-header3": "bar"})
1133    check_headers_404({"x-header1": "bar"})
1134    check_headers({"x-header1": "bar", "x-header4": "foo"})
1135
1136    assert 'success' in client.conf_delete(
1137        'routes/0/match/headers/1'
1138    ), 'match headers array configure 2'
1139
1140    check_headers_404({"x-header2": "bar"})
1141    check_headers({"x-header3": "foo"})
1142
1143
1144def test_routes_match_arguments():
1145    route_match({"arguments": {"foo": "bar"}})
1146
1147    assert client.get()['status'] == 404, 'args'
1148    assert client.get(url='/?foo=bar')['status'] == 200, 'args 2'
1149    assert client.get(url='/?foo=bar1')['status'] == 404, 'args 3'
1150    assert client.get(url='/?1foo=bar')['status'] == 404, 'args 4'
1151    assert client.get(url='/?Foo=bar')['status'] == 404, 'case'
1152    assert client.get(url='/?foo=Bar')['status'] == 404, 'case 2'
1153
1154
1155def test_routes_match_arguments_chars():
1156    chars = (
1157        " !\"%23$%25%26'()*%2B,-./0123456789:;<%3D>?@"
1158        "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
1159    )
1160
1161    chars_enc = ""
1162    for h1 in ["2", "3", "4", "5", "6", "7"]:
1163        for h2 in [
1164            "0",
1165            "1",
1166            "2",
1167            "3",
1168            "4",
1169            "5",
1170            "6",
1171            "7",
1172            "8",
1173            "9",
1174            "A",
1175            "B",
1176            "C",
1177            "D",
1178            "E",
1179            "F",
1180        ]:
1181            chars_enc += f'%{h1}{h2}'
1182    chars_enc = chars_enc[:-3]
1183
1184    def check_args(args, query):
1185        route_match({"arguments": args})
1186        assert client.get(url=f'/?{query}')['status'] == 200
1187
1188    check_args({chars: chars}, f'{chars}={chars}')
1189    check_args({chars: chars}, f'{chars}={chars_enc}')
1190    check_args({chars: chars}, f'{chars_enc}={chars}')
1191    check_args({chars: chars}, f'{chars_enc}={chars_enc}')
1192    check_args({chars_enc: chars_enc}, f'{chars}={chars}')
1193    check_args({chars_enc: chars_enc}, f'{chars}={chars_enc}')
1194    check_args({chars_enc: chars_enc}, f'{chars_enc}={chars}')
1195    check_args({chars_enc: chars_enc}, f'{chars_enc}={chars_enc}')
1196
1197
1198def test_routes_match_arguments_empty():
1199    route_match({"arguments": {}})
1200    assert client.get()['status'] == 200, 'arguments empty'
1201
1202    route_match({"arguments": []})
1203    assert client.get()['status'] == 200, 'arguments empty 2'
1204
1205
1206def test_routes_match_arguments_space():
1207    route_match({"arguments": {"+fo o%20": "%20b+a r"}})
1208    assert client.get(url='/? fo o = b a r&')['status'] == 200
1209    assert client.get(url='/?+fo+o+=+b+a+r&')['status'] == 200
1210    assert client.get(url='/?%20fo%20o%20=%20b%20a%20r&')['status'] == 200
1211
1212    route_match({"arguments": {"%20foo": " bar"}})
1213    assert client.get(url='/? foo= bar')['status'] == 200
1214    assert client.get(url='/?+foo=+bar')['status'] == 200
1215    assert client.get(url='/?%20foo=%20bar')['status'] == 200
1216    assert client.get(url='/?+foo= bar')['status'] == 200
1217    assert client.get(url='/?%20foo=+bar')['status'] == 200
1218
1219
1220def test_routes_match_arguments_equal():
1221    route_match({"arguments": {"=": "="}})
1222    assert client.get(url='/?%3D=%3D')['status'] == 200
1223    assert client.get(url='/?%3D==')['status'] == 200
1224    assert client.get(url='/?===')['status'] == 404
1225    assert client.get(url='/?%3D%3D%3D')['status'] == 404
1226    assert client.get(url='/?==%3D')['status'] == 404
1227
1228
1229def test_routes_match_arguments_enc():
1230    route_match({"arguments": {"Ю": "н"}})
1231    assert client.get(url='/?%D0%AE=%D0%BD')['status'] == 200
1232    assert client.get(url='/?%d0%ae=%d0%Bd')['status'] == 200
1233
1234
1235def test_routes_match_arguments_hash():
1236    route_match({"arguments": {"#": "#"}})
1237    assert client.get(url='/?%23=%23')['status'] == 200
1238    assert client.get(url='/?%23=%23#')['status'] == 200
1239    assert client.get(url='/?#=#')['status'] == 404
1240    assert client.get(url='/?%23=#')['status'] == 404
1241
1242
1243def test_routes_match_arguments_wildcard():
1244    route_match({"arguments": {"foo": "*"}})
1245    assert client.get(url='/?foo')['status'] == 200
1246    assert client.get(url='/?foo=')['status'] == 200
1247    assert client.get(url='/?foo=blah')['status'] == 200
1248    assert client.get(url='/?blah=foo')['status'] == 404
1249
1250    route_match({"arguments": {"foo": "%25*"}})
1251    assert client.get(url='/?foo=%xx')['status'] == 200
1252
1253    route_match({"arguments": {"foo": "%2A*"}})
1254    assert client.get(url='/?foo=*xx')['status'] == 200
1255    assert client.get(url='/?foo=xx')['status'] == 404
1256
1257    route_match({"arguments": {"foo": "*%2A"}})
1258    assert client.get(url='/?foo=xx*')['status'] == 200
1259    assert client.get(url='/?foo=xx*x')['status'] == 404
1260
1261    route_match({"arguments": {"foo": "1*2"}})
1262    assert client.get(url='/?foo=12')['status'] == 200
1263    assert client.get(url='/?foo=1blah2')['status'] == 200
1264    assert client.get(url='/?foo=1%2A2')['status'] == 200
1265    assert client.get(url='/?foo=x12')['status'] == 404
1266
1267    route_match({"arguments": {"foo": "bar*", "%25": "%25"}})
1268    assert client.get(url='/?foo=barxx&%=%')['status'] == 200
1269    assert client.get(url='/?foo=barxx&x%=%')['status'] == 404
1270
1271
1272def test_routes_match_arguments_negative():
1273    route_match({"arguments": {"foo": "!"}})
1274    assert client.get(url='/?bar')['status'] == 404
1275    assert client.get(url='/?foo')['status'] == 404
1276    assert client.get(url='/?foo=')['status'] == 404
1277    assert client.get(url='/?foo=%25')['status'] == 200
1278
1279    route_match({"arguments": {"foo": "!*"}})
1280    assert client.get(url='/?bar')['status'] == 404
1281    assert client.get(url='/?foo')['status'] == 404
1282    assert client.get(url='/?foo=')['status'] == 404
1283    assert client.get(url='/?foo=blah')['status'] == 404
1284
1285    route_match({"arguments": {"foo": "!%25"}})
1286    assert client.get(url='/?foo=blah')['status'] == 200
1287    assert client.get(url='/?foo=%')['status'] == 404
1288
1289    route_match({"arguments": {"foo": "%21blah"}})
1290    assert client.get(url='/?foo=%21blah')['status'] == 200
1291    assert client.get(url='/?foo=!blah')['status'] == 200
1292    assert client.get(url='/?foo=bar')['status'] == 404
1293
1294    route_match({"arguments": {"foo": "!!%21*a"}})
1295    assert client.get(url='/?foo=blah')['status'] == 200
1296    assert client.get(url='/?foo=!blah')['status'] == 200
1297    assert client.get(url='/?foo=!!a')['status'] == 404
1298    assert client.get(url='/?foo=!!bla')['status'] == 404
1299
1300
1301def test_routes_match_arguments_percent():
1302    route_match({"arguments": {"%25": "%25"}})
1303    assert client.get(url='/?%=%')['status'] == 200
1304    assert client.get(url='/?%25=%25')['status'] == 200
1305    assert client.get(url='/?%25=%')['status'] == 200
1306
1307    route_match({"arguments": {"%251": "%252"}})
1308    assert client.get(url='/?%1=%2')['status'] == 200
1309    assert client.get(url='/?%251=%252')['status'] == 200
1310    assert client.get(url='/?%251=%2')['status'] == 200
1311
1312    route_match({"arguments": {"%25%21%251": "%25%24%252"}})
1313    assert client.get(url='/?%!%1=%$%2')['status'] == 200
1314    assert client.get(url='/?%25!%251=%25$%252')['status'] == 200
1315    assert client.get(url='/?%25!%1=%$%2')['status'] == 200
1316
1317
1318def test_routes_match_arguments_ampersand():
1319    route_match({"arguments": {"foo": "&"}})
1320    assert client.get(url='/?foo=%26')['status'] == 200
1321    assert client.get(url='/?foo=%26&')['status'] == 200
1322    assert client.get(url='/?foo=%26%26')['status'] == 404
1323    assert client.get(url='/?foo=&')['status'] == 404
1324
1325    route_match({"arguments": {"&": ""}})
1326    assert client.get(url='/?%26=')['status'] == 200
1327    assert client.get(url='/?%26=&')['status'] == 200
1328    assert client.get(url='/?%26=%26')['status'] == 404
1329    assert client.get(url='/?&=')['status'] == 404
1330
1331
1332def test_routes_match_arguments_complex():
1333    route_match({"arguments": {"foo": ""}})
1334
1335    assert client.get(url='/?foo')['status'] == 200, 'complex'
1336    assert client.get(url='/?blah=blah&foo=')['status'] == 200, 'complex 2'
1337    assert client.get(url='/?&&&foo&&&')['status'] == 200, 'complex 3'
1338    assert client.get(url='/?foo&foo=bar&foo')['status'] == 404, 'complex 4'
1339    assert client.get(url='/?foo=&foo')['status'] == 200, 'complex 5'
1340    assert client.get(url='/?&=&foo&==&')['status'] == 200, 'complex 6'
1341    assert client.get(url='/?&=&bar&==&')['status'] == 404, 'complex 7'
1342
1343
1344def test_routes_match_arguments_multiple():
1345    route_match({"arguments": {"foo": "bar", "blah": "test"}})
1346
1347    assert client.get()['status'] == 404, 'multiple'
1348    assert client.get(url='/?foo=bar&blah=test')['status'] == 200, 'multiple 2'
1349    assert client.get(url='/?foo=bar&blah')['status'] == 404, 'multiple 3'
1350    assert client.get(url='/?foo=bar&blah=tes')['status'] == 404, 'multiple 4'
1351    assert (
1352        client.get(url='/?foo=b%61r&bl%61h=t%65st')['status'] == 200
1353    ), 'multiple 5'
1354
1355
1356def test_routes_match_arguments_multiple_rules():
1357    route_match({"arguments": {"foo": ["bar", "blah"]}})
1358
1359    assert client.get()['status'] == 404, 'rules'
1360    assert client.get(url='/?foo=bar')['status'] == 200, 'rules 2'
1361    assert client.get(url='/?foo=blah')['status'] == 200, 'rules 3'
1362    assert (
1363        client.get(url='/?foo=blah&foo=bar&foo=blah')['status'] == 200
1364    ), 'rules 4'
1365    assert client.get(url='/?foo=blah&foo=bar&foo=')['status'] == 404, 'rules 5'
1366
1367
1368def test_routes_match_arguments_array():
1369    route_match(
1370        {
1371            "arguments": [
1372                {"var1": "val1*"},
1373                {"var2": "val2"},
1374                {"var3": ["foo", "bar"]},
1375                {"var1": "bar", "var4": "foo"},
1376            ]
1377        }
1378    )
1379
1380    assert client.get()['status'] == 404, 'arr'
1381    assert client.get(url='/?var1=val123')['status'] == 200, 'arr 2'
1382    assert client.get(url='/?var2=val2')['status'] == 200, 'arr 3'
1383    assert client.get(url='/?var3=bar')['status'] == 200, 'arr 4'
1384    assert client.get(url='/?var1=bar')['status'] == 404, 'arr 5'
1385    assert client.get(url='/?var1=bar&var4=foo')['status'] == 200, 'arr 6'
1386
1387    assert 'success' in client.conf_delete(
1388        'routes/0/match/arguments/1'
1389    ), 'match arguments array configure 2'
1390
1391    assert client.get(url='/?var2=val2')['status'] == 404, 'arr 7'
1392    assert client.get(url='/?var3=foo')['status'] == 200, 'arr 8'
1393
1394
1395def test_routes_match_arguments_invalid():
1396    route_match_invalid({"arguments": ["var"]})
1397    route_match_invalid({"arguments": [{"var1": {}}]})
1398    route_match_invalid({"arguments": {"": "bar"}})
1399    route_match_invalid({"arguments": {"foo": "%"}})
1400    route_match_invalid({"arguments": {"foo": "%1G"}})
1401    route_match_invalid({"arguments": {"%": "bar"}})
1402    route_match_invalid({"arguments": {"foo": "%0"}})
1403    route_match_invalid({"arguments": {"foo": "%%1F"}})
1404    route_match_invalid({"arguments": {"%%1F": ""}})
1405    route_match_invalid({"arguments": {"%7%F": ""}})
1406
1407
1408def test_routes_match_query():
1409    route_match({"query": "!"})
1410    assert client.get(url='/')['status'] == 404
1411    assert client.get(url='/?')['status'] == 404
1412    assert client.get(url='/?foo')['status'] == 200
1413    assert client.get(url='/?foo=')['status'] == 200
1414    assert client.get(url='/?foo=baz')['status'] == 200
1415
1416    route_match({"query": "foo=%26"})
1417    assert client.get(url='/?foo=&')['status'] == 200
1418
1419    route_match({"query": "a=b&c=d"})
1420    assert client.get(url='/?a=b&c=d')['status'] == 200
1421
1422    route_match({"query": "a=b%26c%3Dd"})
1423    assert client.get(url='/?a=b%26c%3Dd')['status'] == 200
1424    assert client.get(url='/?a=b&c=d')['status'] == 200
1425
1426    route_match({"query": "a=b%26c%3Dd+e"})
1427    assert client.get(url='/?a=b&c=d e')['status'] == 200
1428
1429
1430def test_routes_match_query_array():
1431    route_match({"query": ["foo", "bar"]})
1432
1433    assert client.get()['status'] == 404, 'no args'
1434    assert client.get(url='/?foo')['status'] == 200, 'arg first'
1435    assert client.get(url='/?bar')['status'] == 200, 'arg second'
1436
1437    assert 'success' in client.conf_delete(
1438        'routes/0/match/query/1'
1439    ), 'query array remove second'
1440
1441    assert client.get(url='/?foo')['status'] == 200, 'still arg first'
1442    assert client.get(url='/?bar')['status'] == 404, 'no arg second'
1443
1444    route_match({"query": ["!f", "foo"]})
1445
1446    assert client.get(url='/?f')['status'] == 404, 'negative arg'
1447    assert client.get(url='/?fo')['status'] == 404, 'negative arg 2'
1448    assert client.get(url='/?foo')['status'] == 200, 'negative arg 3'
1449
1450    route_match({"query": []})
1451    assert client.get()['status'] == 200, 'empty array'
1452
1453
1454def test_routes_match_query_invalid():
1455    route_match_invalid({"query": [1]})
1456    route_match_invalid({"query": "%"})
1457    route_match_invalid({"query": "%1G"})
1458    route_match_invalid({"query": "%0"})
1459    route_match_invalid({"query": "%%1F"})
1460    route_match_invalid({"query": ["foo", "%3D", "%%1F"]})
1461
1462
1463def test_routes_match_cookies():
1464    route_match({"cookies": {"foO": "bar"}})
1465
1466    assert client.get()['status'] == 404, 'cookie'
1467    cookie('foO=bar', 200)
1468    cookie('foO=bar;1', 200)
1469    cookie(['foO=bar', 'blah=blah'], 200)
1470    cookie('foO=bar; blah=blah', 200)
1471    cookie('Foo=bar', 404)
1472    cookie('foO=Bar', 404)
1473    cookie('foO=bar1', 404)
1474    cookie('1foO=bar;', 404)
1475
1476
1477def test_routes_match_cookies_empty():
1478    route_match({"cookies": {}})
1479    assert client.get()['status'] == 200, 'cookies empty'
1480
1481    route_match({"cookies": []})
1482    assert client.get()['status'] == 200, 'cookies empty 2'
1483
1484
1485def test_routes_match_cookies_invalid():
1486    route_match_invalid({"cookies": ["var"]})
1487    route_match_invalid({"cookies": [{"foo": {}}]})
1488
1489
1490def test_routes_match_cookies_complex():
1491    route_match({"cookies": {"foo": "bar=baz"}})
1492    cookie('foo=bar=baz', 200)
1493    cookie('   foo=bar=baz   ', 200)
1494    cookie('=foo=bar=baz', 404)
1495
1496    route_match({"cookies": {"foo": ""}})
1497    cookie('foo=', 200)
1498    cookie('foo=;', 200)
1499    cookie('  foo=;', 200)
1500    cookie('foo', 404)
1501    cookie('', 404)
1502    cookie('=', 404)
1503
1504
1505def test_routes_match_cookies_multiple():
1506    route_match({"cookies": {"foo": "bar", "blah": "blah"}})
1507
1508    assert client.get()['status'] == 404, 'multiple'
1509    cookie('foo=bar; blah=blah', 200)
1510    cookie(['foo=bar', 'blah=blah'], 200)
1511    cookie(['foo=bar; blah', 'blah'], 404)
1512    cookie(['foo=bar; blah=test', 'blah=blah'], 404)
1513
1514
1515def test_routes_match_cookies_multiple_values():
1516    route_match({"cookies": {"blah": "blah"}})
1517
1518    cookie(['blah=blah', 'blah=blah', 'blah=blah'], 200)
1519    cookie(['blah=blah', 'blah=test', 'blah=blah'], 404)
1520    cookie(['blah=blah; blah=', 'blah=blah'], 404)
1521
1522
1523def test_routes_match_cookies_multiple_rules():
1524    route_match({"cookies": {"blah": ["test", "blah"]}})
1525
1526    assert client.get()['status'] == 404, 'multiple rules'
1527    cookie('blah=test', 200)
1528    cookie('blah=blah', 200)
1529    cookie(['blah=blah', 'blah=test', 'blah=blah'], 200)
1530    cookie(['blah=blah; blah=test', 'blah=blah'], 200)
1531    cookie(['blah=blah', 'blah'], 200)  # invalid cookie
1532
1533
1534def test_routes_match_cookies_array():
1535    route_match(
1536        {
1537            "cookies": [
1538                {"var1": "val1*"},
1539                {"var2": "val2"},
1540                {"var3": ["foo", "bar"]},
1541                {"var1": "bar", "var4": "foo"},
1542            ]
1543        }
1544    )
1545
1546    assert client.get()['status'] == 404, 'cookies array'
1547    cookie('var1=val123', 200)
1548    cookie('var2=val2', 200)
1549    cookie(' var2=val2 ', 200)
1550    cookie('var3=bar', 200)
1551    cookie('var3=bar;', 200)
1552    cookie('var1=bar', 404)
1553    cookie('var1=bar; var4=foo;', 200)
1554    cookie(['var1=bar', 'var4=foo'], 200)
1555
1556    assert 'success' in client.conf_delete(
1557        'routes/0/match/cookies/1'
1558    ), 'match cookies array configure 2'
1559
1560    cookie('var2=val2', 404)
1561    cookie('var3=foo', 200)
1562
1563
1564def test_routes_match_scheme():
1565    route_match({"scheme": "http"})
1566    route_match({"scheme": "https"})
1567    route_match({"scheme": "HtTp"})
1568    route_match({"scheme": "HtTpS"})
1569
1570
1571def test_routes_match_scheme_invalid():
1572    route_match_invalid({"scheme": ["http"]})
1573    route_match_invalid({"scheme": "ftp"})
1574    route_match_invalid({"scheme": "ws"})
1575    route_match_invalid({"scheme": "*"})
1576    route_match_invalid({"scheme": ""})
1577
1578
1579def test_routes_source_port():
1580    def sock_port():
1581        sock = client.http(b'', raw=True, no_recv=True)
1582        port = sock.getsockname()[1]
1583        return (sock, port)
1584
1585    sock, port = sock_port()
1586    sock2, _ = sock_port()
1587
1588    route_match({"source": f'127.0.0.1:{port}'})
1589    assert client.get(sock=sock)['status'] == 200, 'exact'
1590    assert client.get(sock=sock2)['status'] == 404, 'exact 2'
1591
1592    sock, port = sock_port()
1593    sock2, _ = sock_port()
1594
1595    route_match({"source": f'!127.0.0.1:{port}'})
1596    assert client.get(sock=sock)['status'] == 404, 'negative'
1597    assert client.get(sock=sock2)['status'] == 200, 'negative 2'
1598
1599    sock, port = sock_port()
1600    sock2, _ = sock_port()
1601
1602    route_match({"source": [f'*:{port}', "!127.0.0.1"]})
1603    assert client.get(sock=sock)['status'] == 404, 'negative 3'
1604    assert client.get(sock=sock2)['status'] == 404, 'negative 4'
1605
1606    sock, port = sock_port()
1607    sock2, _ = sock_port()
1608
1609    route_match({"source": f'127.0.0.1:{port}-{port}'})
1610    assert client.get(sock=sock)['status'] == 200, 'range single'
1611    assert client.get(sock=sock2)['status'] == 404, 'range single 2'
1612
1613    socks = [
1614        sock_port(),
1615        sock_port(),
1616        sock_port(),
1617        sock_port(),
1618        sock_port(),
1619    ]
1620    socks.sort(key=lambda sock: sock[1])
1621
1622    route_match({"source": f'127.0.0.1:{socks[1][1]}-{socks[3][1]}'})
1623    assert client.get(sock=socks[0][0])['status'] == 404, 'range'
1624    assert client.get(sock=socks[1][0])['status'] == 200, 'range 2'
1625    assert client.get(sock=socks[2][0])['status'] == 200, 'range 3'
1626    assert client.get(sock=socks[3][0])['status'] == 200, 'range 4'
1627    assert client.get(sock=socks[4][0])['status'] == 404, 'range 5'
1628
1629    socks = [
1630        sock_port(),
1631        sock_port(),
1632        sock_port(),
1633    ]
1634    socks.sort(key=lambda sock: sock[1])
1635
1636    route_match(
1637        {
1638            "source": [
1639                f'127.0.0.1:{socks[0][1]}',
1640                f'127.0.0.1:{socks[2][1]}',
1641            ]
1642        }
1643    )
1644    assert client.get(sock=socks[0][0])['status'] == 200, 'array'
1645    assert client.get(sock=socks[1][0])['status'] == 404, 'array 2'
1646    assert client.get(sock=socks[2][0])['status'] == 200, 'array 3'
1647
1648
1649def test_routes_source_addr():
1650    assert 'success' in client.conf(
1651        {
1652            "*:8080": {"pass": "routes"},
1653            "[::1]:8081": {"pass": "routes"},
1654        },
1655        'listeners',
1656    ), 'source listeners configure'
1657
1658    def get_ipv6():
1659        return client.get(sock_type='ipv6', port=8081)
1660
1661    route_match({"source": "127.0.0.1"})
1662    assert client.get()['status'] == 200, 'exact'
1663    assert get_ipv6()['status'] == 404, 'exact ipv6'
1664
1665    route_match({"source": ["127.0.0.1"]})
1666    assert client.get()['status'] == 200, 'exact 2'
1667    assert get_ipv6()['status'] == 404, 'exact 2 ipv6'
1668
1669    route_match({"source": "!127.0.0.1"})
1670    assert client.get()['status'] == 404, 'exact neg'
1671    assert get_ipv6()['status'] == 200, 'exact neg ipv6'
1672
1673    route_match({"source": "127.0.0.2"})
1674    assert client.get()['status'] == 404, 'exact 3'
1675    assert get_ipv6()['status'] == 404, 'exact 3 ipv6'
1676
1677    route_match({"source": "127.0.0.1-127.0.0.1"})
1678    assert client.get()['status'] == 200, 'range single'
1679    assert get_ipv6()['status'] == 404, 'range single ipv6'
1680
1681    route_match({"source": "127.0.0.2-127.0.0.2"})
1682    assert client.get()['status'] == 404, 'range single 2'
1683    assert get_ipv6()['status'] == 404, 'range single 2 ipv6'
1684
1685    route_match({"source": "127.0.0.2-127.0.0.3"})
1686    assert client.get()['status'] == 404, 'range'
1687    assert get_ipv6()['status'] == 404, 'range ipv6'
1688
1689    route_match({"source": "127.0.0.1-127.0.0.2"})
1690    assert client.get()['status'] == 200, 'range 2'
1691    assert get_ipv6()['status'] == 404, 'range 2 ipv6'
1692
1693    route_match({"source": "127.0.0.0-127.0.0.2"})
1694    assert client.get()['status'] == 200, 'range 3'
1695    assert get_ipv6()['status'] == 404, 'range 3 ipv6'
1696
1697    route_match({"source": "127.0.0.0-127.0.0.1"})
1698    assert client.get()['status'] == 200, 'range 4'
1699    assert get_ipv6()['status'] == 404, 'range 4 ipv6'
1700
1701    route_match({"source": "126.0.0.0-127.0.0.0"})
1702    assert client.get()['status'] == 404, 'range 5'
1703    assert get_ipv6()['status'] == 404, 'range 5 ipv6'
1704
1705    route_match({"source": "126.126.126.126-127.0.0.2"})
1706    assert client.get()['status'] == 200, 'range 6'
1707    assert get_ipv6()['status'] == 404, 'range 6 ipv6'
1708
1709
1710def test_routes_source_ipv6():
1711    assert 'success' in client.conf(
1712        {
1713            "[::1]:8080": {"pass": "routes"},
1714            "127.0.0.1:8081": {"pass": "routes"},
1715        },
1716        'listeners',
1717    ), 'source listeners configure'
1718
1719    route_match({"source": "::1"})
1720    assert client.get(sock_type='ipv6')['status'] == 200, 'exact'
1721    assert client.get(port=8081)['status'] == 404, 'exact ipv4'
1722
1723    route_match({"source": ["::1"]})
1724    assert client.get(sock_type='ipv6')['status'] == 200, 'exact 2'
1725    assert client.get(port=8081)['status'] == 404, 'exact 2 ipv4'
1726
1727    route_match({"source": "!::1"})
1728    assert client.get(sock_type='ipv6')['status'] == 404, 'exact neg'
1729    assert client.get(port=8081)['status'] == 200, 'exact neg ipv4'
1730
1731    route_match({"source": "::2"})
1732    assert client.get(sock_type='ipv6')['status'] == 404, 'exact 3'
1733    assert client.get(port=8081)['status'] == 404, 'exact 3 ipv4'
1734
1735    route_match({"source": "::1-::1"})
1736    assert client.get(sock_type='ipv6')['status'] == 200, 'range'
1737    assert client.get(port=8081)['status'] == 404, 'range ipv4'
1738
1739    route_match({"source": "::2-::2"})
1740    assert client.get(sock_type='ipv6')['status'] == 404, 'range 2'
1741    assert client.get(port=8081)['status'] == 404, 'range 2 ipv4'
1742
1743    route_match({"source": "::2-::3"})
1744    assert client.get(sock_type='ipv6')['status'] == 404, 'range 3'
1745    assert client.get(port=8081)['status'] == 404, 'range 3 ipv4'
1746
1747    route_match({"source": "::1-::2"})
1748    assert client.get(sock_type='ipv6')['status'] == 200, 'range 4'
1749    assert client.get(port=8081)['status'] == 404, 'range 4 ipv4'
1750
1751    route_match({"source": "::0-::2"})
1752    assert client.get(sock_type='ipv6')['status'] == 200, 'range 5'
1753    assert client.get(port=8081)['status'] == 404, 'range 5 ipv4'
1754
1755    route_match({"source": "::0-::1"})
1756    assert client.get(sock_type='ipv6')['status'] == 200, 'range 6'
1757    assert client.get(port=8081)['status'] == 404, 'range 6 ipv4'
1758
1759
1760def test_routes_source_cidr():
1761    assert 'success' in client.conf(
1762        {
1763            "*:8080": {"pass": "routes"},
1764            "[::1]:8081": {"pass": "routes"},
1765        },
1766        'listeners',
1767    ), 'source listeners configure'
1768
1769    def get_ipv6():
1770        return client.get(sock_type='ipv6', port=8081)
1771
1772    route_match({"source": "127.0.0.1/32"})
1773    assert client.get()['status'] == 200, '32'
1774    assert get_ipv6()['status'] == 404, '32 ipv6'
1775
1776    route_match({"source": "127.0.0.0/32"})
1777    assert client.get()['status'] == 404, '32 2'
1778    assert get_ipv6()['status'] == 404, '32 2 ipv6'
1779
1780    route_match({"source": "127.0.0.0/31"})
1781    assert client.get()['status'] == 200, '31'
1782    assert get_ipv6()['status'] == 404, '31 ipv6'
1783
1784    route_match({"source": "0.0.0.0/1"})
1785    assert client.get()['status'] == 200, '1'
1786    assert get_ipv6()['status'] == 404, '1 ipv6'
1787
1788    route_match({"source": "0.0.0.0/0"})
1789    assert client.get()['status'] == 200, '0'
1790    assert get_ipv6()['status'] == 404, '0 ipv6'
1791
1792
1793def test_routes_source_cidr_ipv6():
1794    assert 'success' in client.conf(
1795        {
1796            "[::1]:8080": {"pass": "routes"},
1797            "127.0.0.1:8081": {"pass": "routes"},
1798        },
1799        'listeners',
1800    ), 'source listeners configure'
1801
1802    route_match({"source": "::1/128"})
1803    assert client.get(sock_type='ipv6')['status'] == 200, '128'
1804    assert client.get(port=8081)['status'] == 404, '128 ipv4'
1805
1806    route_match({"source": "::0/128"})
1807    assert client.get(sock_type='ipv6')['status'] == 404, '128 2'
1808    assert client.get(port=8081)['status'] == 404, '128 ipv4'
1809
1810    route_match({"source": "::0/127"})
1811    assert client.get(sock_type='ipv6')['status'] == 200, '127'
1812    assert client.get(port=8081)['status'] == 404, '127 ipv4'
1813
1814    route_match({"source": "::0/32"})
1815    assert client.get(sock_type='ipv6')['status'] == 200, '32'
1816    assert client.get(port=8081)['status'] == 404, '32 ipv4'
1817
1818    route_match({"source": "::0/1"})
1819    assert client.get(sock_type='ipv6')['status'] == 200, '1'
1820    assert client.get(port=8081)['status'] == 404, '1 ipv4'
1821
1822    route_match({"source": "::/0"})
1823    assert client.get(sock_type='ipv6')['status'] == 200, '0'
1824    assert client.get(port=8081)['status'] == 404, '0 ipv4'
1825
1826
1827def test_routes_source_unix(temp_dir):
1828    addr = f'{temp_dir}/sock'
1829
1830    assert 'success' in client.conf(
1831        {
1832            "127.0.0.1:8081": {"pass": "routes"},
1833            f'unix:{addr}': {"pass": "routes"},
1834        },
1835        'listeners',
1836    ), 'source listeners configure'
1837
1838    route_match({"source": "!0.0.0.0/0"})
1839    assert (
1840        client.get(sock_type='unix', addr=addr)['status'] == 200
1841    ), 'unix ipv4 neg'
1842
1843    route_match({"source": "!::/0"})
1844    assert (
1845        client.get(sock_type='unix', addr=addr)['status'] == 200
1846    ), 'unix ipv6 neg'
1847
1848    route_match({"source": "unix"})
1849    assert client.get(port=8081)['status'] == 404, 'unix ipv4'
1850    assert client.get(sock_type='unix', addr=addr)['status'] == 200, 'unix'
1851
1852
1853def test_routes_match_source():
1854    route_match({"source": "::"})
1855    route_match(
1856        {
1857            "source": [
1858                "127.0.0.1",
1859                "192.168.0.10:8080",
1860                "192.168.0.11:8080-8090",
1861            ]
1862        }
1863    )
1864    route_match(
1865        {
1866            "source": [
1867                "10.0.0.0/8",
1868                "10.0.0.0/7:1000",
1869                "10.0.0.0/32:8080-8090",
1870            ]
1871        }
1872    )
1873    route_match(
1874        {
1875            "source": [
1876                "10.0.0.0-10.0.0.1",
1877                "10.0.0.0-11.0.0.0:1000",
1878                "127.0.0.0-127.0.0.255:8080-8090",
1879            ]
1880        }
1881    )
1882    route_match({"source": ["2001::", "[2002::]:8000", "[2003::]:8080-8090"]})
1883    route_match(
1884        {
1885            "source": [
1886                "2001::-200f:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
1887                "[fe08::-feff::]:8000",
1888                "[fff0::-fff0::10]:8080-8090",
1889            ]
1890        }
1891    )
1892    route_match(
1893        {
1894            "source": [
1895                "2001::/16",
1896                "[0ff::/64]:8000",
1897                "[fff0:abcd:ffff:ffff:ffff::/128]:8080-8090",
1898            ]
1899        }
1900    )
1901    route_match({"source": "*:0-65535"})
1902    assert client.get()['status'] == 200, 'source any'
1903
1904
1905def test_routes_match_source_invalid():
1906    route_match_invalid({"source": "127"})
1907    route_match_invalid({"source": "256.0.0.1"})
1908    route_match_invalid({"source": "127.0.0."})
1909    route_match_invalid({"source": " 127.0.0.1"})
1910    route_match_invalid({"source": "127.0.0.1:"})
1911    route_match_invalid({"source": "127.0.0.1/"})
1912    route_match_invalid({"source": "11.0.0.0/33"})
1913    route_match_invalid({"source": "11.0.0.0/65536"})
1914    route_match_invalid({"source": "11.0.0.0-10.0.0.0"})
1915    route_match_invalid({"source": "11.0.0.0:3000-2000"})
1916    route_match_invalid({"source": ["11.0.0.0:3000-2000"]})
1917    route_match_invalid({"source": "[2001::]:3000-2000"})
1918    route_match_invalid({"source": "2001::-2000::"})
1919    route_match_invalid({"source": "2001::/129"})
1920    route_match_invalid({"source": "::FFFFF"})
1921    route_match_invalid({"source": "[::1]:"})
1922    route_match_invalid({"source": "[:::]:8080"})
1923    route_match_invalid({"source": "*:"})
1924    route_match_invalid({"source": "*:1-a"})
1925    route_match_invalid({"source": "*:65536"})
1926
1927
1928def test_routes_match_source_none():
1929    route_match({"source": []})
1930    assert client.get()['status'] == 404, 'source none'
1931
1932
1933def test_routes_match_destination():
1934    assert 'success' in client.conf(
1935        {"*:8080": {"pass": "routes"}, "*:8081": {"pass": "routes"}},
1936        'listeners',
1937    ), 'listeners configure'
1938
1939    route_match({"destination": "*:8080"})
1940    assert client.get()['status'] == 200, 'dest'
1941    assert client.get(port=8081)['status'] == 404, 'dest 2'
1942
1943    route_match({"destination": ["127.0.0.1:8080"]})
1944    assert client.get()['status'] == 200, 'dest 3'
1945    assert client.get(port=8081)['status'] == 404, 'dest 4'
1946
1947    route_match({"destination": "!*:8080"})
1948    assert client.get()['status'] == 404, 'dest neg'
1949    assert client.get(port=8081)['status'] == 200, 'dest neg 2'
1950
1951    route_match({"destination": ['!*:8080', '!*:8081']})
1952    assert client.get()['status'] == 404, 'dest neg 3'
1953    assert client.get(port=8081)['status'] == 404, 'dest neg 4'
1954
1955    route_match({"destination": ['!*:8081', '!*:8082']})
1956    assert client.get()['status'] == 200, 'dest neg 5'
1957
1958    route_match({"destination": ['*:8080', '!*:8080']})
1959    assert client.get()['status'] == 404, 'dest neg 6'
1960
1961    route_match({"destination": ['127.0.0.1:8080', '*:8081', '!*:8080']})
1962    assert client.get()['status'] == 404, 'dest neg 7'
1963    assert client.get(port=8081)['status'] == 200, 'dest neg 8'
1964
1965    route_match({"destination": ['!*:8081', '!*:8082', '*:8083']})
1966    assert client.get()['status'] == 404, 'dest neg 9'
1967
1968    route_match({"destination": ['*:8081', '!127.0.0.1:8080', '*:8080']})
1969    assert client.get()['status'] == 404, 'dest neg 10'
1970    assert client.get(port=8081)['status'] == 200, 'dest neg 11'
1971
1972    assert 'success' in client.conf_delete(
1973        'routes/0/match/destination/0'
1974    ), 'remove destination rule'
1975    assert client.get()['status'] == 404, 'dest neg 12'
1976    assert client.get(port=8081)['status'] == 404, 'dest neg 13'
1977
1978    assert 'success' in client.conf_delete(
1979        'routes/0/match/destination/0'
1980    ), 'remove destination rule 2'
1981    assert client.get()['status'] == 200, 'dest neg 14'
1982    assert client.get(port=8081)['status'] == 404, 'dest neg 15'
1983
1984    assert 'success' in client.conf_post(
1985        "\"!127.0.0.1\"", 'routes/0/match/destination'
1986    ), 'add destination rule'
1987    assert client.get()['status'] == 404, 'dest neg 16'
1988    assert client.get(port=8081)['status'] == 404, 'dest neg 17'
1989
1990
1991def test_routes_match_destination_proxy():
1992    assert 'success' in client.conf(
1993        {
1994            "listeners": {
1995                "*:8080": {"pass": "routes/first"},
1996                "*:8081": {"pass": "routes/second"},
1997            },
1998            "routes": {
1999                "first": [{"action": {"proxy": "http://127.0.0.1:8081"}}],
2000                "second": [
2001                    {
2002                        "match": {"destination": ["127.0.0.1:8081"]},
2003                        "action": {"return": 200},
2004                    }
2005                ],
2006            },
2007            "applications": {},
2008        }
2009    ), 'proxy configure'
2010
2011    assert client.get()['status'] == 200, 'proxy'
2012