Merge pull request #5768 from anujagrawal699/addedTests-pkg/helper/status.go-cronfederatedhpa.go

Added unit tests for status and cronfederatedhpa helper utilities
This commit is contained in:
karmada-bot 2024-12-02 17:38:07 +08:00 committed by GitHub
commit 064ccf1140
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 538 additions and 0 deletions

View File

@ -0,0 +1,168 @@
/*
Copyright 2024 The Karmada 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 helper
import (
"testing"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
autoscalingv1alpha1 "github.com/karmada-io/karmada/pkg/apis/autoscaling/v1alpha1"
)
func TestIsCronFederatedHPARuleSuspend(t *testing.T) {
tests := []struct {
name string
rule autoscalingv1alpha1.CronFederatedHPARule
expected bool
}{
{
name: "suspend is nil",
rule: autoscalingv1alpha1.CronFederatedHPARule{},
expected: false,
},
{
name: "suspend is true",
rule: autoscalingv1alpha1.CronFederatedHPARule{
Suspend: ptr.To(true),
},
expected: true,
},
{
name: "suspend is false",
rule: autoscalingv1alpha1.CronFederatedHPARule{
Suspend: ptr.To(false),
},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := IsCronFederatedHPARuleSuspend(tt.rule)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetCronFederatedHPASuccessHistoryLimits(t *testing.T) {
tests := []struct {
name string
rule autoscalingv1alpha1.CronFederatedHPARule
expected int
}{
{
name: "successful history limit is nil",
rule: autoscalingv1alpha1.CronFederatedHPARule{},
expected: 3,
},
{
name: "successful history limit is set to 5",
rule: autoscalingv1alpha1.CronFederatedHPARule{
SuccessfulHistoryLimit: ptr.To[int32](5),
},
expected: 5,
},
{
name: "successful history limit is set to 0",
rule: autoscalingv1alpha1.CronFederatedHPARule{
SuccessfulHistoryLimit: ptr.To[int32](0),
},
expected: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetCronFederatedHPASuccessHistoryLimits(tt.rule)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetCronFederatedHPAFailedHistoryLimits(t *testing.T) {
tests := []struct {
name string
rule autoscalingv1alpha1.CronFederatedHPARule
expected int
}{
{
name: "failed history limit is nil",
rule: autoscalingv1alpha1.CronFederatedHPARule{},
expected: 3,
},
{
name: "failed history limit is set to 5",
rule: autoscalingv1alpha1.CronFederatedHPARule{
FailedHistoryLimit: ptr.To[int32](5),
},
expected: 5,
},
{
name: "failed history limit is set to 0",
rule: autoscalingv1alpha1.CronFederatedHPARule{
FailedHistoryLimit: ptr.To[int32](0),
},
expected: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetCronFederatedHPAFailedHistoryLimits(tt.rule)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetCronFederatedHPAKey(t *testing.T) {
tests := []struct {
name string
cronFHPA *autoscalingv1alpha1.CronFederatedHPA
expected string
}{
{
name: "default namespace",
cronFHPA: &autoscalingv1alpha1.CronFederatedHPA{
ObjectMeta: metav1.ObjectMeta{
Name: "test-hpa",
Namespace: "default",
},
},
expected: "default/test-hpa",
},
{
name: "custom namespace",
cronFHPA: &autoscalingv1alpha1.CronFederatedHPA{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-hpa",
Namespace: "karmada-system",
},
},
expected: "karmada-system/custom-hpa",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetCronFederatedHPAKey(tt.cronFHPA)
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -0,0 +1,370 @@
/*
Copyright 2024 The Karmada 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 helper
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
func TestUpdateStatus(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
tests := []struct {
name string
setupObj *corev1.Pod
mutateStatus func(*corev1.Pod)
statusError bool
expectedOp controllerutil.OperationResult
expectedError string
}{
{
name: "successful status update",
setupObj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
Status: corev1.PodStatus{
Phase: corev1.PodPending,
},
},
mutateStatus: func(pod *corev1.Pod) {
pod.Status.Phase = corev1.PodRunning
},
statusError: false,
expectedOp: controllerutil.OperationResultUpdatedStatusOnly,
expectedError: "",
},
{
name: "status update error",
setupObj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
Status: corev1.PodStatus{
Phase: corev1.PodPending,
},
},
mutateStatus: func(pod *corev1.Pod) {
pod.Status.Phase = corev1.PodRunning
},
statusError: true,
expectedOp: controllerutil.OperationResultNone,
expectedError: "Internal error occurred: status update failed",
},
{
name: "no changes needed",
setupObj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
},
mutateStatus: func(_ *corev1.Pod) {
// No changes to status
},
statusError: false,
expectedOp: controllerutil.OperationResultNone,
expectedError: "",
},
{
name: "object not found",
setupObj: nil, // Not create the object
mutateStatus: func(pod *corev1.Pod) {
pod.Status.Phase = corev1.PodRunning
},
statusError: false,
expectedOp: controllerutil.OperationResultNone,
expectedError: "not found",
},
{
name: "mutation error",
setupObj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
},
mutateStatus: func(pod *corev1.Pod) {
// Simulate mutation error by changing name
pod.Name = "different-name"
},
statusError: false,
expectedOp: controllerutil.OperationResultNone,
expectedError: "cannot mutate object name",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clientBuilder := fake.NewClientBuilder().WithScheme(scheme)
if tt.setupObj != nil {
clientBuilder = clientBuilder.WithObjects(tt.setupObj)
}
fakeClient := clientBuilder.Build()
var client client.Client
if tt.statusError {
client = &mockClient{
Client: fakeClient,
shouldError: true,
}
} else {
client = fakeClient
}
// Create a new object for update
obj := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
}
// Run the update
op, err := UpdateStatus(context.TODO(), client, obj, func() error {
if tt.mutateStatus != nil {
tt.mutateStatus(obj)
}
return nil
})
// Check error
if tt.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
} else {
assert.NoError(t, err)
}
// Check operation result
assert.Equal(t, tt.expectedOp, op)
// If successful update, verify the status was actually changed
if tt.expectedOp == controllerutil.OperationResultUpdatedStatusOnly {
updatedPod := &corev1.Pod{}
err := client.Get(context.TODO(), types.NamespacedName{Name: "test-pod", Namespace: "default"}, updatedPod)
assert.NoError(t, err)
assert.Equal(t, corev1.PodRunning, updatedPod.Status.Phase)
}
})
}
}
func TestMutate(t *testing.T) {
tests := []struct {
name string
key types.NamespacedName
obj *corev1.Pod
mutateFn controllerutil.MutateFn
expectedError string
}{
{
name: "successful mutation with doing nothing",
key: types.NamespacedName{
Name: "test-pod",
Namespace: "default",
},
obj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
},
mutateFn: func() error {
return nil
},
expectedError: "",
},
{
name: "mutation function error",
key: types.NamespacedName{
Name: "test-pod",
Namespace: "default",
},
obj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
},
mutateFn: func() error {
return fmt.Errorf("mutation failed")
},
expectedError: "mutation failed",
},
{
name: "attempt to mutate object name",
key: types.NamespacedName{
Name: "test-pod",
Namespace: "default",
},
obj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
},
mutateFn: nil, // Will be set in the test
expectedError: "MutateFn cannot mutate object name and/or object namespace",
},
{
name: "attempt to mutate object namespace",
key: types.NamespacedName{
Name: "test-pod",
Namespace: "default",
},
obj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
},
mutateFn: nil, // Will be set in the test
expectedError: "MutateFn cannot mutate object name and/or object namespace",
},
{
name: "attempt to mutate both name and namespace",
key: types.NamespacedName{
Name: "test-pod",
Namespace: "default",
},
obj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
},
mutateFn: nil, // Will be set in the test
expectedError: "MutateFn cannot mutate object name and/or object namespace",
},
{
name: "successful mutation with labels and annotations",
key: types.NamespacedName{
Name: "test-pod",
Namespace: "default",
},
obj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
},
},
mutateFn: nil, // Will be set in the test
expectedError: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create local copy of the test object
testObj := tt.obj.DeepCopy()
// Set up mutation functions that operate on the local object
switch tt.name {
case "attempt to mutate object name":
tt.mutateFn = func() error {
testObj.SetName("modified-pod")
return nil
}
case "attempt to mutate object namespace":
tt.mutateFn = func() error {
testObj.SetNamespace("new-namespace")
return nil
}
case "attempt to mutate both name and namespace":
tt.mutateFn = func() error {
testObj.SetName("modified-pod")
testObj.SetNamespace("new-namespace")
return nil
}
case "successful mutation with labels and annotations":
tt.mutateFn = func() error {
testObj.SetLabels(map[string]string{"key": "value"})
testObj.SetAnnotations(map[string]string{"note": "test"})
return nil
}
}
err := mutate(tt.mutateFn, tt.key, testObj)
if tt.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
return
}
assert.NoError(t, err)
// Verify the object key hasn't changed for successful mutations
newKey := types.NamespacedName{
Name: testObj.GetName(),
Namespace: testObj.GetNamespace(),
}
assert.Equal(t, tt.key, newKey)
// For successful mutations, verify other metadata changes were applied
if testObj.GetLabels() != nil {
assert.Equal(t, "value", testObj.GetLabels()["key"])
}
if testObj.GetAnnotations() != nil {
assert.Equal(t, "test", testObj.GetAnnotations()["note"])
}
})
}
}
// Mock Implementations
// mockStatusWriter is a mock implementation of client.StatusWriter that returns an error
type mockStatusWriter struct {
client.StatusWriter
shouldError bool
}
func (m *mockStatusWriter) Update(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error {
if m.shouldError {
return apierrors.NewInternalError(fmt.Errorf("status update failed"))
}
return nil
}
// mockClient wraps the fake client and returns our mock status writer
type mockClient struct {
client.Client
shouldError bool
}
func (m *mockClient) Status() client.StatusWriter {
return &mockStatusWriter{shouldError: m.shouldError}
}