sigs.k8s.io/gateway-api@v1.0.0/apis/v1beta1/validation/httproute_test.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 }