package template_test import ( "context" "io" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "sort" "strings" "testing" "github.com/influxdata/influx-cli/v2/api" "github.com/influxdata/influx-cli/v2/pkg/template" "github.com/stretchr/testify/require" ) func TestSourcesFromPath(t *testing.T) { t.Parallel() type contents struct { name string encoding template.Encoding contents string } testCases := []struct { name string setup func(t *testing.T, rootDir string) inPath func(rootDir string) string inEncoding template.Encoding recursive bool expected func(rootDir string) []contents }{ { name: "JSON file", setup: func(t *testing.T, rootDir string) { require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.json"), []byte("foo"), os.ModePerm)) }, inPath: func(rootDir string) string { return filepath.Join(rootDir, "foo.json") }, expected: func(string) []contents { return []contents{{ name: "foo.json", encoding: template.EncodingJson, contents: "foo", }} }, }, { name: "YAML file", setup: func(t *testing.T, rootDir string) { require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.yaml"), []byte("foo"), os.ModePerm)) }, inPath: func(rootDir string) string { return filepath.Join(rootDir, "foo.yaml") }, expected: func(string) []contents { return []contents{{ name: "foo.yaml", encoding: template.EncodingYaml, contents: "foo", }} }, }, { name: "YML file", setup: func(t *testing.T, rootDir string) { require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.yml"), []byte("foo"), os.ModePerm)) }, inPath: func(rootDir string) string { return filepath.Join(rootDir, "foo.yml") }, expected: func(string) []contents { return []contents{{ name: "foo.yml", encoding: template.EncodingYaml, contents: "foo", }} }, }, { name: "JSONNET file", setup: func(t *testing.T, rootDir string) { require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.jsonnet"), []byte("foo"), os.ModePerm)) }, inPath: func(rootDir string) string { return filepath.Join(rootDir, "foo.jsonnet") }, expected: func(string) []contents { return []contents{{ name: "foo.jsonnet", encoding: template.EncodingJsonnet, contents: "foo", }} }, }, { name: "explicit inEncoding", setup: func(t *testing.T, rootDir string) { require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo"), []byte("foo"), os.ModePerm)) }, inEncoding: template.EncodingJson, inPath: func(rootDir string) string { return filepath.Join(rootDir, "foo") }, expected: func(string) []contents { return []contents{{ name: "foo", encoding: template.EncodingJson, contents: "foo", }} }, }, { name: "directory - non-recursive", setup: func(t *testing.T, rootDir string) { require.NoError(t, os.Mkdir(filepath.Join(rootDir, "bar"), os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.json"), []byte("foo.json"), os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.yml"), []byte("foo.yml"), os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(rootDir, "bar", "foo.jsonnet"), []byte("foo.jsonnet"), os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(rootDir, "bar", "foo.yaml"), []byte("foo.yaml"), os.ModePerm)) }, inPath: func(rootDir string) string { return rootDir }, expected: func(rootDir string) []contents { return []contents{ { name: filepath.Join(rootDir, "foo.json"), contents: "foo.json", encoding: template.EncodingJson, }, { name: filepath.Join(rootDir, "foo.yml"), contents: "foo.yml", encoding: template.EncodingYaml, }, } }, }, { name: "directory - recursive", setup: func(t *testing.T, rootDir string) { require.NoError(t, os.Mkdir(filepath.Join(rootDir, "bar"), os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.json"), []byte("foo.json"), os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.yml"), []byte("foo.yml"), os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(rootDir, "bar", "foo.jsonnet"), []byte("foo.jsonnet"), os.ModePerm)) require.NoError(t, os.WriteFile(filepath.Join(rootDir, "bar", "foo.yaml"), []byte("foo.yaml"), os.ModePerm)) }, inPath: func(rootDir string) string { return rootDir }, recursive: true, expected: func(rootDir string) []contents { return []contents{ { name: filepath.Join(rootDir, "foo.json"), contents: "foo.json", encoding: template.EncodingJson, }, { name: filepath.Join(rootDir, "foo.yml"), contents: "foo.yml", encoding: template.EncodingYaml, }, { name: filepath.Join(rootDir, "bar", "foo.yaml"), contents: "foo.yaml", encoding: template.EncodingYaml, }, { name: filepath.Join(rootDir, "bar", "foo.jsonnet"), contents: "foo.jsonnet", encoding: template.EncodingJsonnet, }, } }, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() tmp, err := os.MkdirTemp("", "") require.NoError(t, err) defer os.RemoveAll(tmp) tc.setup(t, tmp) sources, err := template.SourcesFromPath(tc.inPath(tmp), tc.recursive, tc.inEncoding) require.NoError(t, err) expected := tc.expected(tmp) require.Len(t, sources, len(expected)) sort.Slice(sources, func(i, j int) bool { return sources[i].Name < sources[j].Name }) sort.Slice(expected, func(i, j int) bool { return expected[i].name < expected[j].name }) for i := range expected { source := sources[i] contents := expected[i] require.Equal(t, contents.encoding, source.Encoding) sourceIn, err := source.Open(context.Background()) require.NoError(t, err) bytes, err := io.ReadAll(sourceIn) sourceIn.Close() require.NoError(t, err) require.Equal(t, contents.contents, string(bytes)) } }) } } func TestSourceFromURL(t *testing.T) { t.Parallel() testCases := []struct { name string filename string inEncoding template.Encoding expectEncoding template.Encoding resStatus int resBody string expectErr bool }{ { name: "JSON file", filename: "foo.json", expectEncoding: template.EncodingJson, resStatus: 200, resBody: "Foo bar", }, { name: "YAML file", filename: "foo.yaml", expectEncoding: template.EncodingYaml, resStatus: 200, resBody: "Foo bar", }, { name: "YML file", filename: "foo.yml", expectEncoding: template.EncodingYaml, resStatus: 200, resBody: "Foo bar", }, { name: "JSONNET file", filename: "foo.jsonnet", expectEncoding: template.EncodingJsonnet, resStatus: 200, resBody: "Foo bar", }, { name: "explicit encoding", filename: "foo", inEncoding: template.EncodingJson, expectEncoding: template.EncodingJson, resStatus: 200, resBody: "Foo bar", }, { name: "err response", filename: "foo.json", expectEncoding: template.EncodingJson, resStatus: 403, resBody: "OH NO", expectErr: true, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(tc.resStatus) rw.Write([]byte(tc.resBody)) })) defer server.Close() u, err := url.Parse(server.URL) require.NoError(t, err) u.Path = tc.filename source := template.SourceFromURL(u, tc.inEncoding) in, err := source.Open(context.Background()) if tc.expectErr { require.Error(t, err) require.Contains(t, err.Error(), "bad response") require.Contains(t, err.Error(), tc.resBody) return } require.NoError(t, err) defer in.Close() bytes, err := io.ReadAll(in) require.NoError(t, err) require.Equal(t, tc.resBody, string(bytes)) }) } } func TestTemplateSource_Read(t *testing.T) { t.Parallel() yamlTemplate := `--- apiVersion: influxdata.com/v2alpha1 kind: Bucket meta: null spec: name: test retentionRules: - type: expire --- apiVersion: influxdata.com/v2alpha1 kind: Bucket meta: null spec: name: test2 retentionRules: - type: expire ` jsonTemplate := `[ { "apiVersion": "influxdata.com/v2alpha1", "kind": "Bucket", "spec": { "name": "test", "retentionRules": [ { "type": "expire" } ] } }, { "apiVersion": "influxdata.com/v2alpha1", "kind": "Bucket", "spec": { "name": "test2", "retentionRules": [ { "type": "expire" } ] } } ] ` jsonnetTemplate := `local Bucket(name) = { apiVersion: "influxdata.com/v2alpha1", kind: "Bucket", spec: { name: name, retentionRules: [{ type: "expire", }], }, }; [Bucket("test"), Bucket("test2")] ` parsed := []api.TemplateEntry{ { ApiVersion: "influxdata.com/v2alpha1", Kind: "Bucket", Spec: map[string]interface{}{ "name": "test", "retentionRules": []interface{}{ map[string]interface{}{ "type": "expire", }, }, }, }, { ApiVersion: "influxdata.com/v2alpha1", Kind: "Bucket", Spec: map[string]interface{}{ "name": "test2", "retentionRules": []interface{}{ map[string]interface{}{ "type": "expire", }, }, }, }, } testCases := []struct { name string encoding template.Encoding data string }{ { name: "JSON", encoding: template.EncodingJson, data: jsonTemplate, }, { name: "YAML", encoding: template.EncodingYaml, data: yamlTemplate, }, { name: "JSONNET", encoding: template.EncodingJsonnet, data: jsonnetTemplate, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() source := template.SourceFromReader(strings.NewReader(tc.data), tc.encoding) tmpl, err := source.Read(context.Background()) require.NoError(t, err) expected := api.TemplateApplyTemplate{ Sources: []string{source.Name + ".generated.json"}, Contents: parsed, } require.Equal(t, expected, tmpl) }) } }