From afed23e3359c2fd1cd4bd6d4987e1442a0a706e1 Mon Sep 17 00:00:00 2001 From: Ashmit Bhardwaj Date: Wed, 11 Jun 2025 15:40:18 +0000 Subject: [PATCH] added pkg/action test files Signed-off-by: Ashmit Bhardwaj --- pkg/action/get_values_test.go | 134 ++++++++++++++++++++++++ pkg/action/pull_test.go | 175 ++++++++++++++++++++++++++++++++ pkg/action/status_test.go | 186 ++++++++++++++++++++++++++++++++++ 3 files changed, 495 insertions(+) create mode 100644 pkg/action/get_values_test.go create mode 100644 pkg/action/pull_test.go create mode 100644 pkg/action/status_test.go diff --git a/pkg/action/get_values_test.go b/pkg/action/get_values_test.go new file mode 100644 index 000000000..f2c3872ad --- /dev/null +++ b/pkg/action/get_values_test.go @@ -0,0 +1,134 @@ +package action + +import ( + "io" + "testing" + + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" +) + +func TestNewGetValues(t *testing.T) { + cfg := &Configuration{} + gv := NewGetValues(cfg) + if gv.cfg != cfg { + t.Errorf("Expected cfg to be %v, got %v", cfg, gv.cfg) + } +} + +func TestGetValues_Run(t *testing.T) { + tests := []struct { + name string + release *release.Release + allValues bool + version int + wantErr bool + }{ + { + name: "get default values", + release: &release.Release{ + Name: "test-release", + Version: 1, + Config: map[string]interface{}{ + "key": "value", + }, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "test-chart", Version: "0.1.0"}, + Values: map[string]interface{}{ + "default": "value", + }, + }, + Info: &release.Info{}, + }, + allValues: false, + version: 1, + wantErr: false, + }, + { + name: "get all values", + release: &release.Release{ + Name: "test-release", + Version: 1, + Config: map[string]interface{}{ + "key": "value", + }, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "test-chart", Version: "0.1.0"}, + Values: map[string]interface{}{ + "default": "value", + }, + }, + Info: &release.Info{}, + }, + allValues: true, + version: 1, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + memDriver := driver.NewMemory() + store := storage.Init(memDriver) + // Insert the release into storage + if err := store.Create(tt.release); err != nil { + t.Fatalf("failed to insert release: %v", err) + } + + cfg := &Configuration{ + KubeClient: &MockKubeClient{}, + Releases: store, + } + gv := NewGetValues(cfg) + gv.AllValues = tt.allValues + gv.Version = tt.version + + got, err := gv.Run("test-release") + if (err != nil) != tt.wantErr { + t.Errorf("GetValues.Run() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.allValues { + expected, _ := chartutil.CoalesceValues(tt.release.Chart, tt.release.Config) + if !compareMaps(got, expected) { + t.Errorf("GetValues.Run() = %v, want %v", got, expected) + } + } else { + if !compareMaps(got, tt.release.Config) { + t.Errorf("GetValues.Run() = %v, want %v", got, tt.release.Config) + } + } + }) + } +} + +// MockKubeClient is a minimal mock implementation of the kube.Interface +// Only IsReachable is implemented for this test. +type MockKubeClient struct{} + +func (m *MockKubeClient) IsReachable() error { return nil } +func (m *MockKubeClient) Create(kube.ResourceList) (*kube.Result, error) { return nil, nil } +func (m *MockKubeClient) Delete(kube.ResourceList) (*kube.Result, []error) { return nil, nil } +func (m *MockKubeClient) Update(kube.ResourceList, kube.ResourceList, bool) (*kube.Result, error) { + return nil, nil +} +func (m *MockKubeClient) Build(io.Reader, bool) (kube.ResourceList, error) { return nil, nil } +func (m *MockKubeClient) GetWaiter(kube.WaitStrategy) (kube.Waiter, error) { return nil, nil } + +// Helper function to compare maps +func compareMaps(a, b map[string]interface{}) bool { + if len(a) != len(b) { + return false + } + for k, v := range a { + if b[k] != v { + return false + } + } + return true +} diff --git a/pkg/action/pull_test.go b/pkg/action/pull_test.go new file mode 100644 index 000000000..29a1391b1 --- /dev/null +++ b/pkg/action/pull_test.go @@ -0,0 +1,175 @@ +package action + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/registry" +) + +func TestNewPull(t *testing.T) { + cfg := &Configuration{} + p := NewPull(WithConfig(cfg)) + assert.NotNil(t, p) + assert.Equal(t, cfg, p.cfg) +} + +func TestPull_SetRegistryClient(t *testing.T) { + cfg := &Configuration{} + p := NewPull(WithConfig(cfg)) + + regClient, err := registry.NewClient() + assert.NoError(t, err) + + p.SetRegistryClient(regClient) + assert.Equal(t, regClient, p.cfg.RegistryClient) +} + +func TestPull_Run(t *testing.T) { + // Create a temporary directory for testing + tmpDir, err := os.MkdirTemp("", "helm-pull-test-*") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + + // Create test settings + settings := cli.New() + settings.RepositoryConfig = filepath.Join(tmpDir, "repositories.yaml") + settings.RepositoryCache = filepath.Join(tmpDir, "cache") + + // Create test configuration + cfg := &Configuration{} + regClient, err := registry.NewClient() + assert.NoError(t, err) + cfg.RegistryClient = regClient + + // Create repositories.yaml + err = os.MkdirAll(filepath.Dir(settings.RepositoryConfig), 0755) + assert.NoError(t, err) + err = os.WriteFile(settings.RepositoryConfig, []byte{}, 0644) + assert.NoError(t, err) + + // Create cache directory + err = os.MkdirAll(settings.RepositoryCache, 0755) + assert.NoError(t, err) + + tests := []struct { + name string + chartRef string + opts []PullOpt + wantErr bool + setupFunc func(*Pull) + }{ + { + name: "invalid chart reference", + chartRef: "invalid-chart", + opts: []PullOpt{WithConfig(cfg)}, + setupFunc: func(p *Pull) { + p.Settings = settings + }, + wantErr: true, + }, + { + name: "with untar option", + chartRef: "test-chart", + opts: []PullOpt{ + WithConfig(cfg), + }, + setupFunc: func(p *Pull) { + p.Untar = true + p.UntarDir = tmpDir + p.DestDir = tmpDir + p.Settings = settings + }, + wantErr: true, // Will fail because chart doesn't exist, but we test the setup + }, + { + name: "with verify option", + chartRef: "test-chart", + opts: []PullOpt{ + WithConfig(cfg), + }, + setupFunc: func(p *Pull) { + p.Verify = true + p.Settings = settings + }, + wantErr: true, // Will fail because chart doesn't exist, but we test the setup + }, + { + name: "with repo URL", + chartRef: "test-chart", + opts: []PullOpt{ + WithConfig(cfg), + }, + setupFunc: func(p *Pull) { + p.RepoURL = "https://example.com/charts" + p.Settings = settings + }, + wantErr: true, // Will fail because repo doesn't exist, but we test the setup + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewPull(tt.opts...) + if tt.setupFunc != nil { + tt.setupFunc(p) + } + + _, err := p.Run(tt.chartRef) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestPull_Run_WithValidChart(t *testing.T) { + // Create a temporary directory for testing + tmpDir, err := os.MkdirTemp("", "helm-pull-test-*") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + + // Create test settings + settings := cli.New() + settings.RepositoryConfig = filepath.Join(tmpDir, "repositories.yaml") + settings.RepositoryCache = filepath.Join(tmpDir, "cache") + + // Create test configuration + cfg := &Configuration{} + regClient, err := registry.NewClient() + assert.NoError(t, err) + cfg.RegistryClient = regClient + + // Create repositories.yaml + err = os.MkdirAll(filepath.Dir(settings.RepositoryConfig), 0755) + assert.NoError(t, err) + err = os.WriteFile(settings.RepositoryConfig, []byte{}, 0644) + assert.NoError(t, err) + + // Create cache directory + err = os.MkdirAll(settings.RepositoryCache, 0755) + assert.NoError(t, err) + + // Create a test chart in the cache + chartDir := filepath.Join(settings.RepositoryCache, "charts") + err = os.MkdirAll(chartDir, 0755) + assert.NoError(t, err) + + // Create a test chart file + chartFile := filepath.Join(chartDir, "test-chart-0.1.0.tgz") + err = os.WriteFile(chartFile, []byte("test chart content"), 0644) + assert.NoError(t, err) + + // Test pulling the chart + p := NewPull(WithConfig(cfg)) + p.Settings = settings + p.DestDir = tmpDir + + _, err = p.Run("test-chart") + assert.Error(t, err) // Will fail because it's not a valid chart, but we test the setup +} diff --git a/pkg/action/status_test.go b/pkg/action/status_test.go new file mode 100644 index 000000000..ebc2ced98 --- /dev/null +++ b/pkg/action/status_test.go @@ -0,0 +1,186 @@ +package action + +import ( + "errors" + "io" + "testing" + + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" + "helm.sh/helm/v4/pkg/time" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + ErrNotReachable = errors.New("kubernetes cluster is not reachable") + ErrInvalidManifest = errors.New("invalid manifest") + ErrResourceNotFound = errors.New("resource not found") +) + +type mockKubeClient struct { + reachable bool + buildErr error + getErr error + resources kube.ResourceList + getResp map[string][]interface{} +} + +var _ kube.InterfaceResources = &mockKubeClient{} + +func (m *mockKubeClient) IsReachable() error { + if !m.reachable { + return ErrNotReachable + } + return nil +} + +func (m *mockKubeClient) Build(manifest io.Reader, validate bool) (kube.ResourceList, error) { + if m.buildErr != nil { + return nil, m.buildErr + } + return m.resources, nil +} + +func (m *mockKubeClient) Get(resources kube.ResourceList, related bool) (map[string][]runtime.Object, error) { + if m.getErr != nil { + return nil, m.getErr + } + return map[string][]runtime.Object{"Service": {}}, nil +} + +func (m *mockKubeClient) BuildTable(manifest io.Reader, validate bool) (kube.ResourceList, error) { + return m.Build(manifest, validate) +} + +func (m *mockKubeClient) Create(resources kube.ResourceList) (*kube.Result, error) { + return &kube.Result{Created: resources}, nil +} + +func (m *mockKubeClient) Delete(resources kube.ResourceList) (*kube.Result, []error) { + return &kube.Result{Deleted: resources}, nil +} + +func (m *mockKubeClient) Update(original, target kube.ResourceList, force bool) (*kube.Result, error) { + return &kube.Result{Updated: target}, nil +} + +func (m *mockKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) { + return nil, nil +} + +func TestStatus(t *testing.T) { + tests := []struct { + name string + releaseName string + version int + reachable bool + buildErr error + getErr error + expectedErr bool + expectedStatus *release.Release + }{ + { + name: "successful status check", + releaseName: "test-release", + version: 1, + reachable: true, + expectedStatus: &release.Release{ + Name: "test-release", + Namespace: "default", + Info: &release.Info{ + Status: release.StatusDeployed, + FirstDeployed: time.Now(), + LastDeployed: time.Now(), + }, + Version: 1, + Chart: &chart.Chart{}, + Manifest: "apiVersion: v1\nkind: Service\nmetadata:\n name: test-service", + }, + }, + { + name: "unreachable cluster", + releaseName: "test-release", + version: 1, + reachable: false, + expectedErr: true, + }, + { + name: "build error", + releaseName: "test-release", + version: 1, + reachable: true, + buildErr: ErrInvalidManifest, + expectedErr: true, + }, + { + name: "get error", + releaseName: "test-release", + version: 1, + reachable: true, + getErr: ErrResourceNotFound, + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock storage driver + store := storage.Init(driver.NewMemory()) + + // Create a mock kube client + mockClient := &mockKubeClient{ + reachable: tt.reachable, + buildErr: tt.buildErr, + getErr: tt.getErr, + } + + // Create configuration + cfg := &Configuration{ + Releases: store, + KubeClient: mockClient, + Capabilities: chartutil.DefaultCapabilities, + } + + // Create status action + status := NewStatus(cfg) + status.Version = tt.version + + // Store test release if needed + if tt.expectedStatus != nil { + err := store.Create(tt.expectedStatus) + if err != nil { + t.Fatalf("Failed to create test release: %v", err) + } + } + + // Run status check + got, err := status.Run(tt.releaseName) + + // Check error + if tt.expectedErr { + if err == nil { + t.Error("expected error but got none") + } + return + } + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + // Verify release + if got == nil { + t.Error("expected release but got nil") + return + } + + if got.Name != tt.expectedStatus.Name { + t.Errorf("expected release name %q, got %q", tt.expectedStatus.Name, got.Name) + } + }) + } +}