1
18
19 package keys
20
21 import (
22 "fmt"
23 "strings"
24 "testing"
25
26 "github.com/google/go-cmp/cmp"
27 rlspb "google.golang.org/grpc/internal/proto/grpc_lookup_v1"
28 "google.golang.org/grpc/metadata"
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
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
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
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
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
304
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
312
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
320
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
328
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
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
354
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
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 }
651
View as plain text