1
16
17 package validation
18
19 import (
20 "testing"
21
22 "github.com/stretchr/testify/assert"
23 "github.com/stretchr/testify/require"
24 "k8s.io/apimachinery/pkg/util/validation/field"
25
26 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
27 gatewayv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
28 )
29
30 func TestValidateHTTPRoute(t *testing.T) {
31 testService := gatewayv1b1.ObjectName("test-service")
32 pathPrefixMatchType := gatewayv1.PathMatchPathPrefix
33
34 tests := []struct {
35 name string
36 rules []gatewayv1b1.HTTPRouteRule
37 errCount int
38 }{{
39 name: "valid httpRoute with no filters",
40 errCount: 0,
41 rules: []gatewayv1b1.HTTPRouteRule{
42 {
43 Matches: []gatewayv1b1.HTTPRouteMatch{
44 {
45 Path: &gatewayv1b1.HTTPPathMatch{
46 Type: ptrTo(gatewayv1b1.PathMatchType("PathPrefix")),
47 Value: ptrTo("/"),
48 },
49 },
50 },
51 BackendRefs: []gatewayv1b1.HTTPBackendRef{
52 {
53 BackendRef: gatewayv1b1.BackendRef{
54 BackendObjectReference: gatewayv1b1.BackendObjectReference{
55 Name: testService,
56 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
57 },
58 Weight: ptrTo(int32(100)),
59 },
60 },
61 },
62 },
63 },
64 }, {
65 name: "valid httpRoute with 1 filter",
66 errCount: 0,
67 rules: []gatewayv1b1.HTTPRouteRule{
68 {
69 Matches: []gatewayv1b1.HTTPRouteMatch{
70 {
71 Path: &gatewayv1b1.HTTPPathMatch{
72 Type: ptrTo(gatewayv1b1.PathMatchType("PathPrefix")),
73 Value: ptrTo("/"),
74 },
75 },
76 },
77 Filters: []gatewayv1b1.HTTPRouteFilter{
78 {
79 Type: gatewayv1.HTTPRouteFilterRequestMirror,
80 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{
81 BackendRef: gatewayv1b1.BackendObjectReference{
82 Name: testService,
83 Port: ptrTo(gatewayv1b1.PortNumber(8081)),
84 },
85 },
86 },
87 },
88 },
89 },
90 }, {
91 name: "invalid httpRoute with 2 extended filters",
92 errCount: 1,
93 rules: []gatewayv1b1.HTTPRouteRule{
94 {
95 Matches: []gatewayv1b1.HTTPRouteMatch{
96 {
97 Path: &gatewayv1b1.HTTPPathMatch{
98 Type: ptrTo(gatewayv1b1.PathMatchType("PathPrefix")),
99 Value: ptrTo("/"),
100 },
101 },
102 },
103 Filters: []gatewayv1b1.HTTPRouteFilter{
104 {
105 Type: gatewayv1.HTTPRouteFilterURLRewrite,
106 URLRewrite: &gatewayv1b1.HTTPURLRewriteFilter{
107 Path: &gatewayv1b1.HTTPPathModifier{
108 Type: gatewayv1.PrefixMatchHTTPPathModifier,
109 ReplacePrefixMatch: ptrTo("foo"),
110 },
111 },
112 },
113 {
114 Type: gatewayv1.HTTPRouteFilterURLRewrite,
115 URLRewrite: &gatewayv1b1.HTTPURLRewriteFilter{
116 Path: &gatewayv1b1.HTTPPathModifier{
117 Type: gatewayv1.PrefixMatchHTTPPathModifier,
118 ReplacePrefixMatch: ptrTo("bar"),
119 },
120 },
121 },
122 },
123 },
124 },
125 }, {
126 name: "invalid httpRoute with mix of filters and one duplicate",
127 errCount: 1,
128 rules: []gatewayv1b1.HTTPRouteRule{
129 {
130 Matches: []gatewayv1b1.HTTPRouteMatch{
131 {
132 Path: &gatewayv1b1.HTTPPathMatch{
133 Type: ptrTo(gatewayv1b1.PathMatchType("PathPrefix")),
134 Value: ptrTo("/"),
135 },
136 },
137 },
138 Filters: []gatewayv1b1.HTTPRouteFilter{
139 {
140 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
141 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
142 Set: []gatewayv1b1.HTTPHeader{
143 {
144 Name: "special-header",
145 Value: "foo",
146 },
147 },
148 },
149 },
150 {
151 Type: gatewayv1.HTTPRouteFilterRequestMirror,
152 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{
153 BackendRef: gatewayv1b1.BackendObjectReference{
154 Name: testService,
155 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
156 },
157 },
158 },
159 {
160 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
161 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
162 Add: []gatewayv1b1.HTTPHeader{
163 {
164 Name: "my-header",
165 Value: "bar",
166 },
167 },
168 },
169 },
170 },
171 },
172 },
173 }, {
174 name: "invalid httpRoute with multiple duplicate filters",
175 errCount: 2,
176 rules: []gatewayv1b1.HTTPRouteRule{
177 {
178 Matches: []gatewayv1b1.HTTPRouteMatch{
179 {
180 Path: &gatewayv1b1.HTTPPathMatch{
181 Type: ptrTo(gatewayv1b1.PathMatchType("PathPrefix")),
182 Value: ptrTo("/"),
183 },
184 },
185 },
186 Filters: []gatewayv1b1.HTTPRouteFilter{
187 {
188 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
189 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
190 Set: []gatewayv1b1.HTTPHeader{
191 {
192 Name: "special-header",
193 Value: "foo",
194 },
195 },
196 },
197 },
198 {
199 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
200 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
201 Add: []gatewayv1b1.HTTPHeader{
202 {
203 Name: "my-header",
204 Value: "bar",
205 },
206 },
207 },
208 },
209 {
210 Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier,
211 ResponseHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
212 Add: []gatewayv1b1.HTTPHeader{
213 {
214 Name: "extra-header",
215 Value: "foo",
216 },
217 },
218 },
219 },
220 {
221 Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier,
222 ResponseHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
223 Set: []gatewayv1b1.HTTPHeader{
224 {
225 Name: "other-header",
226 Value: "bat",
227 },
228 },
229 },
230 },
231 },
232 },
233 },
234 }, {
235 name: "valid httpRoute with duplicate ExtensionRef filters",
236 errCount: 0,
237 rules: []gatewayv1b1.HTTPRouteRule{
238 {
239 Matches: []gatewayv1b1.HTTPRouteMatch{
240 {
241 Path: &gatewayv1b1.HTTPPathMatch{
242 Type: ptrTo(gatewayv1b1.PathMatchType("PathPrefix")),
243 Value: ptrTo("/"),
244 },
245 },
246 },
247 Filters: []gatewayv1b1.HTTPRouteFilter{
248 {
249 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
250 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
251 Set: []gatewayv1b1.HTTPHeader{
252 {
253 Name: "special-header",
254 Value: "foo",
255 },
256 },
257 },
258 },
259 {
260 Type: gatewayv1.HTTPRouteFilterRequestMirror,
261 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{
262 BackendRef: gatewayv1b1.BackendObjectReference{
263 Name: testService,
264 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
265 },
266 },
267 },
268 {
269 Type: "ExtensionRef",
270 ExtensionRef: &gatewayv1b1.LocalObjectReference{
271 Kind: "Service",
272 Name: "test",
273 },
274 },
275 {
276 Type: "ExtensionRef",
277 ExtensionRef: &gatewayv1b1.LocalObjectReference{
278 Kind: "Service",
279 Name: "test",
280 },
281 },
282 {
283 Type: "ExtensionRef",
284 ExtensionRef: &gatewayv1b1.LocalObjectReference{
285 Kind: "Service",
286 Name: "test",
287 },
288 },
289 },
290 },
291 },
292 }, {
293 name: "valid redirect path modifier",
294 errCount: 0,
295 rules: []gatewayv1b1.HTTPRouteRule{
296 {
297 Filters: []gatewayv1b1.HTTPRouteFilter{
298 {
299 Type: gatewayv1.HTTPRouteFilterRequestRedirect,
300 RequestRedirect: &gatewayv1b1.HTTPRequestRedirectFilter{
301 Path: &gatewayv1b1.HTTPPathModifier{
302 Type: gatewayv1.FullPathHTTPPathModifier,
303 ReplaceFullPath: ptrTo("foo"),
304 },
305 },
306 },
307 },
308 },
309 },
310 }, {
311 name: "redirect path modifier with type mismatch",
312 errCount: 2,
313 rules: []gatewayv1b1.HTTPRouteRule{{
314 Filters: []gatewayv1b1.HTTPRouteFilter{{
315 Type: gatewayv1.HTTPRouteFilterRequestRedirect,
316 RequestRedirect: &gatewayv1b1.HTTPRequestRedirectFilter{
317 Path: &gatewayv1b1.HTTPPathModifier{
318 Type: gatewayv1.PrefixMatchHTTPPathModifier,
319 ReplaceFullPath: ptrTo("foo"),
320 },
321 },
322 }},
323 }},
324 }, {
325 name: "valid rewrite path modifier",
326 errCount: 0,
327 rules: []gatewayv1b1.HTTPRouteRule{{
328 Matches: []gatewayv1b1.HTTPRouteMatch{{
329 Path: &gatewayv1b1.HTTPPathMatch{
330 Type: &pathPrefixMatchType,
331 Value: ptrTo("/bar"),
332 },
333 }},
334 Filters: []gatewayv1b1.HTTPRouteFilter{{
335 Type: gatewayv1.HTTPRouteFilterURLRewrite,
336 URLRewrite: &gatewayv1b1.HTTPURLRewriteFilter{
337 Path: &gatewayv1b1.HTTPPathModifier{
338 Type: gatewayv1.PrefixMatchHTTPPathModifier,
339 ReplacePrefixMatch: ptrTo("foo"),
340 },
341 },
342 }},
343 }},
344 }, {
345 name: "rewrite path modifier missing path match",
346 errCount: 1,
347 rules: []gatewayv1b1.HTTPRouteRule{{
348 Filters: []gatewayv1b1.HTTPRouteFilter{{
349 Type: gatewayv1.HTTPRouteFilterURLRewrite,
350 URLRewrite: &gatewayv1b1.HTTPURLRewriteFilter{
351 Path: &gatewayv1b1.HTTPPathModifier{
352 Type: gatewayv1.PrefixMatchHTTPPathModifier,
353 ReplacePrefixMatch: ptrTo("foo"),
354 },
355 },
356 }},
357 }},
358 }, {
359 name: "rewrite path too many matches",
360 errCount: 1,
361 rules: []gatewayv1b1.HTTPRouteRule{{
362 Matches: []gatewayv1b1.HTTPRouteMatch{{
363 Path: &gatewayv1b1.HTTPPathMatch{
364 Type: &pathPrefixMatchType,
365 Value: ptrTo("/foo"),
366 },
367 }, {
368 Path: &gatewayv1b1.HTTPPathMatch{
369 Type: &pathPrefixMatchType,
370 Value: ptrTo("/bar"),
371 },
372 }},
373 Filters: []gatewayv1b1.HTTPRouteFilter{{
374 Type: gatewayv1.HTTPRouteFilterURLRewrite,
375 URLRewrite: &gatewayv1b1.HTTPURLRewriteFilter{
376 Path: &gatewayv1b1.HTTPPathModifier{
377 Type: gatewayv1.PrefixMatchHTTPPathModifier,
378 ReplacePrefixMatch: ptrTo("foo"),
379 },
380 },
381 }},
382 }},
383 }, {
384 name: "redirect path modifier with type mismatch",
385 errCount: 2,
386 rules: []gatewayv1b1.HTTPRouteRule{{
387 Filters: []gatewayv1b1.HTTPRouteFilter{{
388 Type: gatewayv1.HTTPRouteFilterURLRewrite,
389 URLRewrite: &gatewayv1b1.HTTPURLRewriteFilter{
390 Path: &gatewayv1b1.HTTPPathModifier{
391 Type: gatewayv1.FullPathHTTPPathModifier,
392 ReplacePrefixMatch: ptrTo("foo"),
393 },
394 },
395 }},
396 }},
397 }, {
398 name: "rewrite and redirect filters combined (invalid)",
399 errCount: 3,
400 rules: []gatewayv1b1.HTTPRouteRule{{
401 Filters: []gatewayv1b1.HTTPRouteFilter{{
402 Type: gatewayv1.HTTPRouteFilterURLRewrite,
403 URLRewrite: &gatewayv1b1.HTTPURLRewriteFilter{
404 Path: &gatewayv1b1.HTTPPathModifier{
405 Type: gatewayv1.PrefixMatchHTTPPathModifier,
406 ReplacePrefixMatch: ptrTo("foo"),
407 },
408 },
409 }, {
410 Type: gatewayv1.HTTPRouteFilterRequestRedirect,
411 RequestRedirect: &gatewayv1b1.HTTPRequestRedirectFilter{
412 Path: &gatewayv1b1.HTTPPathModifier{
413 Type: gatewayv1.PrefixMatchHTTPPathModifier,
414 ReplacePrefixMatch: ptrTo("foo"),
415 },
416 },
417 }},
418 }},
419 }, {
420 name: "multiple actions for the same request header (invalid)",
421 errCount: 2,
422 rules: []gatewayv1b1.HTTPRouteRule{{
423 Filters: []gatewayv1b1.HTTPRouteFilter{{
424 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
425 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
426 Add: []gatewayv1b1.HTTPHeader{
427 {
428 Name: gatewayv1.HTTPHeaderName("x-fruit"),
429 Value: "apple",
430 },
431 {
432 Name: gatewayv1.HTTPHeaderName("x-vegetable"),
433 Value: "carrot",
434 },
435 {
436 Name: gatewayv1.HTTPHeaderName("x-grain"),
437 Value: "rye",
438 },
439 },
440 Set: []gatewayv1b1.HTTPHeader{
441 {
442 Name: gatewayv1.HTTPHeaderName("x-fruit"),
443 Value: "watermelon",
444 },
445 {
446 Name: gatewayv1.HTTPHeaderName("x-grain"),
447 Value: "wheat",
448 },
449 {
450 Name: gatewayv1.HTTPHeaderName("x-spice"),
451 Value: "coriander",
452 },
453 },
454 },
455 }},
456 }},
457 }, {
458 name: "multiple actions for the same request header with inconsistent case (invalid)",
459 errCount: 1,
460 rules: []gatewayv1b1.HTTPRouteRule{{
461 Filters: []gatewayv1b1.HTTPRouteFilter{{
462 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
463 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
464 Add: []gatewayv1b1.HTTPHeader{
465 {
466 Name: gatewayv1.HTTPHeaderName("x-fruit"),
467 Value: "apple",
468 },
469 },
470 Set: []gatewayv1b1.HTTPHeader{
471 {
472 Name: gatewayv1.HTTPHeaderName("X-Fruit"),
473 Value: "watermelon",
474 },
475 },
476 },
477 }},
478 }},
479 }, {
480 name: "multiple of the same action for the same request header (invalid)",
481 errCount: 1,
482 rules: []gatewayv1b1.HTTPRouteRule{{
483 Filters: []gatewayv1b1.HTTPRouteFilter{{
484 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
485 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
486 Add: []gatewayv1b1.HTTPHeader{
487 {
488 Name: gatewayv1.HTTPHeaderName("x-fruit"),
489 Value: "apple",
490 },
491 {
492 Name: gatewayv1.HTTPHeaderName("x-fruit"),
493 Value: "plum",
494 },
495 },
496 },
497 }},
498 }},
499 }, {
500 name: "multiple actions for different request headers",
501 errCount: 0,
502 rules: []gatewayv1b1.HTTPRouteRule{{
503 Filters: []gatewayv1b1.HTTPRouteFilter{{
504 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
505 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
506 Add: []gatewayv1b1.HTTPHeader{
507 {
508 Name: gatewayv1.HTTPHeaderName("x-vegetable"),
509 Value: "carrot",
510 },
511 {
512 Name: gatewayv1.HTTPHeaderName("x-grain"),
513 Value: "rye",
514 },
515 },
516 Set: []gatewayv1b1.HTTPHeader{
517 {
518 Name: gatewayv1.HTTPHeaderName("x-fruit"),
519 Value: "watermelon",
520 },
521 {
522 Name: gatewayv1.HTTPHeaderName("x-spice"),
523 Value: "coriander",
524 },
525 },
526 },
527 }},
528 }},
529 }, {
530 name: "multiple actions for the same response header (invalid)",
531 errCount: 1,
532 rules: []gatewayv1b1.HTTPRouteRule{{
533 Filters: []gatewayv1b1.HTTPRouteFilter{{
534 Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier,
535 ResponseHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
536 Add: []gatewayv1b1.HTTPHeader{{
537 Name: gatewayv1.HTTPHeaderName("x-example"),
538 Value: "blueberry",
539 }},
540 Set: []gatewayv1b1.HTTPHeader{{
541 Name: gatewayv1.HTTPHeaderName("x-example"),
542 Value: "turnip",
543 }},
544 },
545 }},
546 }},
547 }, {
548 name: "multiple actions for different response headers",
549 errCount: 0,
550 rules: []gatewayv1b1.HTTPRouteRule{{
551 Filters: []gatewayv1b1.HTTPRouteFilter{{
552 Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier,
553 ResponseHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
554 Add: []gatewayv1b1.HTTPHeader{{
555 Name: gatewayv1.HTTPHeaderName("x-example"),
556 Value: "blueberry",
557 }},
558 Set: []gatewayv1b1.HTTPHeader{{
559 Name: gatewayv1.HTTPHeaderName("x-different"),
560 Value: "turnip",
561 }},
562 },
563 }},
564 }},
565 }}
566
567 for _, tc := range tests {
568 t.Run(tc.name, func(t *testing.T) {
569 var errs field.ErrorList
570 route := gatewayv1b1.HTTPRoute{Spec: gatewayv1b1.HTTPRouteSpec{Rules: tc.rules}}
571 errs = ValidateHTTPRoute(&route)
572 if len(errs) != tc.errCount {
573 t.Errorf("got %d errors, want %d errors: %s", len(errs), tc.errCount, errs)
574 }
575 })
576 }
577 }
578
579 func TestValidateHTTPBackendUniqueFilters(t *testing.T) {
580 var testService gatewayv1b1.ObjectName = "testService"
581 var specialService gatewayv1b1.ObjectName = "specialService"
582 tests := []struct {
583 name string
584 rules []gatewayv1b1.HTTPRouteRule
585 errCount int
586 }{{
587 name: "valid httpRoute Rules backendref filters",
588 errCount: 0,
589 rules: []gatewayv1b1.HTTPRouteRule{{
590 BackendRefs: []gatewayv1b1.HTTPBackendRef{
591 {
592 BackendRef: gatewayv1b1.BackendRef{
593 BackendObjectReference: gatewayv1b1.BackendObjectReference{
594 Name: testService,
595 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
596 },
597 Weight: ptrTo(int32(100)),
598 },
599 Filters: []gatewayv1b1.HTTPRouteFilter{
600 {
601 Type: gatewayv1.HTTPRouteFilterRequestMirror,
602 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{
603 BackendRef: gatewayv1b1.BackendObjectReference{
604 Name: testService,
605 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
606 },
607 },
608 },
609 },
610 },
611 },
612 }},
613 }, {
614 name: "valid httpRoute Rules duplicate mirror filter",
615 errCount: 0,
616 rules: []gatewayv1b1.HTTPRouteRule{{
617 BackendRefs: []gatewayv1b1.HTTPBackendRef{
618 {
619 BackendRef: gatewayv1b1.BackendRef{
620 BackendObjectReference: gatewayv1b1.BackendObjectReference{
621 Name: testService,
622 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
623 },
624 },
625 Filters: []gatewayv1b1.HTTPRouteFilter{
626 {
627 Type: gatewayv1.HTTPRouteFilterRequestMirror,
628 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{
629 BackendRef: gatewayv1b1.BackendObjectReference{
630 Name: testService,
631 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
632 },
633 },
634 },
635 {
636 Type: gatewayv1.HTTPRouteFilterRequestMirror,
637 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{
638 BackendRef: gatewayv1b1.BackendObjectReference{
639 Name: specialService,
640 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
641 },
642 },
643 },
644 },
645 },
646 },
647 }},
648 }}
649
650 for _, tc := range tests {
651 t.Run(tc.name, func(t *testing.T) {
652 route := gatewayv1b1.HTTPRoute{Spec: gatewayv1b1.HTTPRouteSpec{Rules: tc.rules}}
653 errs := ValidateHTTPRoute(&route)
654 if len(errs) != tc.errCount {
655 t.Errorf("got %d errors, want %d errors: %s", len(errs), tc.errCount, errs)
656 }
657 })
658 }
659 }
660
661 func TestValidateHTTPPathMatch(t *testing.T) {
662 tests := []struct {
663 name string
664 path *gatewayv1b1.HTTPPathMatch
665 errCount int
666 }{{
667 name: "invalid httpRoute prefix (/.)",
668 path: &gatewayv1b1.HTTPPathMatch{
669 Type: ptrTo(gatewayv1b1.PathMatchType("PathPrefix")),
670 Value: ptrTo("/."),
671 },
672 errCount: 1,
673 }, {
674 name: "invalid exact (/./)",
675 path: &gatewayv1b1.HTTPPathMatch{
676 Type: ptrTo(gatewayv1b1.PathMatchType("Exact")),
677 Value: ptrTo("/foo/./bar"),
678 },
679 errCount: 1,
680 }, {
681 name: "valid httpRoute prefix",
682 path: &gatewayv1b1.HTTPPathMatch{
683 Type: ptrTo(gatewayv1b1.PathMatchType("PathPrefix")),
684 Value: ptrTo("/"),
685 },
686 errCount: 0,
687 }, {
688 name: "invalid httpRoute prefix (/[])",
689 path: &gatewayv1b1.HTTPPathMatch{
690 Type: ptrTo(gatewayv1b1.PathMatchType("PathPrefix")),
691 Value: ptrTo("/[]"),
692 },
693 errCount: 1,
694 }, {
695 name: "invalid httpRoute exact (/^)",
696 path: &gatewayv1b1.HTTPPathMatch{
697 Type: ptrTo(gatewayv1b1.PathMatchType("Exact")),
698 Value: ptrTo("/^"),
699 },
700 errCount: 1,
701 }}
702
703 for _, tc := range tests {
704 t.Run(tc.name, func(t *testing.T) {
705 route := gatewayv1b1.HTTPRoute{Spec: gatewayv1b1.HTTPRouteSpec{
706 Rules: []gatewayv1b1.HTTPRouteRule{{
707 Matches: []gatewayv1b1.HTTPRouteMatch{{
708 Path: tc.path,
709 }},
710 BackendRefs: []gatewayv1b1.HTTPBackendRef{{
711 BackendRef: gatewayv1b1.BackendRef{
712 BackendObjectReference: gatewayv1b1.BackendObjectReference{
713 Name: gatewayv1b1.ObjectName("test"),
714 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
715 },
716 },
717 }},
718 }},
719 }}
720
721 errs := ValidateHTTPRoute(&route)
722 if len(errs) != tc.errCount {
723 t.Errorf("got %d errors, want %d errors: %s", len(errs), tc.errCount, errs)
724 }
725 })
726 }
727 }
728
729 func TestValidateHTTPHeaderMatches(t *testing.T) {
730 tests := []struct {
731 name string
732 headerMatches []gatewayv1b1.HTTPHeaderMatch
733 expectErr string
734 }{{
735 name: "no header matches",
736 headerMatches: nil,
737 expectErr: "",
738 }, {
739 name: "no header matched more than once",
740 headerMatches: []gatewayv1b1.HTTPHeaderMatch{
741 {Name: "Header-Name-1", Value: "val-1"},
742 {Name: "Header-Name-2", Value: "val-2"},
743 {Name: "Header-Name-3", Value: "val-3"},
744 },
745 expectErr: "",
746 }, {
747 name: "header matched more than once (same case)",
748 headerMatches: []gatewayv1b1.HTTPHeaderMatch{
749 {Name: "Header-Name-1", Value: "val-1"},
750 {Name: "Header-Name-2", Value: "val-2"},
751 {Name: "Header-Name-1", Value: "val-3"},
752 },
753 expectErr: "spec.rules[0].matches[0].headers: Invalid value: \"Header-Name-1\": cannot match the same header multiple times in the same rule",
754 }, {
755 name: "header matched more than once (different case)",
756 headerMatches: []gatewayv1b1.HTTPHeaderMatch{
757 {Name: "Header-Name-1", Value: "val-1"},
758 {Name: "Header-Name-2", Value: "val-2"},
759 {Name: "HEADER-NAME-2", Value: "val-3"},
760 },
761 expectErr: "spec.rules[0].matches[0].headers: Invalid value: \"Header-Name-2\": cannot match the same header multiple times in the same rule",
762 }}
763
764 for _, tc := range tests {
765 t.Run(tc.name, func(t *testing.T) {
766 route := gatewayv1b1.HTTPRoute{Spec: gatewayv1b1.HTTPRouteSpec{
767 Rules: []gatewayv1b1.HTTPRouteRule{{
768 Matches: []gatewayv1b1.HTTPRouteMatch{{
769 Headers: tc.headerMatches,
770 }},
771 BackendRefs: []gatewayv1b1.HTTPBackendRef{{
772 BackendRef: gatewayv1b1.BackendRef{
773 BackendObjectReference: gatewayv1b1.BackendObjectReference{
774 Name: gatewayv1b1.ObjectName("test"),
775 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
776 },
777 },
778 }},
779 }},
780 }}
781
782 errs := ValidateHTTPRoute(&route)
783 if len(tc.expectErr) == 0 {
784 assert.Emptyf(t, errs, "expected no errors, got %d errors: %s", len(errs), errs)
785 } else {
786 require.Lenf(t, errs, 1, "expected one error, got %d errors: %s", len(errs), errs)
787 assert.Equal(t, tc.expectErr, errs[0].Error())
788 }
789 })
790 }
791 }
792
793 func TestValidateHTTPQueryParamMatches(t *testing.T) {
794 tests := []struct {
795 name string
796 queryParamMatches []gatewayv1b1.HTTPQueryParamMatch
797 expectErr string
798 }{{
799 name: "no query param matches",
800 queryParamMatches: nil,
801 expectErr: "",
802 }, {
803 name: "no query param matched more than once",
804 queryParamMatches: []gatewayv1b1.HTTPQueryParamMatch{
805 {Name: "query-param-1", Value: "val-1"},
806 {Name: "query-param-2", Value: "val-2"},
807 {Name: "query-param-3", Value: "val-3"},
808 },
809 expectErr: "",
810 }, {
811 name: "query param matched more than once",
812 queryParamMatches: []gatewayv1b1.HTTPQueryParamMatch{
813 {Name: "query-param-1", Value: "val-1"},
814 {Name: "query-param-2", Value: "val-2"},
815 {Name: "query-param-1", Value: "val-3"},
816 },
817 expectErr: "spec.rules[0].matches[0].queryParams: Invalid value: \"query-param-1\": cannot match the same query parameter multiple times in the same rule",
818 }, {
819 name: "query param names with different casing are not considered duplicates",
820 queryParamMatches: []gatewayv1b1.HTTPQueryParamMatch{
821 {Name: "query-param-1", Value: "val-1"},
822 {Name: "query-param-2", Value: "val-2"},
823 {Name: "QUERY-PARAM-1", Value: "val-3"},
824 },
825 expectErr: "",
826 }}
827
828 for _, tc := range tests {
829 t.Run(tc.name, func(t *testing.T) {
830 route := gatewayv1b1.HTTPRoute{Spec: gatewayv1b1.HTTPRouteSpec{
831 Rules: []gatewayv1b1.HTTPRouteRule{{
832 Matches: []gatewayv1b1.HTTPRouteMatch{{
833 QueryParams: tc.queryParamMatches,
834 }},
835 BackendRefs: []gatewayv1b1.HTTPBackendRef{{
836 BackendRef: gatewayv1b1.BackendRef{
837 BackendObjectReference: gatewayv1b1.BackendObjectReference{
838 Name: gatewayv1b1.ObjectName("test"),
839 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
840 },
841 },
842 }},
843 }},
844 }}
845
846 errs := ValidateHTTPRoute(&route)
847 if len(tc.expectErr) == 0 {
848 assert.Emptyf(t, errs, "expected no errors, got %d errors: %s", len(errs), errs)
849 } else {
850 require.Lenf(t, errs, 1, "expected one error, got %d errors: %s", len(errs), errs)
851 assert.Equal(t, tc.expectErr, errs[0].Error())
852 }
853 })
854 }
855 }
856
857 func TestValidateServicePort(t *testing.T) {
858 portPtr := func(n int) *gatewayv1b1.PortNumber {
859 p := gatewayv1b1.PortNumber(n)
860 return &p
861 }
862
863 groupPtr := func(g string) *gatewayv1b1.Group {
864 p := gatewayv1b1.Group(g)
865 return &p
866 }
867
868 kindPtr := func(k string) *gatewayv1b1.Kind {
869 p := gatewayv1b1.Kind(k)
870 return &p
871 }
872
873 tests := []struct {
874 name string
875 rules []gatewayv1b1.HTTPRouteRule
876 errCount int
877 }{{
878 name: "default groupkind with port",
879 errCount: 0,
880 rules: []gatewayv1b1.HTTPRouteRule{{
881 BackendRefs: []gatewayv1b1.HTTPBackendRef{{
882 BackendRef: gatewayv1b1.BackendRef{
883 BackendObjectReference: gatewayv1b1.BackendObjectReference{
884 Name: "backend",
885 Port: portPtr(99),
886 },
887 },
888 }},
889 }},
890 }, {
891 name: "default groupkind with no port",
892 errCount: 1,
893 rules: []gatewayv1b1.HTTPRouteRule{{
894 BackendRefs: []gatewayv1b1.HTTPBackendRef{{
895 BackendRef: gatewayv1b1.BackendRef{
896 BackendObjectReference: gatewayv1b1.BackendObjectReference{
897 Name: "backend",
898 },
899 },
900 }},
901 }},
902 }, {
903 name: "explicit service with port",
904 errCount: 0,
905 rules: []gatewayv1b1.HTTPRouteRule{{
906 BackendRefs: []gatewayv1b1.HTTPBackendRef{{
907 BackendRef: gatewayv1b1.BackendRef{
908 BackendObjectReference: gatewayv1b1.BackendObjectReference{
909 Group: groupPtr(""),
910 Kind: kindPtr("Service"),
911 Name: "backend",
912 Port: portPtr(99),
913 },
914 },
915 }},
916 }},
917 }, {
918 name: "explicit service with no port",
919 errCount: 1,
920 rules: []gatewayv1b1.HTTPRouteRule{{
921 BackendRefs: []gatewayv1b1.HTTPBackendRef{{
922 BackendRef: gatewayv1b1.BackendRef{
923 BackendObjectReference: gatewayv1b1.BackendObjectReference{
924 Group: groupPtr(""),
925 Kind: kindPtr("Service"),
926 Name: "backend",
927 },
928 },
929 }},
930 }},
931 }, {
932 name: "explicit ref with no port",
933 errCount: 0,
934 rules: []gatewayv1b1.HTTPRouteRule{{
935 BackendRefs: []gatewayv1b1.HTTPBackendRef{{
936 BackendRef: gatewayv1b1.BackendRef{
937 BackendObjectReference: gatewayv1b1.BackendObjectReference{
938 Group: groupPtr("foo.example.com"),
939 Kind: kindPtr("Foo"),
940 Name: "backend",
941 },
942 },
943 }},
944 }},
945 }}
946
947 for _, tc := range tests {
948 t.Run(tc.name, func(t *testing.T) {
949 route := gatewayv1b1.HTTPRoute{Spec: gatewayv1b1.HTTPRouteSpec{Rules: tc.rules}}
950 errs := ValidateHTTPRoute(&route)
951 if len(errs) != tc.errCount {
952 t.Errorf("got %d errors, want %d errors: %s", len(errs), tc.errCount, errs)
953 }
954 })
955 }
956 }
957
958 func TestValidateHTTPRouteTypeMatchesField(t *testing.T) {
959 tests := []struct {
960 name string
961 routeFilter gatewayv1b1.HTTPRouteFilter
962 errCount int
963 }{{
964 name: "valid HTTPRouteFilterRequestHeaderModifier route filter",
965 routeFilter: gatewayv1b1.HTTPRouteFilter{
966 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
967 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{
968 Set: []gatewayv1b1.HTTPHeader{{Name: "name"}},
969 Add: []gatewayv1b1.HTTPHeader{{Name: "add"}},
970 Remove: []string{"remove"},
971 },
972 },
973 errCount: 0,
974 }, {
975 name: "invalid HTTPRouteFilterRequestHeaderModifier type filter with non-matching field",
976 routeFilter: gatewayv1b1.HTTPRouteFilter{
977 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
978 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{},
979 },
980 errCount: 2,
981 }, {
982 name: "invalid HTTPRouteFilterRequestHeaderModifier type filter with empty value field",
983 routeFilter: gatewayv1b1.HTTPRouteFilter{
984 Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier,
985 },
986 errCount: 1,
987 }, {
988 name: "valid HTTPRouteFilterRequestMirror route filter",
989 routeFilter: gatewayv1b1.HTTPRouteFilter{
990 Type: gatewayv1.HTTPRouteFilterRequestMirror,
991 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{BackendRef: gatewayv1b1.BackendObjectReference{
992 Group: new(gatewayv1b1.Group),
993 Kind: new(gatewayv1b1.Kind),
994 Name: "name",
995 Namespace: new(gatewayv1b1.Namespace),
996 Port: ptrTo(gatewayv1b1.PortNumber(22)),
997 }},
998 },
999 errCount: 0,
1000 }, {
1001 name: "invalid HTTPRouteFilterRequestMirror type filter with non-matching field",
1002 routeFilter: gatewayv1b1.HTTPRouteFilter{
1003 Type: gatewayv1.HTTPRouteFilterRequestMirror,
1004 RequestHeaderModifier: &gatewayv1b1.HTTPHeaderFilter{},
1005 },
1006 errCount: 2,
1007 }, {
1008 name: "invalid HTTPRouteFilterRequestMirror type filter with empty value field",
1009 routeFilter: gatewayv1b1.HTTPRouteFilter{
1010 Type: gatewayv1.HTTPRouteFilterRequestMirror,
1011 },
1012 errCount: 1,
1013 }, {
1014 name: "valid HTTPRouteFilterRequestRedirect route filter",
1015 routeFilter: gatewayv1b1.HTTPRouteFilter{
1016 Type: gatewayv1.HTTPRouteFilterRequestRedirect,
1017 RequestRedirect: &gatewayv1b1.HTTPRequestRedirectFilter{
1018 Scheme: new(string),
1019 Hostname: new(gatewayv1b1.PreciseHostname),
1020 Path: &gatewayv1b1.HTTPPathModifier{},
1021 Port: new(gatewayv1b1.PortNumber),
1022 StatusCode: new(int),
1023 },
1024 },
1025 errCount: 1,
1026 }, {
1027 name: "invalid HTTPRouteFilterRequestRedirect type filter with non-matching field",
1028 routeFilter: gatewayv1b1.HTTPRouteFilter{
1029 Type: gatewayv1.HTTPRouteFilterRequestRedirect,
1030 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{},
1031 },
1032 errCount: 2,
1033 }, {
1034 name: "invalid HTTPRouteFilterRequestRedirect type filter with empty value field",
1035 routeFilter: gatewayv1b1.HTTPRouteFilter{
1036 Type: gatewayv1.HTTPRouteFilterRequestRedirect,
1037 },
1038 errCount: 1,
1039 }, {
1040 name: "valid HTTPRouteFilterExtensionRef filter",
1041 routeFilter: gatewayv1b1.HTTPRouteFilter{
1042 Type: gatewayv1.HTTPRouteFilterExtensionRef,
1043 ExtensionRef: &gatewayv1b1.LocalObjectReference{
1044 Group: "group",
1045 Kind: "kind",
1046 Name: "name",
1047 },
1048 },
1049 errCount: 0,
1050 }, {
1051 name: "invalid HTTPRouteFilterExtensionRef type filter with non-matching field",
1052 routeFilter: gatewayv1b1.HTTPRouteFilter{
1053 Type: gatewayv1.HTTPRouteFilterExtensionRef,
1054 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{},
1055 },
1056 errCount: 2,
1057 }, {
1058 name: "invalid HTTPRouteFilterExtensionRef type filter with empty value field",
1059 routeFilter: gatewayv1b1.HTTPRouteFilter{
1060 Type: gatewayv1.HTTPRouteFilterExtensionRef,
1061 },
1062 errCount: 1,
1063 }, {
1064 name: "valid HTTPRouteFilterURLRewrite route filter",
1065 routeFilter: gatewayv1b1.HTTPRouteFilter{
1066 Type: gatewayv1.HTTPRouteFilterURLRewrite,
1067 URLRewrite: &gatewayv1b1.HTTPURLRewriteFilter{
1068 Hostname: new(gatewayv1b1.PreciseHostname),
1069 Path: &gatewayv1b1.HTTPPathModifier{},
1070 },
1071 },
1072 errCount: 0,
1073 }, {
1074 name: "invalid HTTPRouteFilterURLRewrite type filter with non-matching field",
1075 routeFilter: gatewayv1b1.HTTPRouteFilter{
1076 Type: gatewayv1.HTTPRouteFilterURLRewrite,
1077 RequestMirror: &gatewayv1b1.HTTPRequestMirrorFilter{},
1078 },
1079 errCount: 2,
1080 }, {
1081 name: "invalid HTTPRouteFilterURLRewrite type filter with empty value field",
1082 routeFilter: gatewayv1b1.HTTPRouteFilter{
1083 Type: gatewayv1.HTTPRouteFilterURLRewrite,
1084 },
1085 errCount: 1,
1086 }, {
1087 name: "empty type filter is valid (caught by CRD validation)",
1088 routeFilter: gatewayv1b1.HTTPRouteFilter{},
1089 errCount: 0,
1090 }}
1091
1092 for _, tc := range tests {
1093 t.Run(tc.name, func(t *testing.T) {
1094 route := gatewayv1b1.HTTPRoute{
1095 Spec: gatewayv1b1.HTTPRouteSpec{
1096 Rules: []gatewayv1b1.HTTPRouteRule{{
1097 Filters: []gatewayv1b1.HTTPRouteFilter{tc.routeFilter},
1098 BackendRefs: []gatewayv1b1.HTTPBackendRef{{
1099 BackendRef: gatewayv1b1.BackendRef{
1100 BackendObjectReference: gatewayv1b1.BackendObjectReference{
1101 Name: gatewayv1b1.ObjectName("test"),
1102 Port: ptrTo(gatewayv1b1.PortNumber(8080)),
1103 },
1104 },
1105 }},
1106 }},
1107 },
1108 }
1109 errs := ValidateHTTPRoute(&route)
1110 if len(errs) != tc.errCount {
1111 t.Errorf("got %d errors, want %d errors: %s", len(errs), tc.errCount, errs)
1112 }
1113 })
1114 }
1115 }
1116
1117 func TestValidateRequestRedirectFiltersWithNoBackendRef(t *testing.T) {
1118 testService := gatewayv1b1.ObjectName("test-service")
1119 tests := []struct {
1120 name string
1121 rules []gatewayv1b1.HTTPRouteRule
1122 errCount int
1123 }{
1124 {
1125 name: "backendref with request redirect httpRoute filter",
1126 errCount: 1,
1127 rules: []gatewayv1b1.HTTPRouteRule{
1128 {
1129 Filters: []gatewayv1b1.HTTPRouteFilter{
1130 {
1131 Type: gatewayv1.HTTPRouteFilterRequestRedirect,
1132 RequestRedirect: &gatewayv1b1.HTTPRequestRedirectFilter{
1133 Scheme: ptrTo("https"),
1134 StatusCode: ptrTo(301),
1135 },
1136 },
1137 },
1138 BackendRefs: []gatewayv1b1.HTTPBackendRef{
1139 {
1140 BackendRef: gatewayv1b1.BackendRef{
1141 BackendObjectReference: gatewayv1b1.BackendObjectReference{
1142 Name: testService,
1143 Port: ptrTo(gatewayv1b1.PortNumber(80)),
1144 },
1145 },
1146 },
1147 },
1148 },
1149 },
1150 }, {
1151 name: "request redirect without backendref in httpRoute filter",
1152 errCount: 0,
1153 rules: []gatewayv1b1.HTTPRouteRule{
1154 {
1155 Filters: []gatewayv1b1.HTTPRouteFilter{
1156 {
1157 Type: gatewayv1.HTTPRouteFilterRequestRedirect,
1158 RequestRedirect: &gatewayv1b1.HTTPRequestRedirectFilter{
1159 Scheme: ptrTo("https"),
1160 StatusCode: ptrTo(301),
1161 },
1162 },
1163 },
1164 },
1165 },
1166 },
1167 }
1168
1169 for _, tc := range tests {
1170 t.Run(tc.name, func(t *testing.T) {
1171 var errs field.ErrorList
1172 route := gatewayv1b1.HTTPRoute{Spec: gatewayv1b1.HTTPRouteSpec{Rules: tc.rules}}
1173 errs = ValidateHTTPRoute(&route)
1174 if len(errs) != tc.errCount {
1175 t.Errorf("got %d errors, want %d errors: %s", len(errs), tc.errCount, errs)
1176 }
1177 })
1178 }
1179 }
1180
1181 func toDuration(durationString string) *gatewayv1b1.Duration {
1182 return (*gatewayv1b1.Duration)(&durationString)
1183 }
1184
1185 func TestValidateHTTPTimeouts(t *testing.T) {
1186 tests := []struct {
1187 name string
1188 rules []gatewayv1b1.HTTPRouteRule
1189 errCount int
1190 }{
1191 {
1192 name: "valid httpRoute Rules timeouts",
1193 errCount: 0,
1194 rules: []gatewayv1b1.HTTPRouteRule{
1195 {
1196 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1197 Request: toDuration("1ms"),
1198 },
1199 },
1200 },
1201 }, {
1202 name: "valid httpRoute Rules timeout set to 0s (disabled)",
1203 errCount: 0,
1204 rules: []gatewayv1b1.HTTPRouteRule{
1205 {
1206 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1207 Request: toDuration("0s"),
1208 },
1209 },
1210 },
1211 }, {
1212 name: "valid httpRoute Rules timeout set to 0ms (disabled)",
1213 errCount: 0,
1214 rules: []gatewayv1b1.HTTPRouteRule{
1215 {
1216 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1217 Request: toDuration("0ms"),
1218 },
1219 },
1220 },
1221 }, {}, {
1222 name: "valid httpRoute Rules timeout set to 0h (disabled)",
1223 errCount: 0,
1224 rules: []gatewayv1b1.HTTPRouteRule{
1225 {
1226 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1227 Request: toDuration("0h"),
1228 },
1229 },
1230 },
1231 }, {
1232 name: "valid httpRoute Rules timeout and backendRequest have the same value",
1233 errCount: 0,
1234 rules: []gatewayv1b1.HTTPRouteRule{
1235 {
1236 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1237 Request: toDuration("1ms"),
1238 BackendRequest: toDuration("1ms"),
1239 },
1240 },
1241 },
1242 }, {
1243 name: "invalid httpRoute Rules backendRequest timeout cannot be longer than request timeout",
1244 errCount: 1,
1245 rules: []gatewayv1b1.HTTPRouteRule{
1246 {
1247 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1248 Request: toDuration("1ms"),
1249 BackendRequest: toDuration("2ms"),
1250 },
1251 },
1252 },
1253 }, {
1254 name: "valid httpRoute Rules request timeout 1s and backendRequest timeout 200ms",
1255 errCount: 0,
1256 rules: []gatewayv1b1.HTTPRouteRule{
1257 {
1258 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1259 Request: toDuration("1s"),
1260 BackendRequest: toDuration("200ms"),
1261 },
1262 },
1263 },
1264 }, {
1265 name: "valid httpRoute Rules request timeout 10s and backendRequest timeout 10s",
1266 errCount: 0,
1267 rules: []gatewayv1b1.HTTPRouteRule{
1268 {
1269 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1270 Request: toDuration("10s"),
1271 BackendRequest: toDuration("10s"),
1272 },
1273 },
1274 },
1275 }, {
1276 name: "invalid httpRoute Rules backendRequest timeout cannot be greater than request timeout",
1277 errCount: 1,
1278 rules: []gatewayv1b1.HTTPRouteRule{
1279 {
1280 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1281 Request: toDuration("200ms"),
1282 BackendRequest: toDuration("1s"),
1283 },
1284 },
1285 },
1286 }, {
1287 name: "valid httpRoute Rules request 0s (infinite) and backendRequest 100ms",
1288 errCount: 0,
1289 rules: []gatewayv1b1.HTTPRouteRule{
1290 {
1291 Timeouts: &gatewayv1b1.HTTPRouteTimeouts{
1292 Request: toDuration("0s"),
1293 BackendRequest: toDuration("100ms"),
1294 },
1295 },
1296 },
1297 },
1298 }
1299 for _, tc := range tests {
1300 t.Run(tc.name, func(t *testing.T) {
1301 route := gatewayv1b1.HTTPRoute{Spec: gatewayv1b1.HTTPRouteSpec{Rules: tc.rules}}
1302 errs := ValidateHTTPRoute(&route)
1303 if len(errs) != tc.errCount {
1304 t.Errorf("got %d errors, want %d errors: %s", len(errs), tc.errCount, errs)
1305 }
1306 })
1307 }
1308 }
1309
View as plain text