Skip to content

Commit

Permalink
[test] Unit-tests for common/k8s_utils.go (part 1 of 3) (#190)
Browse files Browse the repository at this point in the history
* test: Add unit-tests to ObjectInfo (#167)

* refactor: Add comment to ObjectInfo (#167)

* test: Add unit-test to ReadAnnotationsFromObject (#167)

* refactor: Add comment to ReadAnnotationsFromObject (#167)

* test: Add unit-test to TagObjectToDelete (#167)

* refactor: Add comment to TagObjectToDelete (#167)

* test: Add unit-tests to IsObjectTaggedToDelete (#167)

* refactor: Add comment to IsObjectTaggedToDelete (#167)

* refactor: run goimporst to pass the code-style check (#167)

* refactor: Rename 'Kind' fields according to CamelCase naming convention

* refactor: Simplify comment to ObjectInfo (#167)

* refactor: Compare maps with DeepEqual instead of looping in TestTagObjectToDelete (#167)

* refactor: Clean the tests for ObjectInfo (#167)

- Deleted redundant test-case (#190 (comment))

- Generalized the names of objects to 'Kubernetes object' in test-case names

kudos to @guicassolato!
  • Loading branch information
art-tapin authored and didierofrivia committed May 31, 2023
1 parent a07a4db commit cbf0d40
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 0 deletions.
9 changes: 9 additions & 0 deletions pkg/common/k8s_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ const (
ReadyStatusConditionType = "Ready"
)

// ObjectInfo generates a string representation of the provided Kubernetes object, including its kind and name.
// The generated string follows the format: "kind/name".
func ObjectInfo(obj client.Object) string {
return fmt.Sprintf("%s/%s", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName())
}

// ReadAnnotationsFromObject reads the annotations from a Kubernetes object
// and returns them as a map. If the object has no annotations, it returns an empty map.
func ReadAnnotationsFromObject(obj client.Object) map[string]string {
annotations := obj.GetAnnotations()
if annotations == nil {
Expand All @@ -47,6 +51,8 @@ func ReadAnnotationsFromObject(obj client.Object) map[string]string {
return annotations
}

// TagObjectToDelete adds a special DeleteTagAnnotation to the object's annotations.
// If the object's annotations are nil, it first initializes the Annotations field with an empty map.
func TagObjectToDelete(obj client.Object) {
// Add custom annotation
annotations := obj.GetAnnotations()
Expand All @@ -57,6 +63,9 @@ func TagObjectToDelete(obj client.Object) {
annotations[DeleteTagAnnotation] = "true"
}

// IsObjectTaggedToDelete checks if the given object is tagged for deletion.
// It looks for the DeleteTagAnnotation in the object's annotations
// and returns true if the annotation value is set to "true", false otherwise.
func IsObjectTaggedToDelete(obj client.Object) bool {
annotations := obj.GetAnnotations()
if annotations == nil {
Expand Down
222 changes: 222 additions & 0 deletions pkg/common/k8s_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ package common

import (
"context"
"reflect"
"testing"

"github.com/kuadrant/limitador-operator/api/v1alpha1"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -137,3 +140,222 @@ func TestGetServiceWorkloadSelector(t *testing.T) {
t.Error("should have failed to get the service workload selector")
}
}

func TestObjectInfo(t *testing.T) {
testCases := []struct {
name string
input client.Object
expected string
}{
{
name: "when given a Kubernetes object then return formatted string",
input: &v1alpha1.Limitador{
TypeMeta: metav1.TypeMeta{
Kind: "Limitador",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-limitador",
},
},
expected: "Limitador/test-limitador",
},
{
name: "when given a Kubernetes object with empty Kind then return formatted string",
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test-service",
},
},
expected: "/test-service",
},
{
name: "when given a Kubernetes object with empty Name then return formatted string",
input: &corev1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
},
ObjectMeta: metav1.ObjectMeta{
Name: "",
},
},
expected: "Namespace/",
},
{
name: "when given empty Kubernetes object then return formatted string (separator only)",
input: &corev1.Pod{},
expected: "/",
},
}

for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
if actual := ObjectInfo(c.input); actual != c.expected {
t.Errorf("Expected %q, got %q", c.expected, actual)
}
})
}
}

func TestReadAnnotationsFromObject(t *testing.T) {
testCases := []struct {
name string
input client.Object
expected map[string]string
}{
{
name: "when object has annotations then return the annotations",
input: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
},
expected: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
{
name: "when object has no annotations then return an empty map",
input: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{},
},
expected: map[string]string{},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := ReadAnnotationsFromObject(tc.input)
if !reflect.DeepEqual(actual, tc.expected) {
t.Errorf("Expected annotations %v, but got %v", tc.expected, actual)
}
})
}
}

func TestTagObjectToDelete(t *testing.T) {
testCases := []struct {
name string
input client.Object
expectedTags map[string]string
}{
{
name: "when object has no annotations (nil) then initialize them with empty map and add 'delete' tag",
input: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Annotations: nil,
},
},
expectedTags: map[string]string{
DeleteTagAnnotation: "true",
},
},
{
name: "when object has empty annotations (empty map) then add 'delete' tag",
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test-service",
Annotations: map[string]string{},
},
},
expectedTags: map[string]string{
DeleteTagAnnotation: "true",
},
},
{
name: "when object already has annotations then add 'delete' tag",
input: &v1alpha1.Limitador{
ObjectMeta: metav1.ObjectMeta{
Name: "test-service",
Annotations: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
},
expectedTags: map[string]string{
DeleteTagAnnotation: "true",
"key1": "value1",
"key2": "value2",
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
TagObjectToDelete(tc.input)

annotations := tc.input.GetAnnotations()
if annotations == nil {
t.Fatal("Expected annotations to be not nil, but got nil")
}

if !reflect.DeepEqual(annotations, tc.expectedTags) {
t.Errorf("Expected annotations to be '%v', but got '%v'", tc.expectedTags, annotations)
}
})
}
}

func TestIsObjectTaggedToDelete(t *testing.T) {
testCases := []struct {
name string
input client.Object
annotations map[string]string
expected bool
}{
{
name: "when object has delete tag annotation set to true then return true",
input: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
DeleteTagAnnotation: "true",
},
},
},
expected: true,
},
{
name: "when object has delete tag annotation set to false then return false",
input: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
DeleteTagAnnotation: "false",
},
},
},
expected: false,
},
{
name: "when object has no delete tag annotation then return false",
input: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{},
},
},
expected: false,
},
{
name: "when object annotations are nil then return false",
input: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Annotations: nil,
},
},
expected: false,
},
}

for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
actual := IsObjectTaggedToDelete(c.input)
if actual != c.expected {
t.Errorf("Expected %v, but got %v", c.expected, actual)
}
})
}
}

0 comments on commit cbf0d40

Please sign in to comment.