istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/validation/virtualservice_test.go (about)

     1  // Copyright 2010 Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package validation
    16  
    17  import (
    18  	"testing"
    19  
    20  	"google.golang.org/protobuf/proto"
    21  	"google.golang.org/protobuf/types/known/wrapperspb"
    22  
    23  	networking "istio.io/api/networking/v1alpha3"
    24  	"istio.io/istio/pkg/config"
    25  )
    26  
    27  func TestValidateChainingVirtualService(t *testing.T) {
    28  	testCases := []struct {
    29  		name  string
    30  		in    proto.Message
    31  		valid bool
    32  	}{
    33  		{
    34  			name: "root simple",
    35  			in: &networking.VirtualService{
    36  				Hosts:    []string{"foo.bar"},
    37  				Gateways: []string{"test-gateway"},
    38  				Http: []*networking.HTTPRoute{{
    39  					Delegate: &networking.Delegate{
    40  						Name:      "test",
    41  						Namespace: "test",
    42  					},
    43  				}},
    44  			},
    45  			valid: true,
    46  		},
    47  		{
    48  			name: "root applies to sidecar with delegate",
    49  			in: &networking.VirtualService{
    50  				Hosts: []string{"foo.bar"},
    51  				Http: []*networking.HTTPRoute{{
    52  					Delegate: &networking.Delegate{
    53  						Name:      "test",
    54  						Namespace: "test",
    55  					},
    56  				}},
    57  			},
    58  			valid: true,
    59  		},
    60  		{
    61  			name: "root with delegate and destination in one route",
    62  			in: &networking.VirtualService{
    63  				Hosts: []string{"foo.bar"},
    64  				Http: []*networking.HTTPRoute{{
    65  					Delegate: &networking.Delegate{
    66  						Name:      "test",
    67  						Namespace: "test",
    68  					},
    69  					Route: []*networking.HTTPRouteDestination{{
    70  						Destination: &networking.Destination{Host: "foo.baz"},
    71  					}},
    72  				}},
    73  			},
    74  			valid: false,
    75  		},
    76  		{
    77  			name: "delegate with no destination",
    78  			in: &networking.VirtualService{
    79  				Hosts: []string{},
    80  				Http:  []*networking.HTTPRoute{{}},
    81  			},
    82  			valid: false,
    83  		},
    84  		{
    85  			name: "delegate with delegate",
    86  			in: &networking.VirtualService{
    87  				Hosts: []string{},
    88  				Http: []*networking.HTTPRoute{
    89  					{
    90  						Delegate: &networking.Delegate{
    91  							Name:      "test",
    92  							Namespace: "test",
    93  						},
    94  					},
    95  				},
    96  			},
    97  			valid: false,
    98  		},
    99  		{
   100  			name: "delegate with TCP route",
   101  			in: &networking.VirtualService{
   102  				Hosts: []string{},
   103  				Tcp: []*networking.TCPRoute{{
   104  					Route: []*networking.RouteDestination{{
   105  						Destination: &networking.Destination{Host: "foo.baz"},
   106  					}},
   107  				}},
   108  			},
   109  			valid: false,
   110  		},
   111  		{
   112  			name: "delegate with gateway",
   113  			in: &networking.VirtualService{
   114  				Hosts:    []string{},
   115  				Gateways: []string{"test"},
   116  				Http: []*networking.HTTPRoute{{
   117  					Route: []*networking.HTTPRouteDestination{{
   118  						Destination: &networking.Destination{Host: "foo.baz"},
   119  					}},
   120  				}},
   121  			},
   122  			valid: false,
   123  		},
   124  		{
   125  			name: "delegate with sourceNamespace",
   126  			in: &networking.VirtualService{
   127  				Hosts: []string{},
   128  				Http: []*networking.HTTPRoute{{
   129  					Match: []*networking.HTTPMatchRequest{
   130  						{
   131  							SourceNamespace: "test",
   132  						},
   133  					},
   134  					Route: []*networking.HTTPRouteDestination{{
   135  						Destination: &networking.Destination{Host: "foo.baz"},
   136  					}},
   137  				}},
   138  			},
   139  			valid: true,
   140  		},
   141  	}
   142  
   143  	for _, tc := range testCases {
   144  		t.Run(tc.name, func(t *testing.T) {
   145  			if _, err := ValidateVirtualService(config.Config{Spec: tc.in}); (err == nil) != tc.valid {
   146  				t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err)
   147  			}
   148  		})
   149  	}
   150  }
   151  
   152  func TestValidateRootHTTPRoute(t *testing.T) {
   153  	testCases := []struct {
   154  		name  string
   155  		route *networking.HTTPRoute
   156  		valid bool
   157  	}{
   158  		{
   159  			name: "simple",
   160  			route: &networking.HTTPRoute{
   161  				Delegate: &networking.Delegate{
   162  					Name:      "test",
   163  					Namespace: "test",
   164  				},
   165  			},
   166  			valid: true,
   167  		},
   168  		{
   169  			name: "route and delegate conflict",
   170  			route: &networking.HTTPRoute{
   171  				Route: []*networking.HTTPRouteDestination{{
   172  					Destination: &networking.Destination{Host: "foo.baz"},
   173  				}},
   174  				Delegate: &networking.Delegate{
   175  					Name:      "test",
   176  					Namespace: "test",
   177  				},
   178  			},
   179  			valid: false,
   180  		},
   181  		{
   182  			name: "redirect and delegate conflict",
   183  			route: &networking.HTTPRoute{
   184  				Redirect: &networking.HTTPRedirect{
   185  					Uri:       "/lerp",
   186  					Authority: "foo.biz",
   187  				},
   188  				Delegate: &networking.Delegate{
   189  					Name:      "test",
   190  					Namespace: "test",
   191  				},
   192  			},
   193  			valid: false,
   194  		},
   195  		{
   196  			name: "null header match", route: &networking.HTTPRoute{
   197  				Delegate: &networking.Delegate{
   198  					Name:      "test",
   199  					Namespace: "test",
   200  				},
   201  				Match: []*networking.HTTPMatchRequest{{
   202  					Headers: map[string]*networking.StringMatch{
   203  						"header": nil,
   204  					},
   205  				}},
   206  			}, valid: false,
   207  		},
   208  		{
   209  			name: "prefix header match", route: &networking.HTTPRoute{
   210  				Delegate: &networking.Delegate{
   211  					Name:      "test",
   212  					Namespace: "test",
   213  				},
   214  				Match: []*networking.HTTPMatchRequest{{
   215  					Headers: map[string]*networking.StringMatch{
   216  						"header": {
   217  							MatchType: &networking.StringMatch_Prefix{Prefix: "test"},
   218  						},
   219  					},
   220  				}},
   221  			}, valid: true,
   222  		},
   223  		{
   224  			name: "empty prefix header match (delegate)", route: &networking.HTTPRoute{
   225  				Delegate: &networking.Delegate{
   226  					Name:      "test",
   227  					Namespace: "test",
   228  				},
   229  				Match: []*networking.HTTPMatchRequest{{
   230  					Headers: map[string]*networking.StringMatch{
   231  						"header": {
   232  							MatchType: &networking.StringMatch_Prefix{Prefix: ""},
   233  						},
   234  					},
   235  				}},
   236  			}, valid: false,
   237  		},
   238  		{
   239  			name: "exact header match", route: &networking.HTTPRoute{
   240  				Delegate: &networking.Delegate{
   241  					Name:      "test",
   242  					Namespace: "test",
   243  				},
   244  				Match: []*networking.HTTPMatchRequest{{
   245  					Headers: map[string]*networking.StringMatch{
   246  						"header": {
   247  							MatchType: &networking.StringMatch_Exact{Exact: "test"},
   248  						},
   249  					},
   250  				}},
   251  			}, valid: true,
   252  		},
   253  		{name: "regex header match", route: &networking.HTTPRoute{
   254  			Delegate: &networking.Delegate{
   255  				Name:      "test",
   256  				Namespace: "test",
   257  			},
   258  			Match: []*networking.HTTPMatchRequest{{
   259  				Headers: map[string]*networking.StringMatch{
   260  					"header": {
   261  						MatchType: &networking.StringMatch_Regex{Regex: "test"},
   262  					},
   263  				},
   264  			}},
   265  		}, valid: true},
   266  		{name: "exact without headers match", route: &networking.HTTPRoute{
   267  			Redirect: &networking.HTTPRedirect{
   268  				Uri:       "/",
   269  				Authority: "foo.biz",
   270  			},
   271  			Match: []*networking.HTTPMatchRequest{{
   272  				WithoutHeaders: map[string]*networking.StringMatch{
   273  					"header": {
   274  						MatchType: &networking.StringMatch_Exact{Exact: "test"},
   275  					},
   276  				},
   277  			}},
   278  		}, valid: true},
   279  		{name: "regex without headers match", route: &networking.HTTPRoute{
   280  			Redirect: &networking.HTTPRedirect{
   281  				Uri:       "/",
   282  				Authority: "foo.biz",
   283  			},
   284  			Match: []*networking.HTTPMatchRequest{{
   285  				WithoutHeaders: map[string]*networking.StringMatch{
   286  					"header": {
   287  						MatchType: &networking.StringMatch_Regex{Regex: "test"},
   288  					},
   289  				},
   290  			}},
   291  		}, valid: true},
   292  		{name: "regex without headers match ?", route: &networking.HTTPRoute{
   293  			Redirect: &networking.HTTPRedirect{
   294  				Uri:       "/",
   295  				Authority: "foo.biz",
   296  			},
   297  			Match: []*networking.HTTPMatchRequest{{
   298  				WithoutHeaders: map[string]*networking.StringMatch{
   299  					"header": {
   300  						MatchType: &networking.StringMatch_Regex{Regex: "?"},
   301  					},
   302  				},
   303  			}},
   304  		}, valid: false},
   305  		{name: "regex without headers match *", route: &networking.HTTPRoute{
   306  			Redirect: &networking.HTTPRedirect{
   307  				Uri:       "/",
   308  				Authority: "foo.biz",
   309  			},
   310  			Match: []*networking.HTTPMatchRequest{{
   311  				WithoutHeaders: map[string]*networking.StringMatch{
   312  					"header": {
   313  						MatchType: &networking.StringMatch_Regex{Regex: "*"},
   314  					},
   315  				},
   316  			}},
   317  		}, valid: true},
   318  		{name: "regex uri match", route: &networking.HTTPRoute{
   319  			Delegate: &networking.Delegate{
   320  				Name:      "test",
   321  				Namespace: "test",
   322  			},
   323  			Match: []*networking.HTTPMatchRequest{{
   324  				Uri: &networking.StringMatch{
   325  					MatchType: &networking.StringMatch_Regex{Regex: "test"},
   326  				},
   327  			}},
   328  		}, valid: true},
   329  		{name: "prefix uri match", route: &networking.HTTPRoute{
   330  			Delegate: &networking.Delegate{
   331  				Name:      "test",
   332  				Namespace: "test",
   333  			},
   334  			Match: []*networking.HTTPMatchRequest{{
   335  				Uri: &networking.StringMatch{
   336  					MatchType: &networking.StringMatch_Prefix{Prefix: "test"},
   337  				},
   338  			}},
   339  		}, valid: true},
   340  		{name: "exact uri match", route: &networking.HTTPRoute{
   341  			Delegate: &networking.Delegate{
   342  				Name:      "test",
   343  				Namespace: "test",
   344  			},
   345  			Match: []*networking.HTTPMatchRequest{{
   346  				Uri: &networking.StringMatch{
   347  					MatchType: &networking.StringMatch_Exact{Exact: "test"},
   348  				},
   349  			}},
   350  		}, valid: true},
   351  		{
   352  			name: "prefix queryParams match", route: &networking.HTTPRoute{
   353  				Delegate: &networking.Delegate{
   354  					Name:      "test",
   355  					Namespace: "test",
   356  				},
   357  				Match: []*networking.HTTPMatchRequest{{
   358  					QueryParams: map[string]*networking.StringMatch{
   359  						"q": {
   360  							MatchType: &networking.StringMatch_Prefix{Prefix: "test"},
   361  						},
   362  					},
   363  				}},
   364  			}, valid: true,
   365  		},
   366  		{
   367  			name: "exact queryParams match", route: &networking.HTTPRoute{
   368  				Delegate: &networking.Delegate{
   369  					Name:      "test",
   370  					Namespace: "test",
   371  				},
   372  				Match: []*networking.HTTPMatchRequest{{
   373  					QueryParams: map[string]*networking.StringMatch{
   374  						"q": {
   375  							MatchType: &networking.StringMatch_Exact{Exact: "test"},
   376  						},
   377  					},
   378  				}},
   379  			}, valid: true,
   380  		},
   381  		{name: "regex queryParams match", route: &networking.HTTPRoute{
   382  			Delegate: &networking.Delegate{
   383  				Name:      "test",
   384  				Namespace: "test",
   385  			},
   386  			Match: []*networking.HTTPMatchRequest{{
   387  				Headers: map[string]*networking.StringMatch{
   388  					"q": {
   389  						MatchType: &networking.StringMatch_Regex{Regex: "test"},
   390  					},
   391  				},
   392  			}},
   393  		}, valid: true},
   394  		{name: "empty prefix match in method", route: &networking.HTTPRoute{
   395  			Match: []*networking.HTTPMatchRequest{{
   396  				Method: &networking.StringMatch{
   397  					MatchType: &networking.StringMatch_Prefix{Prefix: ""},
   398  				},
   399  			}},
   400  			Redirect: &networking.HTTPRedirect{
   401  				Uri:       "/",
   402  				Authority: "foo.biz",
   403  			},
   404  		}, valid: false},
   405  		{name: "empty regex match in method", route: &networking.HTTPRoute{
   406  			Match: []*networking.HTTPMatchRequest{{
   407  				Method: &networking.StringMatch{
   408  					MatchType: &networking.StringMatch_Regex{Regex: ""},
   409  				},
   410  			}},
   411  			Redirect: &networking.HTTPRedirect{
   412  				Uri:       "/",
   413  				Authority: "foo.biz",
   414  			},
   415  		}, valid: false},
   416  		{name: "empty prefix match in uri", route: &networking.HTTPRoute{
   417  			Match: []*networking.HTTPMatchRequest{{
   418  				Uri: &networking.StringMatch{
   419  					MatchType: &networking.StringMatch_Prefix{Prefix: ""},
   420  				},
   421  			}},
   422  			Redirect: &networking.HTTPRedirect{
   423  				Uri:       "/",
   424  				Authority: "foo.biz",
   425  			},
   426  		}, valid: true},
   427  		{name: "empty regex match in uri", route: &networking.HTTPRoute{
   428  			Match: []*networking.HTTPMatchRequest{{
   429  				Uri: &networking.StringMatch{
   430  					MatchType: &networking.StringMatch_Regex{Regex: ""},
   431  				},
   432  			}},
   433  			Redirect: &networking.HTTPRedirect{
   434  				Uri:       "/",
   435  				Authority: "foo.biz",
   436  			},
   437  		}, valid: false},
   438  		{name: "empty regex match in query", route: &networking.HTTPRoute{
   439  			Match: []*networking.HTTPMatchRequest{{
   440  				QueryParams: map[string]*networking.StringMatch{
   441  					"q": {
   442  						MatchType: &networking.StringMatch_Regex{Regex: ""},
   443  					},
   444  				},
   445  			}},
   446  			Redirect: &networking.HTTPRedirect{
   447  				Uri:       "/",
   448  				Authority: "foo.biz",
   449  			},
   450  		}, valid: false},
   451  		{name: "empty regex match in scheme", route: &networking.HTTPRoute{
   452  			Match: []*networking.HTTPMatchRequest{{
   453  				Scheme: &networking.StringMatch{
   454  					MatchType: &networking.StringMatch_Regex{Regex: ""},
   455  				},
   456  			}},
   457  			Redirect: &networking.HTTPRedirect{
   458  				Uri:       "/",
   459  				Authority: "foo.biz",
   460  			},
   461  		}, valid: false},
   462  		{name: "empty prefix match in scheme", route: &networking.HTTPRoute{
   463  			Match: []*networking.HTTPMatchRequest{{
   464  				Scheme: &networking.StringMatch{
   465  					MatchType: &networking.StringMatch_Prefix{Prefix: ""},
   466  				},
   467  			}},
   468  			Redirect: &networking.HTTPRedirect{
   469  				Uri:       "/",
   470  				Authority: "foo.biz",
   471  			},
   472  		}, valid: false},
   473  		{name: "empty prefix header match", route: &networking.HTTPRoute{
   474  			Match: []*networking.HTTPMatchRequest{{
   475  				Headers: map[string]*networking.StringMatch{
   476  					"header": {
   477  						MatchType: &networking.StringMatch_Prefix{Prefix: ""},
   478  					},
   479  				},
   480  			}},
   481  		}, valid: false},
   482  	}
   483  
   484  	for _, tc := range testCases {
   485  		t.Run(tc.name, func(t *testing.T) {
   486  			if err := validateHTTPRoute(tc.route, false, false); (err.Err == nil) != tc.valid {
   487  				t.Fatalf("got valid=%v but wanted valid=%v: %v", err.Err == nil, tc.valid, err)
   488  			}
   489  		})
   490  	}
   491  }
   492  
   493  func TestValidateDelegateHTTPRoute(t *testing.T) {
   494  	testCases := []struct {
   495  		name  string
   496  		route *networking.HTTPRoute
   497  		valid bool
   498  	}{
   499  		{name: "empty", route: &networking.HTTPRoute{ // nothing
   500  		}, valid:                                     false},
   501  		{name: "simple", route: &networking.HTTPRoute{
   502  			Route: []*networking.HTTPRouteDestination{{
   503  				Destination: &networking.Destination{Host: "foo.baz"},
   504  			}},
   505  		}, valid: true},
   506  		{name: "no destination", route: &networking.HTTPRoute{
   507  			Route: []*networking.HTTPRouteDestination{{
   508  				Destination: nil,
   509  			}},
   510  		}, valid: false},
   511  		{name: "weighted", route: &networking.HTTPRoute{
   512  			Route: []*networking.HTTPRouteDestination{{
   513  				Destination: &networking.Destination{Host: "foo.baz.south"},
   514  				Weight:      25,
   515  			}, {
   516  				Destination: &networking.Destination{Host: "foo.baz.east"},
   517  				Weight:      75,
   518  			}},
   519  		}, valid: true},
   520  		{name: "total weight > 100", route: &networking.HTTPRoute{
   521  			Route: []*networking.HTTPRouteDestination{{
   522  				Destination: &networking.Destination{Host: "foo.baz.south"},
   523  				Weight:      55,
   524  			}, {
   525  				Destination: &networking.Destination{Host: "foo.baz.east"},
   526  				Weight:      50,
   527  			}},
   528  		}, valid: true},
   529  		{name: "total weight < 100", route: &networking.HTTPRoute{
   530  			Route: []*networking.HTTPRouteDestination{{
   531  				Destination: &networking.Destination{Host: "foo.baz.south"},
   532  				Weight:      49,
   533  			}, {
   534  				Destination: &networking.Destination{Host: "foo.baz.east"},
   535  				Weight:      50,
   536  			}},
   537  		}, valid: true},
   538  		{name: "simple redirect", route: &networking.HTTPRoute{
   539  			Redirect: &networking.HTTPRedirect{
   540  				Uri:       "/lerp",
   541  				Authority: "foo.biz",
   542  			},
   543  		}, valid: true},
   544  		{name: "conflicting redirect and route", route: &networking.HTTPRoute{
   545  			Route: []*networking.HTTPRouteDestination{{
   546  				Destination: &networking.Destination{Host: "foo.baz"},
   547  			}},
   548  			Redirect: &networking.HTTPRedirect{
   549  				Uri:       "/lerp",
   550  				Authority: "foo.biz",
   551  			},
   552  		}, valid: false},
   553  		{name: "request response headers", route: &networking.HTTPRoute{
   554  			Route: []*networking.HTTPRouteDestination{{
   555  				Destination: &networking.Destination{Host: "foo.baz"},
   556  			}},
   557  		}, valid: true},
   558  		{name: "valid headers", route: &networking.HTTPRoute{
   559  			Route: []*networking.HTTPRouteDestination{{
   560  				Destination: &networking.Destination{Host: "foo.baz"},
   561  				Headers: &networking.Headers{
   562  					Request: &networking.Headers_HeaderOperations{
   563  						Add: map[string]string{
   564  							"name": "",
   565  						},
   566  						Set: map[string]string{
   567  							"name": "",
   568  						},
   569  						Remove: []string{
   570  							"name",
   571  						},
   572  					},
   573  					Response: &networking.Headers_HeaderOperations{
   574  						Add: map[string]string{
   575  							"name": "",
   576  						},
   577  						Set: map[string]string{
   578  							"name": "",
   579  						},
   580  						Remove: []string{
   581  							"name",
   582  						},
   583  					},
   584  				},
   585  			}},
   586  		}, valid: true},
   587  		{name: "empty header name - request add", route: &networking.HTTPRoute{
   588  			Route: []*networking.HTTPRouteDestination{{
   589  				Destination: &networking.Destination{Host: "foo.baz"},
   590  				Headers: &networking.Headers{
   591  					Request: &networking.Headers_HeaderOperations{
   592  						Add: map[string]string{
   593  							"": "value",
   594  						},
   595  					},
   596  				},
   597  			}},
   598  		}, valid: false},
   599  		{name: "empty header name - request set", route: &networking.HTTPRoute{
   600  			Route: []*networking.HTTPRouteDestination{{
   601  				Destination: &networking.Destination{Host: "foo.baz"},
   602  				Headers: &networking.Headers{
   603  					Request: &networking.Headers_HeaderOperations{
   604  						Set: map[string]string{
   605  							"": "value",
   606  						},
   607  					},
   608  				},
   609  			}},
   610  		}, valid: false},
   611  		{name: "empty header name - request remove", route: &networking.HTTPRoute{
   612  			Route: []*networking.HTTPRouteDestination{{
   613  				Destination: &networking.Destination{Host: "foo.baz"},
   614  				Headers: &networking.Headers{
   615  					Request: &networking.Headers_HeaderOperations{
   616  						Remove: []string{
   617  							"",
   618  						},
   619  					},
   620  				},
   621  			}},
   622  		}, valid: false},
   623  		{name: "empty header name - response add", route: &networking.HTTPRoute{
   624  			Route: []*networking.HTTPRouteDestination{{
   625  				Destination: &networking.Destination{Host: "foo.baz"},
   626  				Headers: &networking.Headers{
   627  					Response: &networking.Headers_HeaderOperations{
   628  						Add: map[string]string{
   629  							"": "value",
   630  						},
   631  					},
   632  				},
   633  			}},
   634  		}, valid: false},
   635  		{name: "empty header name - response set", route: &networking.HTTPRoute{
   636  			Route: []*networking.HTTPRouteDestination{{
   637  				Destination: &networking.Destination{Host: "foo.baz"},
   638  				Headers: &networking.Headers{
   639  					Response: &networking.Headers_HeaderOperations{
   640  						Set: map[string]string{
   641  							"": "value",
   642  						},
   643  					},
   644  				},
   645  			}},
   646  		}, valid: false},
   647  		{name: "empty header name - response remove", route: &networking.HTTPRoute{
   648  			Route: []*networking.HTTPRouteDestination{{
   649  				Destination: &networking.Destination{Host: "foo.baz"},
   650  				Headers: &networking.Headers{
   651  					Response: &networking.Headers_HeaderOperations{
   652  						Remove: []string{
   653  							"",
   654  						},
   655  					},
   656  				},
   657  			}},
   658  		}, valid: false},
   659  		{name: "null header match", route: &networking.HTTPRoute{
   660  			Route: []*networking.HTTPRouteDestination{{
   661  				Destination: &networking.Destination{Host: "foo.bar"},
   662  			}},
   663  			Match: []*networking.HTTPMatchRequest{{
   664  				Headers: map[string]*networking.StringMatch{
   665  					"header": nil,
   666  				},
   667  			}},
   668  		}, valid: false},
   669  		{name: "nil match", route: &networking.HTTPRoute{
   670  			Route: []*networking.HTTPRouteDestination{{
   671  				Destination: &networking.Destination{Host: "foo.bar"},
   672  			}},
   673  			Match: nil,
   674  		}, valid: true},
   675  		{name: "match with nil element", route: &networking.HTTPRoute{
   676  			Route: []*networking.HTTPRouteDestination{{
   677  				Destination: &networking.Destination{Host: "foo.bar"},
   678  			}},
   679  			Match: []*networking.HTTPMatchRequest{nil},
   680  		}, valid: true},
   681  		{name: "invalid mirror percent", route: &networking.HTTPRoute{
   682  			MirrorPercent: &wrapperspb.UInt32Value{Value: 101},
   683  			Route: []*networking.HTTPRouteDestination{{
   684  				Destination: &networking.Destination{Host: "foo.bar"},
   685  			}},
   686  			Match: []*networking.HTTPMatchRequest{nil},
   687  		}, valid: false},
   688  		{name: "invalid mirror percentage", route: &networking.HTTPRoute{
   689  			MirrorPercentage: &networking.Percent{
   690  				Value: 101,
   691  			},
   692  			Route: []*networking.HTTPRouteDestination{{
   693  				Destination: &networking.Destination{Host: "foo.bar"},
   694  			}},
   695  			Match: []*networking.HTTPMatchRequest{nil},
   696  		}, valid: false},
   697  		{name: "valid mirror percentage", route: &networking.HTTPRoute{
   698  			MirrorPercentage: &networking.Percent{
   699  				Value: 1,
   700  			},
   701  			Route: []*networking.HTTPRouteDestination{{
   702  				Destination: &networking.Destination{Host: "foo.bar"},
   703  			}},
   704  			Match: []*networking.HTTPMatchRequest{nil},
   705  		}, valid: true},
   706  		{name: "negative mirror percentage", route: &networking.HTTPRoute{
   707  			MirrorPercentage: &networking.Percent{
   708  				Value: -1,
   709  			},
   710  			Route: []*networking.HTTPRouteDestination{{
   711  				Destination: &networking.Destination{Host: "foo.bar"},
   712  			}},
   713  			Match: []*networking.HTTPMatchRequest{nil},
   714  		}, valid: false},
   715  		{name: "delegate route with delegate", route: &networking.HTTPRoute{
   716  			Delegate: &networking.Delegate{
   717  				Name:      "test",
   718  				Namespace: "test",
   719  			},
   720  		}, valid: false},
   721  	}
   722  
   723  	for _, tc := range testCases {
   724  		t.Run(tc.name, func(t *testing.T) {
   725  			if err := validateHTTPRoute(tc.route, true, false); (err.Err == nil) != tc.valid {
   726  				t.Fatalf("got valid=%v but wanted valid=%v: %v", err.Err == nil, tc.valid, err)
   727  			}
   728  		})
   729  	}
   730  }