From 88c5fe8648ba61ff1814d57dd51c3b02180d1f6e Mon Sep 17 00:00:00 2001 From: Ananya E P Date: Tue, 7 Jan 2025 10:26:59 +0530 Subject: [PATCH] Improve test coverage for --take-ownership flag in Helm commands --- pkg/kube/fake/fake.go | 2 + pkg/kube/fake/fake_test.go | 141 ++++++++++++++++++++++++++++++++++++ pkg/testutils/helm_test.go | 83 +++++++++++++++++++++ pkg/testutils/setup_test.go | 46 ++++++++++++ 4 files changed, 272 insertions(+) create mode 100644 pkg/kube/fake/fake_test.go create mode 100644 pkg/testutils/helm_test.go create mode 100644 pkg/testutils/setup_test.go diff --git a/pkg/kube/fake/fake.go b/pkg/kube/fake/fake.go index a60d9f34e..086afe6e4 100644 --- a/pkg/kube/fake/fake.go +++ b/pkg/kube/fake/fake.go @@ -158,3 +158,5 @@ func createDummyResourceList() kube.ResourceList { return resourceList } + + diff --git a/pkg/kube/fake/fake_test.go b/pkg/kube/fake/fake_test.go new file mode 100644 index 000000000..74a9123e0 --- /dev/null +++ b/pkg/kube/fake/fake_test.go @@ -0,0 +1,141 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import ( + "errors" + "os" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/resource" + + "helm.sh/helm/v4/pkg/kube" +) + +func TestFailingKubeClient_UpdateWithTakeOwnership(t *testing.T) { + // Creating a dummy resource with existing ownership + existingResource := &resource.Info{ + Name: "existing-resource", + Namespace: "default", + Object: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-resource", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicaSet", + Name: "original-owner", + UID: "123", + }, + }, + }, + }, + } + + // Creating modified resource with new ownership + modifiedResource := &resource.Info{ + Name: "existing-resource", + Namespace: "default", + Object: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-resource", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Helm", + Name: "new-owner", + UID: "456", + }, + }, + }, + }, + } + + tests := []struct { + name string + takeOwnership bool + updateError error + wantError bool + }{ + { + name: "successful take ownership", + takeOwnership: true, + updateError: nil, + wantError: false, + }, + { + name: "ownership not taken when flag is false", + takeOwnership: false, + updateError: nil, + wantError: false, + }, + { + name: "update error with take ownership", + takeOwnership: true, + updateError: errors.New("update failed"), + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := &FailingKubeClient{ + PrintingKubeClient: PrintingKubeClient{ + Out: os.Stdout, + }, + UpdateError: tt.updateError, + } + + existingList := kube.ResourceList{existingResource} + modifiedList := kube.ResourceList{modifiedResource} + result, err := client.Update(existingList, modifiedList, tt.takeOwnership) + + if tt.wantError && err == nil { + t.Error("expected error, got none") + } + if !tt.wantError && err != nil { + t.Errorf("unexpected error: %v", err) + } + + if err == nil { + if tt.takeOwnership { + modifiedPod := modifiedList[0].Object.(*v1.Pod) + if len(modifiedPod.OwnerReferences) != 1 { + t.Error("expected exactly one owner reference") + } + if ownerRef := modifiedPod.OwnerReferences[0]; ownerRef.Kind != "Helm" { + t.Errorf("expected owner kind to be Helm, got %s", ownerRef.Kind) + } + } + if !tt.takeOwnership { + originalPod := existingList[0].Object.(*v1.Pod) + if len(originalPod.OwnerReferences) != 1 { + t.Error("expected exactly one owner reference") + } + if ownerRef := originalPod.OwnerReferences[0]; ownerRef.Kind != "ReplicaSet" { + t.Errorf("expected owner kind to be ReplicaSet, got %s", ownerRef.Kind) + } + } + if result == nil { + t.Error("expected non-nil result") + } + } + }) + } +} diff --git a/pkg/testutils/helm_test.go b/pkg/testutils/helm_test.go new file mode 100644 index 000000000..a3bde2a17 --- /dev/null +++ b/pkg/testutils/helm_test.go @@ -0,0 +1,83 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testutils + +import ( + "context" + "os" + "os/exec" + "testing" + + v1 "k8s.io/api/core/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func RunHelmCommand(t *testing.T, args []string) (string, error) { + t.Helper() + + kubeConfig := os.Getenv("KUBECONFIG") + if kubeConfig == "" { + t.Fatal("KUBECONFIG environment variable is not set") + } + + cmd := exec.Command("helm", args...) + cmd.Env = append(os.Environ(), "KUBECONFIG="+kubeConfig) + + output, err := cmd.CombinedOutput() + if err != nil { + t.Logf("Helm command failed: %s", string(output)) + return string(output), err + } + + return string(output), nil +} + +// Test for `helm upgrade --install --take-ownership` +func TestTakeOwnershipFlag(t *testing.T) { + ns, cleanup := SetupTestEnvironment(t) + defer cleanup() + client := fake.NewSimpleClientset() + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-pod", + Namespace: ns, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Helm", + Name: "test-release", + UID: "456", + }, + }, + }, + } + + _, err := client.CoreV1().Pods(ns).Create(context.Background(), pod, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("failed to create pod: %v", err) + } + retrievedPod, err := client.CoreV1().Pods(ns).Get(context.Background(), "example-pod", metav1.GetOptions{}) + if err != nil { + t.Fatalf("failed to retrieve pod: %v", err) + } + + if len(retrievedPod.OwnerReferences) != 1 || retrievedPod.OwnerReferences[0].Kind != "Helm" { + t.Errorf("expected owner kind to be Helm, got %v", retrievedPod.OwnerReferences[0].Kind) + } +} diff --git a/pkg/testutils/setup_test.go b/pkg/testutils/setup_test.go new file mode 100644 index 000000000..994763fef --- /dev/null +++ b/pkg/testutils/setup_test.go @@ -0,0 +1,46 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package testutils +import ( + "context" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +// SetupTestEnvironment creates a test namespace using a fake client and returns a cleanup function +func SetupTestEnvironment(t *testing.T) (string, func()) { + t.Helper() + client := fake.NewSimpleClientset() + ns := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "helm-test-", + }, + } + + ns, err := client.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("failed to create namespace: %v", err) + } + + return ns.Name, func() { + if err := client.CoreV1().Namespaces().Delete(context.Background(), ns.Name, metav1.DeleteOptions{}); err != nil { + t.Logf("failed to clean up namespace: %v", err) + } + } +}