package state import ( "reflect" "testing" "errors" "strings" ) func TestReadFromYaml(t *testing.T) { yamlFile := "example/path/to/yaml/file" yamlContent := []byte(`releases: - name: myrelease namespace: mynamespace chart: mychart `) state, err := readFromYaml(yamlContent, yamlFile) if err != nil { t.Errorf("unxpected error: %v", err) } if state.Releases[0].Name != "myrelease" { t.Errorf("unexpected release name: expected=myrelease actual=%s", state.Releases[0].Name) } if state.Releases[0].Namespace != "mynamespace" { t.Errorf("unexpected chart namespace: expected=mynamespace actual=%s", state.Releases[0].Chart) } if state.Releases[0].Chart != "mychart" { t.Errorf("unexpected chart name: expected=mychart actual=%s", state.Releases[0].Chart) } } func TestReadFromYaml_DeprecatedReleaseReferences(t *testing.T) { yamlFile := "example/path/to/yaml/file" yamlContent := []byte(`charts: - name: myrelease chart: mychart `) state, err := readFromYaml(yamlContent, yamlFile) if err != nil { t.Errorf("unxpected error: %v", err) } if state.Releases[0].Name != "myrelease" { t.Errorf("unexpected release name: expected=myrelease actual=%s", state.Releases[0].Name) } if state.Releases[0].Chart != "mychart" { t.Errorf("unexpected chart name: expected=mychart actual=%s", state.Releases[0].Chart) } } func TestReadFromYaml_ConflictingReleasesConfig(t *testing.T) { yamlFile := "example/path/to/yaml/file" yamlContent := []byte(`charts: - name: myrelease1 chart: mychart1 releases: - name: myrelease2 chart: mychart2 `) _, err := readFromYaml(yamlContent, yamlFile) if err == nil { t.Error("expected error") } } func TestReadFromYaml_FilterReleasesOnLabels(t *testing.T) { yamlFile := "example/path/to/yaml/file" yamlContent := []byte(`releases: - name: myrelease1 chart: mychart1 labels: tier: frontend foo: bar - name: myrelease2 chart: mychart2 labels: tier: frontend - name: myrelease3 chart: mychart3 labels: tier: backend `) cases := []struct { filter LabelFilter results []bool }{ {LabelFilter{positiveLabels: map[string]string{"tier": "frontend"}}, []bool{true, true, false}}, {LabelFilter{positiveLabels: map[string]string{"tier": "frontend", "foo": "bar"}}, []bool{true, false, false}}, {LabelFilter{negativeLabels: map[string]string{"tier": "frontend"}}, []bool{false, false, true}}, {LabelFilter{positiveLabels: map[string]string{"tier": "frontend"}, negativeLabels: map[string]string{"foo": "bar"}}, []bool{false, true, false}}, } state, err := readFromYaml(yamlContent, yamlFile) if err != nil { t.Errorf("unexpected error: %v", err) } for idx, c := range cases { for idx2, expected := range c.results { if f := c.filter.Match(state.Releases[idx2]); f != expected { t.Errorf("[case: %d][outcome: %d] Unexpected outcome wanted %t, got %t", idx, idx2, expected, f) } } } } func TestLabelParsing(t *testing.T) { cases := []struct { labelString string expectedFilter LabelFilter errorExected bool }{ {"foo=bar", LabelFilter{positiveLabels: map[string]string{"foo": "bar"}, negativeLabels: map[string]string{}}, false}, {"foo!=bar", LabelFilter{positiveLabels: map[string]string{}, negativeLabels: map[string]string{"foo": "bar"}}, false}, {"foo!=bar,baz=bat", LabelFilter{positiveLabels: map[string]string{"baz": "bat"}, negativeLabels: map[string]string{"foo": "bar"}}, false}, {"foo", LabelFilter{positiveLabels: map[string]string{}, negativeLabels: map[string]string{}}, true}, {"foo!=bar=baz", LabelFilter{positiveLabels: map[string]string{}, negativeLabels: map[string]string{}}, true}, {"=bar", LabelFilter{positiveLabels: map[string]string{}, negativeLabels: map[string]string{}}, true}, } for idx, c := range cases { filter, err := ParseLabels(c.labelString) if err != nil && !c.errorExected { t.Errorf("[%d] Didn't expect an error parsing labels: %s", idx, err) } else if err == nil && c.errorExected { t.Errorf("[%d] Expected %s to result in an error but got none", idx, c.labelString) } else if !reflect.DeepEqual(filter, c.expectedFilter) { t.Errorf("[%d] parsed label did not result in expected filter: %v", idx, filter) } } } func TestHelmState_applyDefaultsTo(t *testing.T) { type fields struct { BaseChartPath string Context string DeprecatedReleases []ReleaseSpec Namespace string Repositories []RepositorySpec Releases []ReleaseSpec } type args struct { spec ReleaseSpec } specWithNamespace := ReleaseSpec{ Chart: "test/chart", Version: "0.1", Verify: false, Name: "test-charts", Namespace: "test-namespace", Values: nil, SetValues: nil, EnvValues: nil, } specWithoutNamespace := specWithNamespace specWithoutNamespace.Namespace = "" specWithNamespaceFromFields := specWithNamespace specWithNamespaceFromFields.Namespace = "test-namespace-field" fieldsWithNamespace := fields{ BaseChartPath: ".", Context: "test_context", DeprecatedReleases: nil, Namespace: specWithNamespaceFromFields.Namespace, Repositories: nil, Releases: []ReleaseSpec{ specWithNamespace, }, } fieldsWithoutNamespace := fieldsWithNamespace fieldsWithoutNamespace.Namespace = "" tests := []struct { name string fields fields args args want ReleaseSpec }{ { name: "Has a namespace from spec", fields: fieldsWithoutNamespace, args: args{ spec: specWithNamespace, }, want: specWithNamespace, }, { name: "Has a namespace from flags", fields: fieldsWithoutNamespace, args: args{ spec: specWithNamespace, }, want: specWithNamespace, }, { name: "Has a namespace from flags and from spec", fields: fieldsWithNamespace, args: args{ spec: specWithNamespace, }, want: specWithNamespaceFromFields, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { state := &HelmState{ BaseChartPath: tt.fields.BaseChartPath, Context: tt.fields.Context, DeprecatedReleases: tt.fields.DeprecatedReleases, Namespace: tt.fields.Namespace, Repositories: tt.fields.Repositories, Releases: tt.fields.Releases, } if got := state.applyDefaultsTo(tt.args.spec); !reflect.DeepEqual(got, tt.want) { t.Errorf("HelmState.applyDefaultsTo() = %v, want %v", got, tt.want) } }) } } func Test_isLocalChart(t *testing.T) { type args struct { chart string } tests := []struct { name string args args want bool }{ { name: "local chart", args: args{ chart: "./charts/nonstop", }, want: true, }, { name: "repo chart", args: args{ chart: "stable/genius", }, want: false, }, { name: "empty", args: args{ chart: "", }, want: false, }, { name: "parent local path", args: args{ chart: "../../dotty", }, want: true, }, { name: "parent-parent local path", args: args{ chart: "../../dotty", }, want: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := isLocalChart(tt.args.chart); got != tt.want { t.Errorf("isLocalChart() = %v, want %v", got, tt.want) } }) } } func Test_normalizeChart(t *testing.T) { type args struct { basePath string chart string } tests := []struct { name string args args want string }{ { name: "construct local chart path", args: args{ basePath: "/Users/jane/code/deploy/charts", chart: "./app", }, want: "/Users/jane/code/deploy/charts/app", }, { name: "repo path", args: args{ basePath: "/Users/jane/code/deploy/charts", chart: "remote/app", }, want: "remote/app", }, { name: "construct local chart path, parent dir", args: args{ basePath: "/Users/jane/code/deploy/charts", chart: "../app", }, want: "/Users/jane/code/deploy/app", }, { name: "too much parent levels", args: args{ basePath: "/src", chart: "../../app", }, want: "/app", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := normalizeChart(tt.args.basePath, tt.args.chart); got != tt.want { t.Errorf("normalizeChart() = %v, want %v", got, tt.want) } }) } } // mocking helmexec.Interface type mockHelmExec struct { charts []string } func (helm *mockHelmExec) UpdateDeps(chart string) error { if strings.Contains(chart, "error") { return errors.New("error") } helm.charts = append(helm.charts, chart) return nil } func (helm *mockHelmExec) SetExtraArgs(args ...string) { return } func (helm *mockHelmExec) AddRepo(name, repository, certfile, keyfile string) error { return nil } func (helm *mockHelmExec) UpdateRepo() error { return nil } func (helm *mockHelmExec) SyncRelease(name, chart string, flags ...string) error { return nil } func (helm *mockHelmExec) DiffRelease(name, chart string, flags ...string) error { return nil } func (helm *mockHelmExec) DeleteRelease(name string) error { return nil } func (helm *mockHelmExec) DecryptSecret(name string) (string, error) { return "", nil } func TestHelmState_UpdateDeps(t *testing.T) { state := &HelmState{ BaseChartPath: "/src", Releases: []ReleaseSpec{ { Chart: "./local", }, { Chart: "../local", }, { Chart: "../../local", }, { Chart: "published", }, { Chart: "published/deeper", }, { Chart: "./error", }, }, } want := []string{"/src/local", "/local", "/local"} helm := &mockHelmExec{} errs := state.UpdateDeps(helm) if !reflect.DeepEqual(helm.charts, want) { t.Errorf("HelmState.UpdateDeps() = %v, want %v", helm.charts, want) } if len(errs) != 1 { t.Errorf("HelmState.UpdateDeps() - expected an error, but got: %v", len(errs)) } }