Files
helmfile/pkg/state/chart_dependency_test.go
T
yxxhero dac42105dd fix: deduplicate chart dependencies in helmfile.lock (#2567)
When multiple releases reference the same chart with the same name,
repository, and version, helmfile deps would write duplicate entries
to helmfile.lock. This adds deduplication of resolved dependencies
after sorting and before writing the lock file.

Fixes #2562

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-05-01 20:29:24 +08:00

229 lines
5.9 KiB
Go

package state
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestDedupResolvedDependencies(t *testing.T) {
tests := []struct {
name string
input []ResolvedChartDependency
expected []ResolvedChartDependency
}{
{
name: "no duplicates",
input: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "redis", Repository: "https://charts.bitnami.com/bitnami", Version: "17.0.7"},
},
expected: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "redis", Repository: "https://charts.bitnami.com/bitnami", Version: "17.0.7"},
},
},
{
name: "duplicates removed",
input: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
},
expected: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
},
},
{
name: "same chart different versions kept",
input: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.1"},
},
expected: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.1"},
},
},
{
name: "same chart different repos kept",
input: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://other.com", Version: "4.6.2"},
},
expected: []ResolvedChartDependency{
{ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"},
{ChartName: "app-template", Repository: "https://other.com", Version: "4.6.2"},
},
},
{
name: "empty input",
input: []ResolvedChartDependency{},
expected: []ResolvedChartDependency{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := dedupResolvedDependencies(tt.input)
require.Equal(t, tt.expected, result)
})
}
}
func TestGetUnresolvedDependenciess(t *testing.T) {
tests := []struct {
name string
helmState *HelmState
expectfile string
expectDeps *UnresolvedDependencies
}{
{
name: "oci chart",
helmState: &HelmState{
FilePath: "helmfile.yaml",
ReleaseSetSpec: ReleaseSetSpec{
Releases: []ReleaseSpec{
{
Name: "foo",
Chart: "chartsa/abc",
Version: "0.1.0",
Namespace: "ns1",
},
{
Name: "empty",
Chart: "chartsb/empty",
Namespace: "ns2",
},
{
Name: "empty",
Chart: "chartsb/empty",
},
},
Repositories: []RepositorySpec{
{
Name: "chartsa",
URL: "localhost:5000/aaa",
OCI: true,
},
{
Name: "chartsb",
URL: "localhost:5000/bbb",
},
},
},
},
expectfile: "helmfile",
expectDeps: &UnresolvedDependencies{
deps: map[string][]unresolvedChartDependency{
"abc": {
{
ChartName: "abc",
Repository: "oci://localhost:5000/aaa",
VersionConstraint: "0.1.0",
Alias: "ns1-foo",
},
},
"empty": {
{
ChartName: "empty",
Repository: "localhost:5000/bbb",
Alias: "ns2-empty",
},
{
ChartName: "empty",
Repository: "localhost:5000/bbb",
Alias: "-empty",
},
},
},
},
},
{
name: "duplicate charts are differentiated by alias",
helmState: &HelmState{
FilePath: "helmfile.yaml",
ReleaseSetSpec: ReleaseSetSpec{
Releases: []ReleaseSpec{
{
Name: "foo",
Chart: "myrepo/abc",
Version: "> 0.2.0",
Namespace: "ns1",
},
{
Name: "bar",
Chart: "myrepo/abc",
Version: "0.1.0",
Namespace: "ns2",
},
{
Name: "baz",
Chart: "myrepo/abc",
Version: "0.3.0",
},
},
Repositories: []RepositorySpec{
{
Name: "myrepo",
URL: "localhost:5000/aaa",
},
},
},
},
expectfile: "helmfile",
expectDeps: &UnresolvedDependencies{
deps: map[string][]unresolvedChartDependency{
"abc": {
{
ChartName: "abc",
Repository: "localhost:5000/aaa",
VersionConstraint: "> 0.2.0",
Alias: "ns1-foo",
},
{
ChartName: "abc",
Repository: "localhost:5000/aaa",
VersionConstraint: "0.1.0",
Alias: "ns2-bar",
},
{
ChartName: "abc",
Repository: "localhost:5000/aaa",
VersionConstraint: "0.3.0",
Alias: "-baz",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, ds := getUnresolvedDependenciess(tt.helmState)
require.Equalf(t, tt.expectfile, f, "getUnresolvedDependenciess() expect file %s, got %s", tt.expectfile, f)
require.Equalf(t, tt.expectDeps, ds, "getUnresolvedDependenciess() expect deps %v, got %v", tt.expectDeps, ds)
})
}
}
func TestChartDependenciesAlias(t *testing.T) {
type testCase struct {
releaseName string
namespace string
expected string
}
testCases := []testCase{
{"release1", "n1", "n1-release1"},
{"release2", "n2", "n2-release2"},
{"empty", "", "-empty"},
}
for _, tc := range testCases {
result := chartDependenciesAlias(tc.namespace, tc.releaseName)
if result != tc.expected {
t.Errorf("Expected %s, but got %s", tc.expected, result)
}
}
}