kubelatte-ce
Форк от sbertech/kubelatte-ce
882 строки · 19.9 Кб
1package templates
2
3import (
4"context"
5"github.com/gohobby/deepcopy"
6"github.com/stretchr/testify/assert"
7"github.com/stretchr/testify/require"
8"gitverse.ru/ktrntrsv/kubelatte-ce/pkg/api/v1alpha1"
9"gitverse.ru/ktrntrsv/kubelatte-ce/pkg/observability/logger/lib"
10"go.uber.org/zap"
11"reflect"
12"testing"
13"text/template"
14)
15
16func TestFromYAML(t *testing.T) {
17tests := []struct {
18name string
19yaml string
20want interface{}
21wantErr bool
22}{
23{
24"invalid yaml",
25"'",
26nil,
27true,
28},
29{
30"empty object",
31"{}",
32map[string]interface{}{},
33false,
34},
35{
36"raw string",
37"hello world",
38"hello world",
39false,
40},
41{
42"array of strings",
43`["hello", "goodbye"]`,
44[]interface{}{"hello", "goodbye"},
45false,
46},
47{
48"array of objects",
49`["hello", {}]`,
50[]interface{}{"hello", map[string]interface{}{}},
51false,
52},
53{
54"top-level object",
55`age: "23"
56hello: bar`,
57map[string]interface{}{"age": "23", "hello": "bar"},
58false,
59},
60}
61for _, tt := range tests {
62t.Run(tt.name, func(t *testing.T) {
63got, err := FromYAML(tt.yaml)
64if tt.wantErr {
65require.Error(t, err, "Expected error unmarshalling yaml string")
66return
67}
68
69require.NoError(t, err, "Unexpected error unmarshalling yaml string")
70require.Equal(t, tt.want, got)
71})
72}
73}
74
75func TestToYAML(t *testing.T) {
76type args struct {
77resource interface{}
78}
79tests := []struct {
80name string
81args args
82want string
83wantErr bool
84}{
85{
86name: "ok",
87args: args{resource: map[string]interface{}{"bobob": 123, "bubu": map[string]interface{}{"lolo": "pufpuf"}}},
88want: `bobob: 123
89bubu:
90lolo: pufpuf
91`,
92wantErr: false,
93},
94{
95name: "empty",
96args: args{},
97want: "",
98wantErr: false,
99},
100}
101
102lib.ZapLogger = zap.NewNop()
103
104for _, tt := range tests {
105t.Run(tt.name, func(t *testing.T) {
106got, err := ToYAML(tt.args.resource)
107if (err != nil) != tt.wantErr {
108t.Errorf("ToYAML() error = %v, wantErr %v", err, tt.wantErr)
109return
110}
111if got != tt.want {
112t.Errorf("ToYAML() got = %v, \nwant %v", []byte(got), []byte(tt.want))
113}
114})
115}
116}
117
118func TestNindent(t *testing.T) {
119type args struct {
120in string
121spacesNum int
122}
123tests := []struct {
124name string
125args args
126want string
127}{
128{
129name: "simple",
130args: args{
131in: `some:
132yaml:
133simple:
134string`,
135spacesNum: 2,
136},
137want: `
138some:
139yaml:
140simple:
141string
142`,
143},
144{
145name: "not-so-simple",
146args: args{
147in: `some:
148yaml:
149not-so-simple:
150- strings
151- strings`,
152spacesNum: 0,
153},
154want: `
155some:
156yaml:
157not-so-simple:
158- strings
159- strings
160`,
161},
162{
163name: "empty",
164args: args{
165in: "",
166spacesNum: 0,
167},
168want: "\n\n",
169},
170}
171for _, tt := range tests {
172t.Run(tt.name, func(t *testing.T) {
173if got := Nindent(tt.args.in, tt.args.spacesNum); got != tt.want {
174t.Errorf("Nindent() = %v, want %v", got, tt.want)
175}
176})
177}
178}
179
180func TestIndent(t *testing.T) {
181type args struct {
182in string
183spacesNum int
184}
185tests := []struct {
186name string
187args args
188want string
189}{
190{
191name: "simple",
192args: args{
193in: `some:
194yaml:
195simple:
196string`,
197spacesNum: 2,
198},
199want: `some:
200yaml:
201simple:
202string
203`,
204},
205{
206name: "not-so-simple",
207args: args{
208in: `some:
209yaml:
210not-so-simple:
211- strings
212- strings`,
213spacesNum: 0,
214},
215want: `some:
216yaml:
217not-so-simple:
218- strings
219- strings
220`,
221},
222{
223name: "empty",
224args: args{
225in: "",
226spacesNum: 0,
227},
228want: "\n",
229},
230}
231for _, tt := range tests {
232t.Run(tt.name, func(t *testing.T) {
233if got := Indent(tt.args.in, tt.args.spacesNum); got != tt.want {
234t.Errorf("Indent() = \n%v, want \n%v", got, tt.want)
235}
236})
237}
238}
239
240func TestLoadTemplate(t *testing.T) {
241t.Run("test", func(t *testing.T) {
242expected := template.New("common").Delims("{{%", "%}}")
243got := LoadTemplate(context.Background(), v1alpha1.Template{})
244if got.Name() != expected.Name() {
245t.Errorf("LoadTemplate() = %+v, want %+v", got, expected)
246}
247})
248}
249
250func TestConfigPatchesRBACToV3(t *testing.T) { //nolint:funlen
251type args struct {
252in interface{}
253}
254tests := []struct {
255name string
256args args
257want interface{}
258wantErr bool
259}{
260{
261name: "ok",
262args: args{
263in: map[string]interface{}{
264"match": map[string]interface{}{
265"listener": map[string]interface{}{
266"filterChain": map[string]interface{}{
267"filter": map[string]interface{}{
268"name": "envoy.filters.network.rbac"},
269},
270},
271},
272"patch": map[string]interface{}{
273"value": map[string]interface{}{
274"config": map[string]interface{}{
275"rules": map[string]interface{}{
276"policies": map[string]interface{}{
277"validate": map[string]interface{}{
278"permissions": map[string]interface{}{
279"or_rules": map[string]interface{}{
280"rules": []interface{}{map[string]interface{}{
281"header": map[string]interface{}{
282"name": ":method",
283},
284}},
285},
286},
287"principals": map[string]interface{}{
288"or_ids": map[string]interface{}{
289"ids": []interface{}{map[string]interface{}{
290"header": map[string]interface{}{
291"name": ":x-forwarded-client-cert",
292},
293},
294},
295},
296},
297},
298},
299},
300},
301},
302},
303},
304},
305want: map[string]interface{}{
306"match": map[string]interface{}{
307"listener": map[string]interface{}{
308"filterChain": map[string]interface{}{
309"filter": map[string]interface{}{
310"name": "envoy.filters.network.http_connection_manager",
311},
312},
313},
314},
315"patch": map[string]interface{}{
316"value": map[string]interface{}{
317"typed_config": map[string]interface{}{
318"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
319"rules": map[string]interface{}{
320"policies": map[string]interface{}{
321"validate": map[string]interface{}{
322"permissions": []map[string]interface{}{
323{"or_rules": map[string]interface{}{
324"rules": []interface{}{map[string]interface{}{
325"header": map[string]interface{}{
326"name": ":method",
327},
328},
329},
330},
331},
332},
333"principals": []map[string]interface{}{
334{"or_ids": map[string]interface{}{
335"ids": []interface{}{map[string]interface{}{
336"header": map[string]interface{}{
337"name": ":x-forwarded-client-cert",
338},
339},
340},
341},
342},
343},
344},
345},
346},
347},
348},
349},
350},
351
352wantErr: false,
353},
354{
355name: "!ok",
356args: args{
357in: map[string]interface{}{
358"match": map[string]interface{}{},
359},
360},
361want: map[string]interface{}{
362"match": map[string]interface{}{},
363},
364wantErr: false,
365},
366}
367for _, tt := range tests {
368t.Run(tt.name, func(t *testing.T) {
369got, err := ConfigPatchesRBACToV3(tt.args.in)
370if (err != nil) != tt.wantErr {
371t.Errorf("ConfigPatchesRBACToV3() error = %v, wantErr %v", err, tt.wantErr)
372return
373}
374res, _ := ToYAML(tt.want)
375if !reflect.DeepEqual(got, res) {
376t.Errorf("ConfigPatchesRBACToV3() got = %v, want %v", got, tt.want)
377}
378})
379}
380}
381
382func TestOTTconfigPatchesToV3(t *testing.T) { //nolint:funlen
383type args struct {
384in interface{}
385}
386tests := []struct {
387name string
388args args
389want interface{}
390wantErr bool
391}{
392{
393name: "sbt_authz",
394args: args{
395in: map[string]interface{}{
396"match": map[string]interface{}{
397"listener": map[string]interface{}{
398"filterChain": map[string]interface{}{
399"filter": map[string]interface{}{
400"name": "envoy.http_connection_manager"},
401},
402},
403},
404"patch": map[string]interface{}{
405"value": map[string]interface{}{
406"config": map[string]interface{}{
407"grpc_service": map[string]interface{}{
408"google_grpc": map[string]interface{}{
409"stat_prefix": "sbt_authz",
410},
411},
412},
413},
414},
415},
416},
417want: map[string]interface{}{
418"match": map[string]interface{}{
419"listener": map[string]interface{}{
420"filterChain": map[string]interface{}{
421"filter": map[string]interface{}{
422"name": "envoy.filters.network.http_connection_manager"},
423},
424},
425},
426"patch": map[string]interface{}{
427"value": map[string]interface{}{
428"typed_config": map[string]interface{}{
429"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
430"typeUrl": "type.googleapis.com/envoy.extensions.filters.http.sbt_authz.v3.SbtAuthz",
431"value": map[string]interface{}{
432"api": "STANDART",
433"grpc_service": map[string]interface{}{
434"google_grpc": map[string]interface{}{"stat_prefix": "sbt_authz"},
435},
436"transport_api_version": "V3",
437},
438},
439},
440},
441},
442wantErr: false,
443},
444{
445name: "ext_authz",
446args: args{
447in: map[string]interface{}{
448"match": map[string]interface{}{
449"listener": map[string]interface{}{
450"filterChain": map[string]interface{}{
451"filter": map[string]interface{}{
452"name": "envoy.http_connection_manager"},
453},
454},
455},
456"patch": map[string]interface{}{
457"value": map[string]interface{}{
458"config": map[string]interface{}{
459"grpc_service": map[string]interface{}{
460"google_grpc": map[string]interface{}{
461"stat_prefix": "ext_authz",
462},
463},
464},
465},
466},
467},
468},
469want: map[string]interface{}{
470"match": map[string]interface{}{
471"listener": map[string]interface{}{
472"filterChain": map[string]interface{}{
473"filter": map[string]interface{}{
474"name": "envoy.filters.network.http_connection_manager"},
475},
476},
477},
478"patch": map[string]interface{}{
479"value": map[string]interface{}{
480"typed_config": map[string]interface{}{
481"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz",
482"grpc_service": map[string]interface{}{
483"google_grpc": map[string]interface{}{"stat_prefix": "ext_authz"},
484},
485"transport_api_version": "V3",
486},
487},
488},
489},
490wantErr: false,
491},
492}
493for _, tt := range tests {
494t.Run(tt.name, func(t *testing.T) {
495got, err := OTTconfigPatchesToV3(tt.args.in)
496if (err != nil) != tt.wantErr {
497t.Errorf("OTTconfigPatchesToV3() error = %v, wantErr %v", err, tt.wantErr)
498return
499}
500res, _ := ToYAML(tt.want)
501if !reflect.DeepEqual(got, res) {
502t.Errorf("OTTconfigPatchesToV3() got = %v, want %v", got, res)
503}
504})
505}
506}
507
508func TestReplaceLimiterTypedConfig(t *testing.T) {
509tests := []struct {
510name string
511input interface{}
512wantErr bool
513}{
514{
515name: "valid input",
516input: map[string]interface{}{
517"patch": map[string]interface{}{
518"value": map[string]interface{}{
519"config": map[string]interface{}{
520"rate_limit_service": map[string]interface{}{
521"transport_api_version": "V2",
522},
523},
524},
525},
526},
527wantErr: false,
528},
529{
530name: "invalid input",
531input: "invalid",
532wantErr: true,
533},
534}
535
536for _, tt := range tests {
537t.Run(tt.name, func(t *testing.T) {
538_, err := replaceLimiterTypedConfig(tt.input)
539if (err != nil) != tt.wantErr {
540t.Errorf("replaceLimiterTypedConfig() error = %v, wantErr %v", err, tt.wantErr)
541return
542}
543})
544}
545}
546
547func TestRateLimiterPatchesToV3(t *testing.T) {
548tests := []struct {
549name string
550input interface{}
551wantErr bool
552}{
553{
554name: "successful replacements",
555input: map[string]interface{}{
556"patch": "value",
557},
558wantErr: false,
559},
560{
561name: "error in replaceFilterChainName",
562input: "invalid",
563wantErr: false,
564},
565{
566name: "error in replaceLimiterTypedConfig",
567input: map[string]interface{}{
568"patch": "value",
569},
570wantErr: false,
571},
572}
573
574for _, tt := range tests {
575t.Run(tt.name, func(t *testing.T) {
576_, err := RateLimiterPatchesToV3(tt.input)
577if (err != nil) != tt.wantErr {
578t.Errorf("RateLimiterPatchesToV3() error = %v, wantErr %v", err, tt.wantErr)
579return
580}
581})
582}
583}
584
585func TestReplaceKey(t *testing.T) { //nolint:funlen
586type args struct {
587oldK string
588newK string
589in any
590}
591tests := []struct {
592name string
593args args
594want any
595}{
596{
597name: "replace flat",
598args: args{
599oldK: "port",
600newK: "hostport",
601in: map[string]any{
602"port": map[string]any{"number": 8979},
603"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
604"outlierDetection": map[string]any{
605"baseEjectionTime": "30s",
606"maxEjectionPercent": "10",
607"interval": "40s"},
608},
609},
610want: map[string]any{
611"hostport": map[string]any{"number": 8979},
612"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
613"outlierDetection": map[string]any{
614"baseEjectionTime": "30s",
615"maxEjectionPercent": "10",
616"interval": "40s"},
617},
618},
619{
620name: "replace with path",
621args: args{
622oldK: "outlierDetection.maxEjectionPercent",
623newK: "consecutive5xxErrors",
624in: map[string]any{
625"port": map[string]any{"number": 8979},
626"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
627"outlierDetection": map[string]any{
628"baseEjectionTime": "30s",
629"maxEjectionPercent": "10",
630"interval": "40s"},
631},
632},
633want: map[string]any{
634"port": map[string]any{"number": 8979},
635"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
636"outlierDetection": map[string]any{
637"baseEjectionTime": "30s",
638"consecutive5xxErrors": "10",
639"interval": "40s"},
640},
641},
642{ //nolint:dupl
643name: "replace with loong path",
644args: args{
645oldK: "long.long.looong.path.outlierDetection.baseEjectionTime",
646newK: "BamBamBum",
647in: map[string]any{
648"long": map[string]any{
649"long": map[string]any{
650"looong": map[string]any{
651"path": map[string]any{
652"port": map[string]any{"number": 8979},
653"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
654"outlierDetection": map[string]any{
655"baseEjectionTime": "30s",
656"maxEjectionPercent": "10",
657"interval": "40s"}},
658},
659"nil": nil,
660},
661"some other": "other",
662"some another": "another",
663},
664"some other": "other",
665},
666},
667want: map[string]any{
668"long": map[string]any{
669"long": map[string]any{
670"looong": map[string]any{
671"path": map[string]any{
672"port": map[string]any{"number": 8979},
673"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
674"outlierDetection": map[string]any{
675"BamBamBum": "30s",
676"maxEjectionPercent": "10",
677"interval": "40s"}},
678},
679"nil": nil,
680},
681"some other": "other",
682"some another": "another",
683},
684"some other": "other",
685},
686},
687{ //nolint:dupl
688name: "replace with loong path in the middle",
689args: args{
690oldK: "long.long.looong.path.outlierDetection",
691newK: "BamBamBum",
692in: map[string]any{
693"long": map[string]any{
694"long": map[string]any{
695"looong": map[string]any{
696"path": map[string]any{
697"port": map[string]any{"number": 8979},
698"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
699"outlierDetection": map[string]any{
700"baseEjectionTime": "30s",
701"maxEjectionPercent": "10",
702"interval": "40s"}},
703},
704"nil": nil,
705},
706"some other": "other",
707"some another": "another",
708},
709"some other": "other",
710},
711},
712want: map[string]any{
713"long": map[string]any{
714"long": map[string]any{
715"looong": map[string]any{
716"path": map[string]any{
717"port": map[string]any{"number": 8979},
718"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
719"BamBamBum": map[string]any{
720"baseEjectionTime": "30s",
721"maxEjectionPercent": "10",
722"interval": "40s"}},
723},
724"nil": nil,
725},
726"some other": "other",
727"some another": "another",
728},
729"some other": "other",
730},
731},
732{ //nolint:dupl
733name: "replace with loong path in the middle 2",
734args: args{
735oldK: "long.long",
736newK: "longlonglong",
737in: map[string]any{
738"long": map[string]any{
739"long": map[string]any{
740"looong": map[string]any{
741"path": map[string]any{
742"port": map[string]any{"number": 8979},
743"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
744"outlierDetection": map[string]any{
745"baseEjectionTime": "30s",
746"maxEjectionPercent": "10",
747"interval": "40s"}},
748},
749"nil": nil,
750},
751"some other": "other",
752"some another": "another",
753},
754"some other": "other",
755},
756},
757want: map[string]any{
758"long": map[string]any{
759"longlonglong": map[string]any{
760"looong": map[string]any{
761"path": map[string]any{
762"port": map[string]any{"number": 8979},
763"tls": map[string]any{"mode": "ISTIO_MUTUAL", "sni": "lallala"},
764"outlierDetection": map[string]any{
765"baseEjectionTime": "30s",
766"maxEjectionPercent": "10",
767"interval": "40s"}},
768},
769"nil": nil,
770},
771"some other": "other",
772"some another": "another",
773},
774"some other": "other",
775},
776},
777}
778lib.ZapLogger = zap.NewNop()
779for _, tt := range tests {
780t.Run(tt.name, func(t *testing.T) {
781copyin := deepcopy.DeepCopy(tt.args.in)
782if got := ReplaceKey(tt.args.oldK, tt.args.newK, copyin); !reflect.DeepEqual(got, tt.want) {
783assert.Equal(t, tt.args.in, copyin)
784t.Errorf("ReplaceKey() = %v, want %v", got, tt.want)
785}
786})
787}
788}
789
790func TestDel(t *testing.T) {
791type args struct {
792path string
793obj any
794}
795tests := []struct {
796name string
797args args
798want any
799}{
800{
801name: "simple del",
802args: args{
803path: "key2",
804obj: map[string]interface{}{
805"key1": "val1",
806"key2": "val2",
807},
808},
809want: map[string]interface{}{
810"key1": "val1",
811},
812},
813{
814name: "compound del",
815args: args{
816path: "key3.key3_1",
817obj: map[string]interface{}{
818"key1": "val1",
819"key2": "val2",
820"key3": map[string]interface{}{
821"key3_1": "val3_1",
822"key3_2": "val3_2",
823},
824},
825},
826want: map[string]interface{}{
827"key1": "val1",
828"key2": "val2",
829"key3": map[string]interface{}{
830"key3_2": "val3_2",
831},
832},
833},
834{
835name: "compound del 2 lvl",
836args: args{
837path: "key3.key3_2.key3_2_2.key3_2_2_1",
838obj: map[string]interface{}{
839"key1": "val1",
840"key2": "val2",
841"key3": map[string]interface{}{
842"key3_1": "val3_1",
843"key3_2": map[string]interface{}{
844"key3_2_1": "val3_2_1",
845"key3_2_2": map[string]interface{}{
846"key3_2_2_1": "val3_2_2_1",
847"key3_2_2_2": "val3_2_2_2",
848},
849},
850},
851},
852},
853want: map[string]interface{}{
854"key1": "val1",
855"key2": "val2",
856"key3": map[string]interface{}{
857"key3_1": "val3_1",
858"key3_2": map[string]interface{}{
859"key3_2_1": "val3_2_1",
860"key3_2_2": map[string]interface{}{
861"key3_2_2_2": "val3_2_2_2",
862},
863},
864},
865},
866},
867{
868name: "not a map",
869args: args{
870path: "",
871obj: "shine bright like a diamond",
872},
873want: "shine bright like a diamond",
874},
875}
876lib.ZapLogger = zap.NewNop()
877for _, tt := range tests {
878t.Run(tt.name, func(t *testing.T) {
879assert.Equalf(t, tt.want, Del(tt.args.path, tt.args.obj), "Del(%v, %v)", tt.args.path, tt.args.obj)
880})
881}
882}
883