helmfile/pkg/yaml/append_processor_test.go

495 lines
9.3 KiB
Go

package yaml
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAppendProcessor_ProcessMap(t *testing.T) {
tests := []struct {
name string
input map[string]any
expected map[string]any
wantErr bool
}{
{
name: "simple append to list",
input: map[string]any{
"values+": []any{"new-value"},
},
expected: map[string]any{
"values": []any{"new-value"},
},
},
{
name: "nested append",
input: map[string]any{
"config": map[string]any{
"items+": []any{"item1", "item2"},
},
},
expected: map[string]any{
"config": map[string]any{
"items": []any{"item1", "item2"},
},
},
},
{
name: "mixed regular and append keys",
input: map[string]any{
"name": "test",
"values+": []any{"value1"},
"config": map[string]any{
"enabled": true,
"items+": []any{"item1"},
},
},
expected: map[string]any{
"name": "test",
"values": []any{"value1"},
"config": map[string]any{
"enabled": true,
"items": []any{"item1"},
},
},
},
{
name: "non-list append value",
input: map[string]any{
"key+": "value",
},
expected: map[string]any{
"key": "value",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
processor := NewAppendProcessor()
result, err := processor.ProcessMap(tt.input)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, result)
})
}
}
func TestAppendProcessor_MergeWithAppend(t *testing.T) {
tests := []struct {
name string
dest map[string]any
src map[string]any
expected map[string]any
wantErr bool
}{
{
name: "append to existing list",
dest: map[string]any{
"values": []any{"existing"},
},
src: map[string]any{
"values+": []any{"new"},
},
expected: map[string]any{
"values": []any{"existing", "new"},
},
},
{
name: "append to non-existent list",
dest: map[string]any{
"other": "value",
},
src: map[string]any{
"values+": []any{"new"},
},
expected: map[string]any{
"other": "value",
"values": []any{"new"},
},
},
{
name: "nested append",
dest: map[string]any{
"config": map[string]any{
"items": []any{"existing"},
},
},
src: map[string]any{
"config": map[string]any{
"items+": []any{"new"},
},
},
expected: map[string]any{
"config": map[string]any{
"items": []any{"existing", "new"},
},
},
},
{
name: "scalar with key+ treated as regular key (replace)",
dest: map[string]any{
"replicas": 2,
},
src: map[string]any{
"replicas+": 1,
},
expected: map[string]any{
"replicas": 1,
},
},
{
name: "map with key+ treated as regular key (replace)",
dest: map[string]any{
"resources": map[string]any{
"limits": map[string]any{
"memory": "256Mi",
"cpu": "200m",
},
},
},
src: map[string]any{
"resources+": map[string]any{
"requests": map[string]any{
"memory": "128Mi",
"cpu": "100m",
},
},
},
expected: map[string]any{
"resources": map[string]any{
"requests": map[string]any{
"memory": "128Mi",
"cpu": "100m",
},
},
},
},
{
name: "complex nested merge with key+ syntax for lists only",
dest: map[string]any{
"replicas": 2,
"resources": map[string]any{
"limits": map[string]any{
"memory": "256Mi",
"cpu": "200m",
},
"requests": map[string]any{
"memory": "128Mi",
"cpu": "100m",
},
},
"service": map[string]any{
"type": "ClusterIP",
"port": 80,
},
"kube-state-metrics": map[string]any{
"prometheus": map[string]any{
"metricsRelabel": []any{
map[string]any{"action": "drop"},
},
},
},
},
src: map[string]any{
"replicas+": 1,
"resources+": map[string]any{
"limits": map[string]any{
"memory": "512Mi",
"cpu": "500m",
},
"requests": map[string]any{
"memory": "256Mi",
"cpu": "250m",
},
},
"service+": map[string]any{
"type": "LoadBalancer",
"port": 443,
"annotations": map[string]any{
"service.beta.kubernetes.io/aws-load-balancer-type": "nlb",
},
},
"kube-state-metrics": map[string]any{
"prometheus": map[string]any{
"metricsRelabel+": []any{
map[string]any{"action": "keep"},
},
},
},
},
expected: map[string]any{
"replicas": 1,
"resources": map[string]any{
"limits": map[string]any{
"memory": "512Mi",
"cpu": "500m",
},
"requests": map[string]any{
"memory": "256Mi",
"cpu": "250m",
},
},
"service": map[string]any{
"type": "LoadBalancer",
"port": 443,
"annotations": map[string]any{
"service.beta.kubernetes.io/aws-load-balancer-type": "nlb",
},
},
"kube-state-metrics": map[string]any{
"prometheus": map[string]any{
"metricsRelabel": []any{
map[string]any{"action": "drop"},
map[string]any{"action": "keep"},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
processor := NewAppendProcessor()
err := processor.MergeWithAppend(tt.dest, tt.src)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, tt.dest)
})
}
}
func TestUnmarshalWithAppend(t *testing.T) {
tests := []struct {
name string
yamlData string
expected map[string]any
wantErr bool
}{
{
name: "simple append syntax",
yamlData: `
values+:
- item1
- item2
name: test
`,
expected: map[string]any{
"values": []any{"item1", "item2"},
"name": "test",
},
},
{
name: "nested append syntax",
yamlData: `
config:
items+:
- existing
- new
enabled: true
`,
expected: map[string]any{
"config": map[string]any{
"items": []any{"existing", "new"},
"enabled": true,
},
},
},
{
name: "complex values file with key+ syntax",
yamlData: `
replicas+: 1
resources+:
limits:
memory: 512Mi
cpu: 500m
requests:
memory: 256Mi
cpu: 250m
service+:
type: LoadBalancer
port: 443
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
kube-state-metrics:
prometheus:
metricsRelabel+:
- action: keep
`,
expected: map[string]any{
"replicas": 1,
"resources": map[string]any{
"limits": map[string]any{
"memory": "512Mi",
"cpu": "500m",
},
"requests": map[string]any{
"memory": "256Mi",
"cpu": "250m",
},
},
"service": map[string]any{
"type": "LoadBalancer",
"port": 443,
"annotations": map[string]any{
"service.beta.kubernetes.io/aws-load-balancer-type": "nlb",
},
},
"kube-state-metrics": map[string]any{
"prometheus": map[string]any{
"metricsRelabel": []any{
map[string]any{"action": "keep"},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var result map[string]any
err := UnmarshalWithAppend([]byte(tt.yamlData), &result)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsAppendKey(t *testing.T) {
tests := []struct {
key string
expected bool
}{
{"key+", true},
{"key", false},
{"key++", true},
{"+key", false},
{"", false},
}
for _, tt := range tests {
t.Run(tt.key, func(t *testing.T) {
result := IsAppendKey(tt.key)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetBaseKey(t *testing.T) {
tests := []struct {
key string
expected string
}{
{"key+", "key"},
{"key", "key"},
{"key++", "key+"},
{"+key", "+key"},
{"", ""},
}
for _, tt := range tests {
t.Run(tt.key, func(t *testing.T) {
result := GetBaseKey(tt.key)
assert.Equal(t, tt.expected, result)
})
}
}
func TestAppendProcessor_ErrorCases(t *testing.T) {
tests := []struct {
name string
input map[string]any
wantErr bool
}{
{
name: "invalid map with non-string key",
input: map[string]any{
"valid": map[any]any{
123: "invalid", // non-string key
},
},
wantErr: true,
},
{
name: "valid map with string keys",
input: map[string]any{
"valid": map[string]any{
"key": "value",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
processor := NewAppendProcessor()
_, err := processor.ProcessMap(tt.input)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestUnmarshalWithAppend_ErrorCases(t *testing.T) {
tests := []struct {
name string
yamlData string
wantErr bool
}{
{
name: "invalid YAML",
yamlData: `
invalid: yaml: content
- missing: proper: structure
`,
wantErr: true,
},
{
name: "valid YAML with key+",
yamlData: `
valid: true
key+: value
`,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var result map[string]any
err := UnmarshalWithAppend([]byte(tt.yamlData), &result)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}