package helmfile import ( "bytes" "fmt" "os" "testing" "text/template" "github.com/stretchr/testify/require" "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/tmpl" ) type tmplTestCase struct { // envs are set in the test environment envs map[string]string // name of the test name string // tmplString is the template string to be parsed tmplString string // data is the data to be passed to the template data any // wantErr is true if the template should fail to parse wantErr bool // output is the expected output of the template output string } // setEnvs sets the environment variables for the test case func (t *tmplTestCase) setEnvs(tt *testing.T) { for k, v := range t.envs { tt.Setenv(k, v) } } var requireEnvTestCases = []tmplTestCase{ { envs: map[string]string{ "TEST_VAR1": "test1", }, name: "requiredEnvWithEnvs", tmplString: `{{ requiredEnv "TEST_VAR1" }}`, output: "test1", }, { name: "requiredEnv", tmplString: `{{ requiredEnv "TEST_VAR" }}`, wantErr: true, }, } var requiredTestCases = []tmplTestCase{ { name: "requiredWithEmptyString", tmplString: `{{ "" | required "required" }}`, wantErr: true, }, { name: "required", tmplString: `{{ "test" | required "required" }}`, output: "test", }, } var envExecTestCases = []tmplTestCase{ { name: "envExecWithEnvs", tmplString: `{{ envExec (dict "testkey" "test2") "bash" (list "-c" "echo -n $testkey" ) }}`, output: "test2", }, { name: "envExec", tmplString: `{{ envExec (dict) "bash" (list "-c" "echo -n $testkey" ) }}`, }, { name: "envExecInvalidEnvType", wantErr: true, tmplString: `{{ envExec "dict" "bash" (list "-c" "echo -n $testkey" ) }}`, }, } var execTestCases = []tmplTestCase{ { name: "exec", tmplString: `{{ exec "bash" (list "-c" "echo -n $testkey" ) }}`, }, { name: "execWithError", wantErr: true, tmplString: `{{ exec "bash" (list "-c" "exit 1" ) }}`, }, } var readFileTestCases = []tmplTestCase{ { name: "readFile", tmplString: `{{ readFile "./testdata/tmpl/readfile.txt" }}`, output: "test", }, { name: "readFileWithError", tmplString: `{{ readFile "./testdata/tmpl/readfile_error.txt" }}`, wantErr: true, }, } var readDirTestCases = []tmplTestCase{ { name: "readDir", tmplString: `{{ range $index,$item := readDir "./testdata/tmpl/sample_folder/" }} {{- $itemSplit := splitList "/" $item -}} {{- if contains "\\" $item -}} {{- $itemSplit = splitList "\\" $item -}} {{- end -}} {{- $itemValue := $itemSplit | last -}} {{- $itemValue -}} {{- end -}}`, output: "file1.txtfile2.txt", }, { name: "readDirWithError", tmplString: `{{ readFile "./testdata/tmpl/sample_folder_error/" }}`, wantErr: true, }, } var readDirEntriesTestCases = []tmplTestCase{ { name: "readDirEntries", tmplString: `{{ range $index,$item := readDirEntries "./testdata/tmpl/sample_folder/" }} {{- $item.Name -}} {{- end -}}`, output: "file1.txtfile2.txtsub_folder", }, { name: "readDirEntriesOnlyFolders", tmplString: `{{ range $index,$item := readDirEntries "./testdata/tmpl/sample_folder/" }} {{- if $item.IsDir -}} {{- $item.Name -}} {{- end -}} {{- end -}}`, output: "sub_folder", }, { name: "readDirEntriesWithError", tmplString: `{{ readDirEntries "./testdata/tmpl/sample_folder_error/" }}`, wantErr: true, }, } var toYamlTestCases = []tmplTestCase{ { data: map[string]string{ "test": "test", }, name: "toYaml", tmplString: `{{ . | toYaml }}`, output: "test: test\n", }, } var fromYamlTestCases = []tmplTestCase{ { name: "fromYaml", tmplString: `{{ $value := "test: test" | fromYaml }} {{- $value.test }}`, output: "test", }, } var setValueAtPathTestCases = []tmplTestCase{ { data: map[string]any{ "root": map[string]any{ "testKey": map[string]any{ "testKey2": "test", }, }, }, name: "setValueAtPath", tmplString: `{{- $newValues := . | setValueAtPath "root.testKey.testKey2" "testNewValue" }} {{- $newValues.root.testKey.testKey2 }}`, output: "testNewValue", }, { data: map[string]any{ "root": "test", }, wantErr: true, name: "setValueAtPathWithInvalidPath", tmplString: `{{ . | setValueAtPath "root.nokey" "testNewValue" }}`, }, } var getTestCases = []tmplTestCase{ { data: map[string]any{ "root": map[string]any{ "testGetKey": map[string]any{ "testGetKey2": "test", }, }, }, name: "get", tmplString: `{{- . | get "root.testGetKey.testGetKey2" "notfound" }}`, output: "test", }, { data: map[string]any{ "root": map[string]any{ "testGetKey": map[string]any{ "testGetKey2": "test", }, }, }, name: "getNotExistWithDefault", tmplString: `{{- . | get "root.testGetKey.testGetKey3" "notfound" }}`, output: "notfound", }, } var tplTestCases = []tmplTestCase{ { data: map[string]any{ "root": "tplvalue", }, name: "tpl", tmplString: `{{ . | tpl "{{ .root }}" }}`, output: "tplvalue", }, { data: map[string]any{}, name: "tplInvalidTemplate", wantErr: true, tmplString: `{{ . | tpl "{{ .root }}" }}`, }, } // tmplTestCases are the test cases for the template tests type tmplE2e struct { tcs []tmplTestCase } // append for append testcase into tmplTestCases func (t *tmplE2e) append(ts ...tmplTestCase) { t.tcs = append(t.tcs, ts...) } // load for load testcase into tmplTestCases func (t *tmplE2e) load() { t.append(requireEnvTestCases...) t.append(requiredTestCases...) t.append(envExecTestCases...) t.append(execTestCases...) t.append(readFileTestCases...) t.append(readDirTestCases...) t.append(readDirEntriesTestCases...) t.append(toYamlTestCases...) t.append(fromYamlTestCases...) t.append(setValueAtPathTestCases...) t.append(getTestCases...) t.append(tplTestCases...) } var tmplE2eTest = tmplE2e{} func TestFileRendering(t *testing.T) { tmplE2eTest.load() for _, tc := range tmplE2eTest.tcs { t.Run(tc.name, func(t *testing.T) { tc.setEnvs(t) tempDir, _ := os.MkdirTemp("./testdata", "test") defer os.RemoveAll(tempDir) filename := fmt.Sprintf("%s/%s.gotmpl", tempDir, tc.name) os.WriteFile(filename, []byte(tc.tmplString), 0644) fileRenderer := tmpl.NewFileRenderer(filesystem.DefaultFileSystem(), ".", tc.data) tmpl_bytes, err := fileRenderer.RenderToBytes(filename) if tc.wantErr { require.Error(t, err) } else { require.NoError(t, err) } tmpl := string(tmpl_bytes) require.Equal(t, tc.output, tmpl) }) } } // TestTmplStrings tests the template string func TestTmplStrings(t *testing.T) { c := &tmpl.Context{} c.SetBasePath(".") c.SetFileSystem(filesystem.DefaultFileSystem()) tmpl := template.New("stringTemplateTest").Funcs(c.CreateFuncMap()) tmplE2eTest.load() for _, tc := range tmplE2eTest.tcs { t.Run(tc.name, func(t *testing.T) { tc.setEnvs(t) tmpl, err := tmpl.Parse(tc.tmplString) require.Nilf(t, err, "error parsing template: %v", err) var tplResult bytes.Buffer err = tmpl.Execute(&tplResult, tc.data) if tc.wantErr { require.Error(t, err) } else { require.NoError(t, err) } require.Equal(t, tc.output, tplResult.String()) }) } }