...

Source file src/sigs.k8s.io/gateway-api/pkg/test/cel/httproute_test.go

Documentation: sigs.k8s.io/gateway-api/pkg/test/cel

     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  }
  1394  

View as plain text