From 0ba16afe247bb66d673a0b1fad088c202cdd9c8f Mon Sep 17 00:00:00 2001 From: Muhammad Date: Thu, 4 Dec 2025 15:41:34 -0500 Subject: [PATCH 1/2] ensure uts are parseable --- charts/member-agent-arc/charts_test.go | 172 +++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 charts/member-agent-arc/charts_test.go diff --git a/charts/member-agent-arc/charts_test.go b/charts/member-agent-arc/charts_test.go new file mode 100644 index 000000000..0bd0ef90b --- /dev/null +++ b/charts/member-agent-arc/charts_test.go @@ -0,0 +1,172 @@ +package memberagentarc + +import ( + "os" + "path/filepath" + "strings" + "testing" + "text/template" + + . "github.com/onsi/gomega" + + "sigs.k8s.io/yaml" +) + +// TestHelmChartTemplatesRenderValidYAML tests that Helm chart templates render +// to valid YAML with maximal configuration values that activate all conditional paths. +func TestHelmChartTemplatesRenderValidYAML(t *testing.T) { + g := NewWithT(t) + // Maximal configuration to activate all conditional template paths + values := map[string]interface{}{ + "memberagent": map[string]interface{}{ + "repository": "mcr.microsoft.com/aks/fleet/member-agent", + "tag": "v1.0.0", + }, + "mcscontrollermanager": map[string]interface{}{ + "repository": "mcr.microsoft.com/aks/fleet/mcs-controller-manager", + "tag": "v1.0.0", + }, + "membernetcontrollermanager": map[string]interface{}{ + "repository": "mcr.microsoft.com/aks/fleet/member-net-controller-manager", + "tag": "v1.0.0", + }, + "refreshtoken": map[string]interface{}{ + "repository": "mcr.microsoft.com/aks/fleet/refresh-token", + "tag": "v1.0.0", + }, + "crdinstaller": map[string]interface{}{ + "enabled": true, + "repository": "mcr.microsoft.com/aks/fleet/crd-installer", + "tag": "v1.0.0", + "logVerbosity": 2, + }, + "logVerbosity": 5, + "namespace": "fleet-system", + "config": map[string]interface{}{ + "scope": "https://test.scope", + "hubURL": "https://hub.example.com", + "memberClusterName": "test-cluster", + "hubCA": "test-ca-cert", + }, + "enableV1Beta1APIs": true, + "enableTrafficManagerFeature": true, + "enableNetworkingFeatures": true, + "propertyProvider": "azure", + "Azure": map[string]interface{}{ + "proxySettings": map[string]interface{}{ + "isProxyEnabled": true, + "httpProxy": "http://proxy.example.com:8080", + "httpsProxy": "https://proxy.example.com:8443", + "noProxy": "localhost,127.0.0.1", + "proxyCert": "test-proxy-cert", + }, + "Identity": map[string]interface{}{ + "MSIAdapterYaml": "image: mcr.microsoft.com/aks/msi-adapter:v1.0.0\nresources:\n limits:\n cpu: 100m", + }, + "Extension": map[string]interface{}{ + "Name": "fleet-member-extension", + }, + }, + } + + // Template context matching Helm's structure + context := map[string]interface{}{ + "Values": values, + "Release": map[string]interface{}{ + "Name": "test-release", + "Namespace": "fleet-system", + }, + "Chart": map[string]interface{}{ + "Name": "arc-member-cluster-agents", + "Version": "1.0.0", + }, + } + + templatesDir := "templates" + entries, err := os.ReadDir(templatesDir) + g.Expect(err).ToNot(HaveOccurred(), "Failed to read templates directory") + + validatedCount := 0 + for _, entry := range entries { + // Skip directories and helper templates + if entry.IsDir() || strings.HasPrefix(entry.Name(), "_") { + continue + } + + templatePath := filepath.Join(templatesDir, entry.Name()) + templateBytes, err := os.ReadFile(templatePath) + g.Expect(err).ToNot(HaveOccurred(), "Failed to read template %s", entry.Name()) + + // Parse and render the Go template + tmpl := template.New(entry.Name()).Funcs(helmFuncMap()) + tmpl, err = tmpl.Parse(string(templateBytes)) + g.Expect(err).ToNot(HaveOccurred(), "Failed to parse template %s", entry.Name()) + + var rendered strings.Builder + err = tmpl.Execute(&rendered, context) + g.Expect(err).ToNot(HaveOccurred(), "Failed to render template %s. Err :s", entry.Name(), err) + + renderedContent := strings.TrimSpace(rendered.String()) + g.Expect(renderedContent).ToNot(BeEmpty(), "Rendered template %s is empty", entry.Name()) + + // Validate each YAML document in multi-doc files + docs := strings.Split(renderedContent, "\n---\n") + validDocsCount := 0 + for i, doc := range docs { + doc = strings.TrimSpace(doc) + if doc == "" { + continue + } + + var obj interface{} + err := yaml.Unmarshal([]byte(doc), &obj) + g.Expect(err).ToNot(HaveOccurred(), "Template %s doc %d is invalid YAML: %v\nContent:\n%s", + entry.Name(), i+1, err, doc) + validDocsCount++ + } + + validatedCount++ + } + + g.Expect(validatedCount).ToNot(BeZero(), "No templates were validated") +} + +// helmFuncMap returns template functions that mimic Helm's template functions +func helmFuncMap() template.FuncMap { + return template.FuncMap{ + "nindent": func(spaces int, s string) string { + indent := strings.Repeat(" ", spaces) + lines := strings.Split(s, "\n") + var result []string + for i, line := range lines { + if i == 0 { + result = append(result, "\n"+indent+line) + } else { + result = append(result, indent+line) + } + } + return strings.Join(result, "\n") + }, + "quote": func(s interface{}) string { + return `"` + toString(s) + `"` + }, + "b64enc": func(s string) string { + // Simple mock - just return the string for testing purposes + return s + }, + "include": func(name string, data interface{}) string { + // Simple mock - return empty string for testing + return "" + }, + } +} + +func toString(v interface{}) string { + if v == nil { + return "" + } + if s, ok := v.(string); ok { + return s + } + return "" +} From ef90685838bff1d806f1c4dfb2201ab4a131486d Mon Sep 17 00:00:00 2001 From: Muhammad Date: Tue, 9 Dec 2025 12:21:30 -0500 Subject: [PATCH 2/2] feedback --- charts/member-agent-arc/charts_test.go | 93 +++++++++++++------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/charts/member-agent-arc/charts_test.go b/charts/member-agent-arc/charts_test.go index 0bd0ef90b..29d0e5fa9 100644 --- a/charts/member-agent-arc/charts_test.go +++ b/charts/member-agent-arc/charts_test.go @@ -1,6 +1,7 @@ package memberagentarc import ( + "encoding/base64" "os" "path/filepath" "strings" @@ -15,7 +16,6 @@ import ( // TestHelmChartTemplatesRenderValidYAML tests that Helm chart templates render // to valid YAML with maximal configuration values that activate all conditional paths. func TestHelmChartTemplatesRenderValidYAML(t *testing.T) { - g := NewWithT(t) // Maximal configuration to activate all conditional template paths values := map[string]interface{}{ "memberagent": map[string]interface{}{ @@ -82,53 +82,54 @@ func TestHelmChartTemplatesRenderValidYAML(t *testing.T) { }, } - templatesDir := "templates" - entries, err := os.ReadDir(templatesDir) - g.Expect(err).ToNot(HaveOccurred(), "Failed to read templates directory") - - validatedCount := 0 - for _, entry := range entries { - // Skip directories and helper templates - if entry.IsDir() || strings.HasPrefix(entry.Name(), "_") { - continue - } - - templatePath := filepath.Join(templatesDir, entry.Name()) - templateBytes, err := os.ReadFile(templatePath) - g.Expect(err).ToNot(HaveOccurred(), "Failed to read template %s", entry.Name()) - - // Parse and render the Go template - tmpl := template.New(entry.Name()).Funcs(helmFuncMap()) - tmpl, err = tmpl.Parse(string(templateBytes)) - g.Expect(err).ToNot(HaveOccurred(), "Failed to parse template %s", entry.Name()) - - var rendered strings.Builder - err = tmpl.Execute(&rendered, context) - g.Expect(err).ToNot(HaveOccurred(), "Failed to render template %s. Err :s", entry.Name(), err) + // Table-driven test for each template file + tests := []struct { + name string + templateFile string + }{ + {name: "deployment template", templateFile: "deployment.yaml"}, + {name: "rbac template", templateFile: "rbac.yaml"}, + {name: "serviceaccount template", templateFile: "serviceaccount.yaml"}, + {name: "azure-proxy-secrets template", templateFile: "azure-proxy-secrets.yaml"}, + } - renderedContent := strings.TrimSpace(rendered.String()) - g.Expect(renderedContent).ToNot(BeEmpty(), "Rendered template %s is empty", entry.Name()) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + templatePath := filepath.Join("templates", tt.templateFile) + templateBytes, err := os.ReadFile(templatePath) + g.Expect(err).ToNot(HaveOccurred(), "Failed to read template %s. Err %s", tt.templateFile, err) + + // Parse and render the Go template + tmpl := template.New(tt.templateFile).Funcs(helmFuncMap()) + tmpl, err = tmpl.Parse(string(templateBytes)) + g.Expect(err).ToNot(HaveOccurred(), "Failed to parse template %s. Err %s", tt.templateFile, err) + + var rendered strings.Builder + err = tmpl.Execute(&rendered, context) + g.Expect(err).ToNot(HaveOccurred(), "Failed to render template %s. Err %s", tt.templateFile, err) + renderedContent := strings.TrimSpace(rendered.String()) + + // Validate each YAML document in multi-doc files + docs := strings.Split(renderedContent, "\n---\n") + validDocsCount := 0 + for i, doc := range docs { + doc = strings.TrimSpace(doc) + if doc == "" { + continue + } - // Validate each YAML document in multi-doc files - docs := strings.Split(renderedContent, "\n---\n") - validDocsCount := 0 - for i, doc := range docs { - doc = strings.TrimSpace(doc) - if doc == "" { - continue + var obj interface{} + err := yaml.Unmarshal([]byte(doc), &obj) + g.Expect(err).ToNot(HaveOccurred(), "Template %s doc %d is invalid YAML\nContent:\n%s", + tt.templateFile, i+1, doc) + validDocsCount++ } - var obj interface{} - err := yaml.Unmarshal([]byte(doc), &obj) - g.Expect(err).ToNot(HaveOccurred(), "Template %s doc %d is invalid YAML: %v\nContent:\n%s", - entry.Name(), i+1, err, doc) - validDocsCount++ - } - - validatedCount++ + g.Expect(validDocsCount).To(BeNumerically(">", 0), "Template %s rendered but produced no valid YAML documents", tt.templateFile) + }) } - - g.Expect(validatedCount).ToNot(BeZero(), "No templates were validated") } // helmFuncMap returns template functions that mimic Helm's template functions @@ -151,12 +152,10 @@ func helmFuncMap() template.FuncMap { return `"` + toString(s) + `"` }, "b64enc": func(s string) string { - // Simple mock - just return the string for testing purposes - return s + return base64.StdEncoding.EncodeToString([]byte(s)) }, "include": func(name string, data interface{}) string { - // Simple mock - return empty string for testing - return "" + return "" }, } }