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