gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/balancer/rls/internal/keys/builder_test.go (about)

     1  /*
     2   *
     3   * Copyright 2020 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package keys
    20  
    21  import (
    22  	"fmt"
    23  	"strings"
    24  	"testing"
    25  
    26  	rlspb "gitee.com/ks-custle/core-gm/grpc/internal/proto/grpc_lookup_v1"
    27  	"gitee.com/ks-custle/core-gm/grpc/metadata"
    28  	"github.com/google/go-cmp/cmp"
    29  )
    30  
    31  var (
    32  	goodKeyBuilder1 = &rlspb.GrpcKeyBuilder{
    33  		Names: []*rlspb.GrpcKeyBuilder_Name{
    34  			{Service: "gFoo"},
    35  		},
    36  		Headers: []*rlspb.NameMatcher{
    37  			{Key: "k1", Names: []string{"n1"}},
    38  			{Key: "k2", Names: []string{"n1"}},
    39  		},
    40  		ExtraKeys: &rlspb.GrpcKeyBuilder_ExtraKeys{
    41  			Host:    "host",
    42  			Service: "service",
    43  			Method:  "method",
    44  		},
    45  		ConstantKeys: map[string]string{
    46  			"const-key-1": "const-val-1",
    47  			"const-key-2": "const-val-2",
    48  		},
    49  	}
    50  	goodKeyBuilder2 = &rlspb.GrpcKeyBuilder{
    51  		Names: []*rlspb.GrpcKeyBuilder_Name{
    52  			{Service: "gBar", Method: "method1"},
    53  			{Service: "gFoobar"},
    54  		},
    55  		Headers: []*rlspb.NameMatcher{
    56  			{Key: "k1", Names: []string{"n1", "n2"}},
    57  		},
    58  	}
    59  )
    60  
    61  func TestMakeBuilderMap(t *testing.T) {
    62  	gFooBuilder := builder{
    63  		headerKeys: []matcher{{key: "k1", names: []string{"n1"}}, {key: "k2", names: []string{"n1"}}},
    64  		constantKeys: map[string]string{
    65  			"const-key-1": "const-val-1",
    66  			"const-key-2": "const-val-2",
    67  		},
    68  		hostKey:    "host",
    69  		serviceKey: "service",
    70  		methodKey:  "method",
    71  	}
    72  	wantBuilderMap1 := map[string]builder{"/gFoo/": gFooBuilder}
    73  	wantBuilderMap2 := map[string]builder{
    74  		"/gFoo/":        gFooBuilder,
    75  		"/gBar/method1": {headerKeys: []matcher{{key: "k1", names: []string{"n1", "n2"}}}},
    76  		"/gFoobar/":     {headerKeys: []matcher{{key: "k1", names: []string{"n1", "n2"}}}},
    77  	}
    78  
    79  	tests := []struct {
    80  		desc           string
    81  		cfg            *rlspb.RouteLookupConfig
    82  		wantBuilderMap BuilderMap
    83  	}{
    84  		{
    85  			desc: "One good GrpcKeyBuilder",
    86  			cfg: &rlspb.RouteLookupConfig{
    87  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{goodKeyBuilder1},
    88  			},
    89  			wantBuilderMap: wantBuilderMap1,
    90  		},
    91  		{
    92  			desc: "Two good GrpcKeyBuilders",
    93  			cfg: &rlspb.RouteLookupConfig{
    94  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{goodKeyBuilder1, goodKeyBuilder2},
    95  			},
    96  			wantBuilderMap: wantBuilderMap2,
    97  		},
    98  	}
    99  
   100  	for _, test := range tests {
   101  		t.Run(test.desc, func(t *testing.T) {
   102  			builderMap, err := MakeBuilderMap(test.cfg)
   103  			if err != nil || !builderMap.Equal(test.wantBuilderMap) {
   104  				t.Errorf("MakeBuilderMap(%+v) returned {%v, %v}, want: {%v, nil}", test.cfg, builderMap, err, test.wantBuilderMap)
   105  			}
   106  		})
   107  	}
   108  }
   109  
   110  func TestMakeBuilderMapErrors(t *testing.T) {
   111  	tests := []struct {
   112  		desc          string
   113  		cfg           *rlspb.RouteLookupConfig
   114  		wantErrPrefix string
   115  	}{
   116  		{
   117  			desc:          "No GrpcKeyBuilder",
   118  			cfg:           &rlspb.RouteLookupConfig{},
   119  			wantErrPrefix: "rls: RouteLookupConfig does not contain any GrpcKeyBuilder",
   120  		},
   121  		{
   122  			desc: "Two GrpcKeyBuilders with same Name",
   123  			cfg: &rlspb.RouteLookupConfig{
   124  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{goodKeyBuilder1, goodKeyBuilder1},
   125  			},
   126  			wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated Name field",
   127  		},
   128  		{
   129  			desc: "GrpcKeyBuilder with empty Service field",
   130  			cfg: &rlspb.RouteLookupConfig{
   131  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{
   132  					{
   133  						Names: []*rlspb.GrpcKeyBuilder_Name{
   134  							{Service: "bFoo", Method: "method1"},
   135  							{Service: "bBar"},
   136  							{Method: "method1"},
   137  						},
   138  						Headers: []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}},
   139  					},
   140  					goodKeyBuilder1,
   141  				},
   142  			},
   143  			wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains a Name field with no Service",
   144  		},
   145  		{
   146  			desc: "GrpcKeyBuilder with no Name",
   147  			cfg: &rlspb.RouteLookupConfig{
   148  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{{}, goodKeyBuilder1},
   149  			},
   150  			wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig does not contain any Name",
   151  		},
   152  		{
   153  			desc: "GrpcKeyBuilder with requiredMatch field set",
   154  			cfg: &rlspb.RouteLookupConfig{
   155  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{
   156  					{
   157  						Names:   []*rlspb.GrpcKeyBuilder_Name{{Service: "bFoo", Method: "method1"}},
   158  						Headers: []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}, RequiredMatch: true}},
   159  					},
   160  					goodKeyBuilder1,
   161  				},
   162  			},
   163  			wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig has required_match field set",
   164  		},
   165  		{
   166  			desc: "GrpcKeyBuilder two headers with same key",
   167  			cfg: &rlspb.RouteLookupConfig{
   168  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{
   169  					{
   170  						Names: []*rlspb.GrpcKeyBuilder_Name{
   171  							{Service: "gBar", Method: "method1"},
   172  							{Service: "gFoobar"},
   173  						},
   174  						Headers: []*rlspb.NameMatcher{
   175  							{Key: "k1", Names: []string{"n1", "n2"}},
   176  							{Key: "k1", Names: []string{"n1", "n2"}},
   177  						},
   178  					},
   179  					goodKeyBuilder1,
   180  				},
   181  			},
   182  			wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated key \"k1\" across headers, constant_keys and extra_keys",
   183  		},
   184  		{
   185  			desc: "GrpcKeyBuilder repeated keys across headers and constant_keys",
   186  			cfg: &rlspb.RouteLookupConfig{
   187  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{
   188  					{
   189  						Names: []*rlspb.GrpcKeyBuilder_Name{
   190  							{Service: "gBar", Method: "method1"},
   191  							{Service: "gFoobar"},
   192  						},
   193  						Headers:      []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}},
   194  						ConstantKeys: map[string]string{"k1": "v1"},
   195  					},
   196  					goodKeyBuilder1,
   197  				},
   198  			},
   199  			wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated key \"k1\" across headers, constant_keys and extra_keys",
   200  		},
   201  		{
   202  			desc: "GrpcKeyBuilder repeated keys across headers and extra_keys",
   203  			cfg: &rlspb.RouteLookupConfig{
   204  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{
   205  					{
   206  						Names: []*rlspb.GrpcKeyBuilder_Name{
   207  							{Service: "gBar", Method: "method1"},
   208  							{Service: "gFoobar"},
   209  						},
   210  						Headers:   []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}},
   211  						ExtraKeys: &rlspb.GrpcKeyBuilder_ExtraKeys{Method: "k1"},
   212  					},
   213  					goodKeyBuilder1,
   214  				},
   215  			},
   216  			wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated key \"k1\" in extra_keys from constant_keys or headers",
   217  		},
   218  		{
   219  			desc: "GrpcKeyBuilder repeated keys across constant_keys and extra_keys",
   220  			cfg: &rlspb.RouteLookupConfig{
   221  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{
   222  					{
   223  						Names: []*rlspb.GrpcKeyBuilder_Name{
   224  							{Service: "gBar", Method: "method1"},
   225  							{Service: "gFoobar"},
   226  						},
   227  						Headers:      []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}},
   228  						ConstantKeys: map[string]string{"host": "v1"},
   229  						ExtraKeys:    &rlspb.GrpcKeyBuilder_ExtraKeys{Host: "host"},
   230  					},
   231  					goodKeyBuilder1,
   232  				},
   233  			},
   234  			wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated key \"host\" in extra_keys from constant_keys or headers",
   235  		},
   236  		{
   237  			desc: "GrpcKeyBuilder with slash in method name",
   238  			cfg: &rlspb.RouteLookupConfig{
   239  				GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{
   240  					{
   241  						Names:   []*rlspb.GrpcKeyBuilder_Name{{Service: "gBar", Method: "method1/foo"}},
   242  						Headers: []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}},
   243  					},
   244  				},
   245  			},
   246  			wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains a method with a slash",
   247  		},
   248  	}
   249  
   250  	for _, test := range tests {
   251  		t.Run(test.desc, func(t *testing.T) {
   252  			builderMap, err := MakeBuilderMap(test.cfg)
   253  			if builderMap != nil || !strings.HasPrefix(fmt.Sprint(err), test.wantErrPrefix) {
   254  				t.Errorf("MakeBuilderMap(%+v) returned {%v, %v}, want: {nil, %v}", test.cfg, builderMap, err, test.wantErrPrefix)
   255  			}
   256  		})
   257  	}
   258  }
   259  
   260  func TestRLSKey(t *testing.T) {
   261  	bm, err := MakeBuilderMap(&rlspb.RouteLookupConfig{
   262  		GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{goodKeyBuilder1, goodKeyBuilder2},
   263  	})
   264  	if err != nil {
   265  		t.Fatalf("MakeBuilderMap() failed: %v", err)
   266  	}
   267  
   268  	tests := []struct {
   269  		desc   string
   270  		path   string
   271  		md     metadata.MD
   272  		wantKM KeyMap
   273  	}{
   274  		{
   275  			// No keyBuilder is found for the provided service.
   276  			desc:   "service not found in key builder map",
   277  			path:   "/notFoundService/method",
   278  			md:     metadata.Pairs("n1", "v1", "n2", "v2"),
   279  			wantKM: KeyMap{},
   280  		},
   281  		{
   282  			// No keyBuilder is found for the provided method.
   283  			desc:   "method not found in key builder map",
   284  			path:   "/gBar/notFoundMethod",
   285  			md:     metadata.Pairs("n1", "v1", "n2", "v2"),
   286  			wantKM: KeyMap{},
   287  		},
   288  		{
   289  			// A keyBuilder is found, but none of the headers match.
   290  			desc:   "directPathMatch-NoMatchingKey",
   291  			path:   "/gBar/method1",
   292  			md:     metadata.Pairs("notMatchingKey", "v1"),
   293  			wantKM: KeyMap{Map: map[string]string{}, Str: ""},
   294  		},
   295  		{
   296  			// A keyBuilder is found, and a single headers matches.
   297  			desc:   "directPathMatch-SingleKey",
   298  			path:   "/gBar/method1",
   299  			md:     metadata.Pairs("n1", "v1"),
   300  			wantKM: KeyMap{Map: map[string]string{"k1": "v1"}, Str: "k1=v1"},
   301  		},
   302  		{
   303  			// A keyBuilder is found, and multiple headers match, but the first
   304  			// match is chosen.
   305  			desc:   "directPathMatch-FirstMatchingKey",
   306  			path:   "/gBar/method1",
   307  			md:     metadata.Pairs("n2", "v2", "n1", "v1"),
   308  			wantKM: KeyMap{Map: map[string]string{"k1": "v1"}, Str: "k1=v1"},
   309  		},
   310  		{
   311  			// A keyBuilder is found as a wildcard match, but none of the
   312  			// headers match.
   313  			desc:   "wildcardPathMatch-NoMatchingKey",
   314  			path:   "/gFoobar/method1",
   315  			md:     metadata.Pairs("notMatchingKey", "v1"),
   316  			wantKM: KeyMap{Map: map[string]string{}, Str: ""},
   317  		},
   318  		{
   319  			// A keyBuilder is found as a wildcard match, and a single headers
   320  			// matches.
   321  			desc:   "wildcardPathMatch-SingleKey",
   322  			path:   "/gFoobar/method1",
   323  			md:     metadata.Pairs("n1", "v1"),
   324  			wantKM: KeyMap{Map: map[string]string{"k1": "v1"}, Str: "k1=v1"},
   325  		},
   326  		{
   327  			// A keyBuilder is found as a wildcard match, and multiple headers
   328  			// match, but the first match is chosen.
   329  			desc:   "wildcardPathMatch-FirstMatchingKey",
   330  			path:   "/gFoobar/method1",
   331  			md:     metadata.Pairs("n2", "v2", "n1", "v1"),
   332  			wantKM: KeyMap{Map: map[string]string{"k1": "v1"}, Str: "k1=v1"},
   333  		},
   334  		{
   335  			// Multiple headerKeys find hits in the provided request headers.
   336  			desc: "multipleMatchers",
   337  			path: "/gFoo/method1",
   338  			md:   metadata.Pairs("n2", "v2", "n1", "v1"),
   339  			wantKM: KeyMap{
   340  				Map: map[string]string{
   341  					"const-key-1": "const-val-1",
   342  					"const-key-2": "const-val-2",
   343  					"host":        "dummy-host",
   344  					"service":     "/gFoo/",
   345  					"method":      "method1",
   346  					"k1":          "v1",
   347  					"k2":          "v1",
   348  				},
   349  				Str: "const-key-1=const-val-1,const-key-2=const-val-2,host=dummy-host,k1=v1,k2=v1,method=method1,service=/gFoo/",
   350  			},
   351  		},
   352  		{
   353  			// A match is found for a header which is specified multiple times.
   354  			// So, the values are joined with commas separating them.
   355  			desc:   "commaSeparated",
   356  			path:   "/gBar/method1",
   357  			md:     metadata.Pairs("n1", "v1", "n1", "v2", "n1", "v3"),
   358  			wantKM: KeyMap{Map: map[string]string{"k1": "v1,v2,v3"}, Str: "k1=v1,v2,v3"},
   359  		},
   360  	}
   361  
   362  	for _, test := range tests {
   363  		t.Run(test.desc, func(t *testing.T) {
   364  			if gotKM := bm.RLSKey(test.md, "dummy-host", test.path); !cmp.Equal(gotKM, test.wantKM) {
   365  				t.Errorf("RLSKey(%+v, %s) = %+v, want %+v", test.md, test.path, gotKM, test.wantKM)
   366  			}
   367  		})
   368  	}
   369  }
   370  
   371  func TestMapToString(t *testing.T) {
   372  	tests := []struct {
   373  		desc    string
   374  		input   map[string]string
   375  		wantStr string
   376  	}{
   377  		{
   378  			desc:    "empty map",
   379  			input:   nil,
   380  			wantStr: "",
   381  		},
   382  		{
   383  			desc: "one key",
   384  			input: map[string]string{
   385  				"k1": "v1",
   386  			},
   387  			wantStr: "k1=v1",
   388  		},
   389  		{
   390  			desc: "sorted keys",
   391  			input: map[string]string{
   392  				"k1": "v1",
   393  				"k2": "v2",
   394  				"k3": "v3",
   395  			},
   396  			wantStr: "k1=v1,k2=v2,k3=v3",
   397  		},
   398  		{
   399  			desc: "unsorted keys",
   400  			input: map[string]string{
   401  				"k3": "v3",
   402  				"k1": "v1",
   403  				"k2": "v2",
   404  			},
   405  			wantStr: "k1=v1,k2=v2,k3=v3",
   406  		},
   407  	}
   408  
   409  	for _, test := range tests {
   410  		t.Run(test.desc, func(t *testing.T) {
   411  			if gotStr := mapToString(test.input); gotStr != test.wantStr {
   412  				t.Errorf("mapToString(%v) = %s, want %s", test.input, gotStr, test.wantStr)
   413  			}
   414  		})
   415  	}
   416  }
   417  
   418  func TestBuilderMapEqual(t *testing.T) {
   419  	tests := []struct {
   420  		desc      string
   421  		a         BuilderMap
   422  		b         BuilderMap
   423  		wantEqual bool
   424  	}{
   425  		{
   426  			desc:      "nil builder maps",
   427  			a:         nil,
   428  			b:         nil,
   429  			wantEqual: true,
   430  		},
   431  		{
   432  			desc:      "empty builder maps",
   433  			a:         make(map[string]builder),
   434  			b:         make(map[string]builder),
   435  			wantEqual: true,
   436  		},
   437  		{
   438  			desc:      "nil and non-nil builder maps",
   439  			a:         nil,
   440  			b:         map[string]builder{"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}},
   441  			wantEqual: false,
   442  		},
   443  		{
   444  			desc:      "empty and non-empty builder maps",
   445  			a:         make(map[string]builder),
   446  			b:         map[string]builder{"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}},
   447  			wantEqual: false,
   448  		},
   449  		{
   450  			desc: "different number of map keys",
   451  			a: map[string]builder{
   452  				"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   453  				"/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   454  			},
   455  			b: map[string]builder{
   456  				"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   457  			},
   458  			wantEqual: false,
   459  		},
   460  		{
   461  			desc: "different map keys",
   462  			a: map[string]builder{
   463  				"/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   464  			},
   465  			b: map[string]builder{
   466  				"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   467  			},
   468  			wantEqual: false,
   469  		},
   470  		{
   471  			desc: "equal keys different values",
   472  			a: map[string]builder{
   473  				"/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   474  				"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1", "n2"}}}},
   475  			},
   476  			b: map[string]builder{
   477  				"/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   478  				"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   479  			},
   480  			wantEqual: false,
   481  		},
   482  		{
   483  			desc: "good match",
   484  			a: map[string]builder{
   485  				"/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   486  				"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   487  			},
   488  			b: map[string]builder{
   489  				"/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   490  				"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}},
   491  			},
   492  			wantEqual: true,
   493  		},
   494  	}
   495  
   496  	for _, test := range tests {
   497  		t.Run(test.desc, func(t *testing.T) {
   498  			if gotEqual := test.a.Equal(test.b); gotEqual != test.wantEqual {
   499  				t.Errorf("BuilderMap.Equal(%v, %v) = %v, want %v", test.a, test.b, gotEqual, test.wantEqual)
   500  			}
   501  		})
   502  	}
   503  }
   504  
   505  func TestBuilderEqual(t *testing.T) {
   506  	tests := []struct {
   507  		desc      string
   508  		a         builder
   509  		b         builder
   510  		wantEqual bool
   511  	}{
   512  		{
   513  			desc:      "nil builders",
   514  			a:         builder{headerKeys: nil},
   515  			b:         builder{headerKeys: nil},
   516  			wantEqual: true,
   517  		},
   518  		{
   519  			desc:      "empty builders",
   520  			a:         builder{headerKeys: []matcher{}},
   521  			b:         builder{headerKeys: []matcher{}},
   522  			wantEqual: true,
   523  		},
   524  		{
   525  			desc:      "empty and non-empty builders",
   526  			a:         builder{headerKeys: []matcher{}},
   527  			b:         builder{headerKeys: []matcher{{key: "foo"}}},
   528  			wantEqual: false,
   529  		},
   530  		{
   531  			desc:      "different number of headerKeys",
   532  			a:         builder{headerKeys: []matcher{{key: "foo"}, {key: "bar"}}},
   533  			b:         builder{headerKeys: []matcher{{key: "foo"}}},
   534  			wantEqual: false,
   535  		},
   536  		{
   537  			desc:      "equal number but differing headerKeys",
   538  			a:         builder{headerKeys: []matcher{{key: "bar"}}},
   539  			b:         builder{headerKeys: []matcher{{key: "foo"}}},
   540  			wantEqual: false,
   541  		},
   542  		{
   543  			desc:      "different number of constantKeys",
   544  			a:         builder{constantKeys: map[string]string{"k1": "v1"}},
   545  			b:         builder{constantKeys: map[string]string{"k1": "v1", "k2": "v2"}},
   546  			wantEqual: false,
   547  		},
   548  		{
   549  			desc:      "equal number but differing constantKeys",
   550  			a:         builder{constantKeys: map[string]string{"k1": "v1"}},
   551  			b:         builder{constantKeys: map[string]string{"k2": "v2"}},
   552  			wantEqual: false,
   553  		},
   554  		{
   555  			desc:      "different hostKey",
   556  			a:         builder{hostKey: "host1"},
   557  			b:         builder{hostKey: "host2"},
   558  			wantEqual: false,
   559  		},
   560  		{
   561  			desc:      "different serviceKey",
   562  			a:         builder{hostKey: "service1"},
   563  			b:         builder{hostKey: "service2"},
   564  			wantEqual: false,
   565  		},
   566  		{
   567  			desc:      "different methodKey",
   568  			a:         builder{hostKey: "method1"},
   569  			b:         builder{hostKey: "method2"},
   570  			wantEqual: false,
   571  		},
   572  		{
   573  			desc: "equal",
   574  			a: builder{
   575  				headerKeys:   []matcher{{key: "foo"}},
   576  				constantKeys: map[string]string{"k1": "v1"},
   577  				hostKey:      "host",
   578  				serviceKey:   "/service/",
   579  				methodKey:    "method",
   580  			},
   581  			b: builder{
   582  				headerKeys:   []matcher{{key: "foo"}},
   583  				constantKeys: map[string]string{"k1": "v1"},
   584  				hostKey:      "host",
   585  				serviceKey:   "/service/",
   586  				methodKey:    "method",
   587  			},
   588  			wantEqual: true,
   589  		},
   590  	}
   591  
   592  	for _, test := range tests {
   593  		t.Run(test.desc, func(t *testing.T) {
   594  			t.Run(test.desc, func(t *testing.T) {
   595  				if gotEqual := test.a.Equal(test.b); gotEqual != test.wantEqual {
   596  					t.Errorf("builder.Equal(%v, %v) = %v, want %v", test.a, test.b, gotEqual, test.wantEqual)
   597  				}
   598  			})
   599  		})
   600  	}
   601  }
   602  
   603  // matcher helps extract a key from request headers based on a given name.
   604  func TestMatcherEqual(t *testing.T) {
   605  	tests := []struct {
   606  		desc      string
   607  		a         matcher
   608  		b         matcher
   609  		wantEqual bool
   610  	}{
   611  		{
   612  			desc:      "different keys",
   613  			a:         matcher{key: "foo"},
   614  			b:         matcher{key: "bar"},
   615  			wantEqual: false,
   616  		},
   617  		{
   618  			desc:      "different number of names",
   619  			a:         matcher{key: "foo", names: []string{"v1", "v2"}},
   620  			b:         matcher{key: "foo", names: []string{"v1"}},
   621  			wantEqual: false,
   622  		},
   623  		{
   624  			desc:      "equal number but differing names",
   625  			a:         matcher{key: "foo", names: []string{"v1", "v2"}},
   626  			b:         matcher{key: "foo", names: []string{"v1", "v22"}},
   627  			wantEqual: false,
   628  		},
   629  		{
   630  			desc:      "same names in different order",
   631  			a:         matcher{key: "foo", names: []string{"v2", "v1"}},
   632  			b:         matcher{key: "foo", names: []string{"v1", "v3"}},
   633  			wantEqual: false,
   634  		},
   635  		{
   636  			desc:      "good match",
   637  			a:         matcher{key: "foo", names: []string{"v1", "v2"}},
   638  			b:         matcher{key: "foo", names: []string{"v1", "v2"}},
   639  			wantEqual: true,
   640  		},
   641  	}
   642  
   643  	for _, test := range tests {
   644  		t.Run(test.desc, func(t *testing.T) {
   645  			if gotEqual := test.a.Equal(test.b); gotEqual != test.wantEqual {
   646  				t.Errorf("matcher.Equal(%v, %v) = %v, want %v", test.a, test.b, gotEqual, test.wantEqual)
   647  			}
   648  		})
   649  	}
   650  }