359 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			359 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2022 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package v2_test
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"testing"
 | |
| 	"text/template"
 | |
| 
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 	"k8s.io/apimachinery/pkg/runtime/schema"
 | |
| 	v2 "k8s.io/kubectl/pkg/explain/v2"
 | |
| )
 | |
| 
 | |
| func TestFuncs(t *testing.T) {
 | |
| 	testcases := []struct {
 | |
| 		Name     string
 | |
| 		FuncName string
 | |
| 		Source   string
 | |
| 		Context  any
 | |
| 		Expect   string
 | |
| 		Error    string
 | |
| 	}{
 | |
| 		{
 | |
| 			Name:     "err",
 | |
| 			FuncName: "fail",
 | |
| 			Source:   `{{fail .}}`,
 | |
| 			Context:  "this is a test",
 | |
| 			Error:    "this is a test",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "wrap",
 | |
| 			Source:   `{{wrap 3 .}}`,
 | |
| 			Context:  "this is a really good test",
 | |
| 			Expect:   "this\nis\na\nreally\ngood\ntest",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "split",
 | |
| 			Source:   `{{split . "/"}}`,
 | |
| 			Context:  "this/is/a/slash/separated/thing",
 | |
| 			Expect:   "[this is a slash separated thing]",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "join",
 | |
| 			Source:   `{{join "/" "this" "is" "a" "slash" "separated" "thing"}}`,
 | |
| 			Expect:   "this/is/a/slash/separated/thing",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "include",
 | |
| 			Source:   `{{define "myTemplate"}}{{.}}{{end}}{{$var := include "myTemplate" .}}{{$var}}`,
 | |
| 			Context:  "hello, world!",
 | |
| 			Expect:   "hello, world!",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "nil",
 | |
| 			FuncName: "first",
 | |
| 			Source:   `{{first .}}`,
 | |
| 			Context:  nil,
 | |
| 			Error:    "list is empty",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "empty",
 | |
| 			FuncName: "first",
 | |
| 			Source:   `{{first .}}`,
 | |
| 			Context:  []string{},
 | |
| 			Error:    "list is empty",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "first",
 | |
| 			Source:   `{{first .}}`,
 | |
| 			Context:  []string{"first", "second", "third"},
 | |
| 			Expect:   "first",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "wrongtype",
 | |
| 			FuncName: "first",
 | |
| 			Source:   `{{first .}}`,
 | |
| 			Context:  "test",
 | |
| 			Error:    "first cannot be used on type: string",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "nil",
 | |
| 			FuncName: "last",
 | |
| 			Source:   `{{last .}}`,
 | |
| 			Context:  nil,
 | |
| 			Error:    "list is empty",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "empty",
 | |
| 			FuncName: "last",
 | |
| 			Source:   `{{last .}}`,
 | |
| 			Context:  []string{},
 | |
| 			Error:    "list is empty",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "last",
 | |
| 			Source:   `{{last .}}`,
 | |
| 			Context:  []string{"first", "second", "third"},
 | |
| 			Expect:   "third",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "wrongtype",
 | |
| 			FuncName: "last",
 | |
| 			Source:   `{{last .}}`,
 | |
| 			Context:  "test",
 | |
| 			Error:    "last cannot be used on type: string",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "none",
 | |
| 			FuncName: "indent",
 | |
| 			Source:   `{{indent 0 .}}`,
 | |
| 			Context:  "this is a string",
 | |
| 			Expect:   "this is a string",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "some",
 | |
| 			FuncName: "indent",
 | |
| 			Source:   `{{indent 2 .}}`,
 | |
| 			Context:  "this is a string",
 | |
| 			Expect:   "  this is a string",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "empty",
 | |
| 			FuncName: "dict",
 | |
| 			Source:   `{{dict | toJson}}`,
 | |
| 			Expect:   "{}",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "single value",
 | |
| 			FuncName: "dict",
 | |
| 			Source:   `{{dict "key" "value" | toJson}}`,
 | |
| 			Expect:   `{"key":"value"}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "twoValues",
 | |
| 			FuncName: "dict",
 | |
| 			Source:   `{{dict "key1" "val1" "key2" "val2" | toJson}}`,
 | |
| 			Expect:   `{"key1":"val1","key2":"val2"}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "oddNumberArgs",
 | |
| 			FuncName: "dict",
 | |
| 			Source:   `{{dict "key1" 1 "key2" | toJson}}`,
 | |
| 			Error:    "error calling dict: expected even # of arguments",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "IntegerValue",
 | |
| 			FuncName: "dict",
 | |
| 			Source:   `{{dict "key1" 1 | toJson}}`,
 | |
| 			Expect:   `{"key1":1}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "MixedValues",
 | |
| 			FuncName: "dict",
 | |
| 			Source:   `{{dict "key1" 1 "key2" "val2" "key3" (dict "key1" "val1") | toJson}}`,
 | |
| 			Expect:   `{"key1":1,"key2":"val2","key3":{"key1":"val1"}}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "nil",
 | |
| 			FuncName: "contains",
 | |
| 			Source:   `{{contains . "value"}}`,
 | |
| 			Context:  nil,
 | |
| 			Expect:   `false`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "empty",
 | |
| 			FuncName: "contains",
 | |
| 			Source:   `{{contains . "value"}}`,
 | |
| 			Context:  []string{},
 | |
| 			Expect:   `false`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "contains",
 | |
| 			Source:   `{{contains . "value"}}`,
 | |
| 			Context:  []string{"value"},
 | |
| 			Expect:   `true`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "struct",
 | |
| 			FuncName: "contains",
 | |
| 			Source:   `{{contains $.haystack $.needle}}`,
 | |
| 			Context: map[string]any{
 | |
| 				"needle": schema.GroupVersionKind{Group: "testgroup.k8s.io", Version: "v1", Kind: "Kind"},
 | |
| 				"haystack": []schema.GroupVersionKind{
 | |
| 					{Group: "randomgroup.k8s.io", Version: "v1", Kind: "OtherKind"},
 | |
| 					{Group: "testgroup.k8s.io", Version: "v1", Kind: "OtherKind"},
 | |
| 					{Group: "testgroup.k8s.io", Version: "v1", Kind: "Kind"},
 | |
| 				},
 | |
| 			},
 | |
| 			Expect: `true`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "nil",
 | |
| 			FuncName: "set",
 | |
| 			Source:   `{{set nil "key" "value" | toJson}}`,
 | |
| 			Expect:   `{"key":"value"}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "empty",
 | |
| 			FuncName: "set",
 | |
| 			Source:   `{{set (dict) "key" "value" | toJson}}`,
 | |
| 			Expect:   `{"key":"value"}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "OddArgs",
 | |
| 			FuncName: "set",
 | |
| 			Source:   `{{set (dict) "key" "value" "key2" | toJson}}`,
 | |
| 			Error:    `expected even number of arguments`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "NonStringKey",
 | |
| 			FuncName: "set",
 | |
| 			Source:   `{{set (dict) 1 "value" | toJson}}`,
 | |
| 			Error:    `keys must be strings`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "NilKey",
 | |
| 			FuncName: "set",
 | |
| 			Source:   `{{set (dict) nil "value" | toJson}}`,
 | |
| 			Error:    `keys must be strings`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "NilValue",
 | |
| 			FuncName: "set",
 | |
| 			Source:   `{{set (dict) "key" nil | toJson}}`,
 | |
| 			Expect:   `{"key":null}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "OverwriteKey",
 | |
| 			FuncName: "set",
 | |
| 			Source:   `{{set (dict "key1" "val1" "key2" "val2") "key1" nil | toJson}}`,
 | |
| 			Expect:   `{"key1":null,"key2":"val2"}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "OverwriteKeyWithLefover",
 | |
| 			FuncName: "set",
 | |
| 			Source:   `{{set (dict "key1" "val1" "key2" "val2" "key3" "val3") "key1" nil | toJson}}`,
 | |
| 			Expect:   `{"key1":null,"key2":"val2","key3":"val3"}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "add",
 | |
| 			Source:   `{{add 1 2}}`,
 | |
| 			Expect:   `3`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "sub",
 | |
| 			Source:   `{{sub 1 2}}`,
 | |
| 			Expect:   `-1`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "mul",
 | |
| 			Source:   `{{mul 2 3}}`,
 | |
| 			Expect:   `6`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basic",
 | |
| 			FuncName: "resolveRef",
 | |
| 			Source:   `{{resolveRef "#/components/schemas/myTypeName" . | toJson}}`,
 | |
| 			Context: map[string]any{
 | |
| 				"components": map[string]any{
 | |
| 					"schemas": map[string]any{
 | |
| 						"myTypeName": map[string]any{
 | |
| 							"key": "val",
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			Expect: `{"key":"val"}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "basicNameWithDots",
 | |
| 			FuncName: "resolveRef",
 | |
| 			Source:   `{{resolveRef "#/components/schemas/myTypeName.with.dots" . | toJson}}`,
 | |
| 			Context: map[string]any{
 | |
| 				"components": map[string]any{
 | |
| 					"schemas": map[string]any{
 | |
| 						"myTypeName.with.dots": map[string]any{
 | |
| 							"key": "val",
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			Expect: `{"key":"val"}`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "notFound",
 | |
| 			FuncName: "resolveRef",
 | |
| 			Source:   `{{resolveRef "#/components/schemas/otherTypeName" . | toJson}}`,
 | |
| 			Context: map[string]any{
 | |
| 				"components": map[string]any{
 | |
| 					"schemas": map[string]any{
 | |
| 						"myTypeName": map[string]any{
 | |
| 							"key": "val",
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			Expect: `null`,
 | |
| 		},
 | |
| 		{
 | |
| 			Name:     "url",
 | |
| 			FuncName: "resolveRef",
 | |
| 			Source:   `{{resolveRef "http://swagger.com/swagger.json#/components/schemas/myTypeName" . | toJson}}`,
 | |
| 			Context: map[string]any{
 | |
| 				"components": map[string]any{
 | |
| 					"schemas": map[string]any{
 | |
| 						"myTypeName": map[string]any{
 | |
| 							"key": "val",
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			Expect: `null`,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tcase := range testcases {
 | |
| 		t.Run(tcase.FuncName+"/"+tcase.Name, func(t *testing.T) {
 | |
| 
 | |
| 			tmpl, err := v2.WithBuiltinTemplateFuncs(template.New("me")).Parse(tcase.Source)
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			buf := bytes.NewBuffer(nil)
 | |
| 			err = tmpl.Execute(buf, tcase.Context)
 | |
| 
 | |
| 			if len(tcase.Error) > 0 {
 | |
| 				require.ErrorContains(t, err, tcase.Error)
 | |
| 			} else if output := buf.String(); len(tcase.Expect) > 0 {
 | |
| 				require.NoError(t, err)
 | |
| 				require.Contains(t, output, tcase.Expect)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 |