...

Source file src/sigs.k8s.io/gateway-api/apis/v1beta1/validation/httproute_test.go

Documentation: sigs.k8s.io/gateway-api/apis/v1beta1/validation

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

View as plain text