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