Skip to content

Commit 5de2741

Browse files
chore: Add UT to ensure Arc charts are parseable (#1239)
2 parents 99cc15d + ef90685 commit 5de2741

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package memberagentarc
2+
3+
import (
4+
"encoding/base64"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
"text/template"
10+
11+
. "github.com/onsi/gomega"
12+
13+
"sigs.k8s.io/yaml"
14+
)
15+
16+
// TestHelmChartTemplatesRenderValidYAML tests that Helm chart templates render
17+
// to valid YAML with maximal configuration values that activate all conditional paths.
18+
func TestHelmChartTemplatesRenderValidYAML(t *testing.T) {
19+
// Maximal configuration to activate all conditional template paths
20+
values := map[string]interface{}{
21+
"memberagent": map[string]interface{}{
22+
"repository": "mcr.microsoft.com/aks/fleet/member-agent",
23+
"tag": "v1.0.0",
24+
},
25+
"mcscontrollermanager": map[string]interface{}{
26+
"repository": "mcr.microsoft.com/aks/fleet/mcs-controller-manager",
27+
"tag": "v1.0.0",
28+
},
29+
"membernetcontrollermanager": map[string]interface{}{
30+
"repository": "mcr.microsoft.com/aks/fleet/member-net-controller-manager",
31+
"tag": "v1.0.0",
32+
},
33+
"refreshtoken": map[string]interface{}{
34+
"repository": "mcr.microsoft.com/aks/fleet/refresh-token",
35+
"tag": "v1.0.0",
36+
},
37+
"crdinstaller": map[string]interface{}{
38+
"enabled": true,
39+
"repository": "mcr.microsoft.com/aks/fleet/crd-installer",
40+
"tag": "v1.0.0",
41+
"logVerbosity": 2,
42+
},
43+
"logVerbosity": 5,
44+
"namespace": "fleet-system",
45+
"config": map[string]interface{}{
46+
"scope": "https://test.scope",
47+
"hubURL": "https://hub.example.com",
48+
"memberClusterName": "test-cluster",
49+
"hubCA": "test-ca-cert",
50+
},
51+
"enableV1Beta1APIs": true,
52+
"enableTrafficManagerFeature": true,
53+
"enableNetworkingFeatures": true,
54+
"propertyProvider": "azure",
55+
"Azure": map[string]interface{}{
56+
"proxySettings": map[string]interface{}{
57+
"isProxyEnabled": true,
58+
"httpProxy": "http://proxy.example.com:8080",
59+
"httpsProxy": "https://proxy.example.com:8443",
60+
"noProxy": "localhost,127.0.0.1",
61+
"proxyCert": "test-proxy-cert",
62+
},
63+
"Identity": map[string]interface{}{
64+
"MSIAdapterYaml": "image: mcr.microsoft.com/aks/msi-adapter:v1.0.0\nresources:\n limits:\n cpu: 100m",
65+
},
66+
"Extension": map[string]interface{}{
67+
"Name": "fleet-member-extension",
68+
},
69+
},
70+
}
71+
72+
// Template context matching Helm's structure
73+
context := map[string]interface{}{
74+
"Values": values,
75+
"Release": map[string]interface{}{
76+
"Name": "test-release",
77+
"Namespace": "fleet-system",
78+
},
79+
"Chart": map[string]interface{}{
80+
"Name": "arc-member-cluster-agents",
81+
"Version": "1.0.0",
82+
},
83+
}
84+
85+
// Table-driven test for each template file
86+
tests := []struct {
87+
name string
88+
templateFile string
89+
}{
90+
{name: "deployment template", templateFile: "deployment.yaml"},
91+
{name: "rbac template", templateFile: "rbac.yaml"},
92+
{name: "serviceaccount template", templateFile: "serviceaccount.yaml"},
93+
{name: "azure-proxy-secrets template", templateFile: "azure-proxy-secrets.yaml"},
94+
}
95+
96+
for _, tt := range tests {
97+
t.Run(tt.name, func(t *testing.T) {
98+
g := NewWithT(t)
99+
100+
templatePath := filepath.Join("templates", tt.templateFile)
101+
templateBytes, err := os.ReadFile(templatePath)
102+
g.Expect(err).ToNot(HaveOccurred(), "Failed to read template %s. Err %s", tt.templateFile, err)
103+
104+
// Parse and render the Go template
105+
tmpl := template.New(tt.templateFile).Funcs(helmFuncMap())
106+
tmpl, err = tmpl.Parse(string(templateBytes))
107+
g.Expect(err).ToNot(HaveOccurred(), "Failed to parse template %s. Err %s", tt.templateFile, err)
108+
109+
var rendered strings.Builder
110+
err = tmpl.Execute(&rendered, context)
111+
g.Expect(err).ToNot(HaveOccurred(), "Failed to render template %s. Err %s", tt.templateFile, err)
112+
renderedContent := strings.TrimSpace(rendered.String())
113+
114+
// Validate each YAML document in multi-doc files
115+
docs := strings.Split(renderedContent, "\n---\n")
116+
validDocsCount := 0
117+
for i, doc := range docs {
118+
doc = strings.TrimSpace(doc)
119+
if doc == "" {
120+
continue
121+
}
122+
123+
var obj interface{}
124+
err := yaml.Unmarshal([]byte(doc), &obj)
125+
g.Expect(err).ToNot(HaveOccurred(), "Template %s doc %d is invalid YAML\nContent:\n%s",
126+
tt.templateFile, i+1, doc)
127+
validDocsCount++
128+
}
129+
130+
g.Expect(validDocsCount).To(BeNumerically(">", 0), "Template %s rendered but produced no valid YAML documents", tt.templateFile)
131+
})
132+
}
133+
}
134+
135+
// helmFuncMap returns template functions that mimic Helm's template functions
136+
func helmFuncMap() template.FuncMap {
137+
return template.FuncMap{
138+
"nindent": func(spaces int, s string) string {
139+
indent := strings.Repeat(" ", spaces)
140+
lines := strings.Split(s, "\n")
141+
var result []string
142+
for i, line := range lines {
143+
if i == 0 {
144+
result = append(result, "\n"+indent+line)
145+
} else {
146+
result = append(result, indent+line)
147+
}
148+
}
149+
return strings.Join(result, "\n")
150+
},
151+
"quote": func(s interface{}) string {
152+
return `"` + toString(s) + `"`
153+
},
154+
"b64enc": func(s string) string {
155+
return base64.StdEncoding.EncodeToString([]byte(s))
156+
},
157+
"include": func(name string, data interface{}) string {
158+
return "<included: " + name + ">"
159+
},
160+
}
161+
}
162+
163+
func toString(v interface{}) string {
164+
if v == nil {
165+
return ""
166+
}
167+
if s, ok := v.(string); ok {
168+
return s
169+
}
170+
return ""
171+
}

0 commit comments

Comments
 (0)