From 4c66528ae19407998efdd732b2892b1ff77b0482 Mon Sep 17 00:00:00 2001 From: rayywu Date: Tue, 11 Apr 2023 17:26:16 +0800 Subject: [PATCH] Add UT for workstatus.go and taint.go and rule.go Signed-off-by: rayywu --- pkg/util/helper/taint_test.go | 355 +++++++++++++ pkg/util/helper/workstatus_test.go | 178 +++++++ pkg/util/interpreter/rule_test.go | 828 +++++++++++++++++++++++++++++ 3 files changed, 1361 insertions(+) create mode 100644 pkg/util/interpreter/rule_test.go diff --git a/pkg/util/helper/taint_test.go b/pkg/util/helper/taint_test.go index 8f5d4a0c8..922a18b91 100644 --- a/pkg/util/helper/taint_test.go +++ b/pkg/util/helper/taint_test.go @@ -2,15 +2,19 @@ package helper import ( "context" + "fmt" "reflect" "testing" + "time" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1" + policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1" "github.com/karmada-io/karmada/pkg/util/gclient" ) @@ -98,6 +102,16 @@ func TestUpdateClusterControllerTaint(t *testing.T) { wantTaints: []corev1.Taint{*notReadyTaintTemplate}, wantErr: false, }, + { + name: "clusterTaintsToAdd is nil and clusterTaintsToRemove is nil", + args: args{ + taints: []corev1.Taint{*unreachableTaintTemplate}, + taintsToAdd: []*corev1.Taint{unreachableTaintTemplate.DeepCopy()}, + taintsToRemove: []*corev1.Taint{notReadyTaintTemplate.DeepCopy()}, + }, + wantTaints: []corev1.Taint{*unreachableTaintTemplate}, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -266,6 +280,31 @@ func TestTolerationExists(t *testing.T) { } } +func TestAddTolerations(t *testing.T) { + placement := &policyv1alpha1.Placement{ + ClusterTolerations: []corev1.Toleration{}, + } + + toleration1 := &corev1.Toleration{ + Key: "key1", + Operator: corev1.TolerationOpEqual, + Value: "value1", + Effect: corev1.TaintEffectNoSchedule, + } + toleration2 := &corev1.Toleration{ + Key: "key2", + Operator: corev1.TolerationOpEqual, + Value: "value2", + Effect: corev1.TaintEffectNoSchedule, + } + + AddTolerations(placement, toleration1, toleration2) + + assert.Equal(t, 2, len(placement.ClusterTolerations)) + assert.Equal(t, *toleration1, placement.ClusterTolerations[0]) + assert.Equal(t, *toleration2, placement.ClusterTolerations[1]) +} + func TestHasNoExecuteTaints(t *testing.T) { tests := []struct { name string @@ -357,3 +396,319 @@ func TestGetNoExecuteTaints(t *testing.T) { }) } } + +func TestGetMinTolerationTime(t *testing.T) { + tests := []struct { + name string + noExecuteTaints []corev1.Taint + usedTolerantion []corev1.Toleration + wantResult time.Duration + }{ + { + name: "no noExecuteTaints", + noExecuteTaints: []corev1.Taint{}, + usedTolerantion: []corev1.Toleration{ + { + Key: "key", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoExecute, + TolerationSeconds: &[]int64{60}[0], + }, + }, + wantResult: -1, + }, + { + name: "no usedTolerations", + noExecuteTaints: []corev1.Taint{ + { + Key: "key", + Value: "value", + Effect: corev1.TaintEffectNoExecute, + TimeAdded: &metav1.Time{Time: time.Now()}, + }, + }, + usedTolerantion: []corev1.Toleration{}, + wantResult: 0, + }, + { + name: "with noExecuteTaints and usedTolerations", + noExecuteTaints: []corev1.Taint{ + { + Key: "key", + Value: "value", + Effect: corev1.TaintEffectNoExecute, + TimeAdded: &metav1.Time{Time: time.Now()}, + }, + }, + usedTolerantion: []corev1.Toleration{ + { + Key: "key", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoExecute, + TolerationSeconds: &[]int64{60}[0], + }, + }, + wantResult: 60, + }, + { + name: "usedTolerantion.TolerationSeconds is nil", + noExecuteTaints: []corev1.Taint{ + { + Key: "key", + Value: "value", + Effect: corev1.TaintEffectNoExecute, + TimeAdded: &metav1.Time{Time: time.Now()}, + }, + }, + usedTolerantion: []corev1.Toleration{ + { + Key: "key", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoExecute, + TolerationSeconds: nil, + }, + }, + wantResult: -1, + }, + { + name: "noExecuteTaints.TimeAdded is nil", + noExecuteTaints: []corev1.Taint{ + { + Key: "key", + Value: "value", + Effect: corev1.TaintEffectNoExecute, + TimeAdded: nil, + }, + }, + usedTolerantion: []corev1.Toleration{ + { + Key: "key", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoExecute, + TolerationSeconds: &[]int64{60}[0], + }, + }, + wantResult: -1, + }, + { + name: "find the latest trigger time", + noExecuteTaints: []corev1.Taint{ + { + Key: "key1", + Value: "value1", + Effect: corev1.TaintEffectNoExecute, + TimeAdded: &metav1.Time{Time: time.Now()}, + }, + { + Key: "key2", + Value: "value2", + Effect: corev1.TaintEffectNoExecute, + TimeAdded: &metav1.Time{Time: time.Now()}, + }, + }, + usedTolerantion: []corev1.Toleration{ + { + Key: "key1", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoExecute, + TolerationSeconds: &[]int64{120}[0], + }, + { + Key: "key2", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoExecute, + TolerationSeconds: &[]int64{60}[0], + }, + }, + wantResult: 60, + }, + { + name: "trigger time is up", + noExecuteTaints: []corev1.Taint{ + { + Key: "key", + Value: "value", + Effect: corev1.TaintEffectNoExecute, + TimeAdded: &metav1.Time{ + Time: time.Now().Add(-time.Hour), + }, + }, + }, + usedTolerantion: []corev1.Toleration{ + { + Key: "key", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoExecute, + TolerationSeconds: &[]int64{60}[0], + }, + }, + wantResult: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := GetMinTolerationTime(tt.noExecuteTaints, tt.usedTolerantion) + fmt.Printf("%+v", result) + if result > 0 { + if result > (tt.wantResult+1)*time.Second || result < (tt.wantResult-1)*time.Second { + t.Errorf("GetMinTolerationTime() = %v, want %v", result, tt.wantResult) + } + } else if result != tt.wantResult { + t.Errorf("GetMinTolerationTime() = %v, want %v", result, tt.wantResult) + } + }) + } +} + +func TestGetMatchingTolerations(t *testing.T) { + tests := []struct { + name string + taints []corev1.Taint + tolerations []corev1.Toleration + wantActual bool + wantActualTolerations []corev1.Toleration + }{ + { + name: "taints is nil", + taints: []corev1.Taint{}, + tolerations: []corev1.Toleration{ + { + Key: "key1", + Value: "value1", + Effect: corev1.TaintEffectNoSchedule, + }, + }, + wantActual: true, + wantActualTolerations: []corev1.Toleration{}, + }, + { + name: "tolerations is nil", + taints: []corev1.Taint{ + { + Key: "key1", + Value: "value1", + Effect: corev1.TaintEffectNoSchedule, + }, + }, + tolerations: []corev1.Toleration{}, + wantActual: false, + wantActualTolerations: []corev1.Toleration{}, + }, + { + name: "tolerated is true", + taints: []corev1.Taint{ + { + Key: "key1", + Value: "value1", + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "key2", + Value: "value2", + Effect: corev1.TaintEffectNoSchedule, + }, + }, + tolerations: []corev1.Toleration{ + { + Key: "key1", + Value: "value1", + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "key2", + Value: "value2", + Effect: corev1.TaintEffectNoSchedule, + }, + }, + wantActual: true, + wantActualTolerations: []corev1.Toleration{ + { + Key: "key1", + Value: "value1", + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "key2", + Value: "value2", + Effect: corev1.TaintEffectNoSchedule, + }, + }, + }, + { + name: "tolerated is false", + taints: []corev1.Taint{ + { + Key: "key1", + Value: "value1", + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "key2", + Value: "value2", + Effect: corev1.TaintEffectNoSchedule, + }, + }, + tolerations: []corev1.Toleration{ + { + Key: "key1", + Value: "value_1", + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "key2", + Value: "value_2", + Effect: corev1.TaintEffectNoSchedule, + }, + }, + wantActual: false, + wantActualTolerations: []corev1.Toleration{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual, actualTolerations := GetMatchingTolerations(tt.taints, tt.tolerations) + if actual != tt.wantActual || !reflect.DeepEqual(actualTolerations, tt.wantActualTolerations) { + t.Errorf("GetMatchingTolerations(%v, %v) = (%v, %v), expected (%v, %v)", tt.taints, tt.tolerations, actual, actualTolerations, tt.wantActual, tt.wantActualTolerations) + } + }) + } +} + +func TestNewNotReadyToleration(t *testing.T) { + expectedKey := clusterv1alpha1.TaintClusterNotReady + expectedOperator := corev1.TolerationOpExists + expectedEffect := corev1.TaintEffectNoExecute + expectedSeconds := int64(123) + + toleration := NewNotReadyToleration(expectedSeconds) + + if toleration.Key != expectedKey { + t.Errorf("Expected key %q but got %q", expectedKey, toleration.Key) + } + if toleration.Operator != expectedOperator { + t.Errorf("Expected operator %q but got %q", expectedOperator, toleration.Operator) + } + if toleration.Effect != expectedEffect { + t.Errorf("Expected effect %q but got %q", expectedEffect, toleration.Effect) + } + if *toleration.TolerationSeconds != expectedSeconds { + t.Errorf("Expected seconds %d but got %d", expectedSeconds, *toleration.TolerationSeconds) + } +} + +func TestNewUnreachableToleration(t *testing.T) { + tolerationSeconds := int64(300) + expectedToleration := &corev1.Toleration{ + Key: clusterv1alpha1.TaintClusterUnreachable, + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoExecute, + TolerationSeconds: &tolerationSeconds, + } + + actualToleration := NewUnreachableToleration(tolerationSeconds) + + if !reflect.DeepEqual(actualToleration, expectedToleration) { + t.Errorf("NewUnreachableToleration() = %v, want %v", actualToleration, expectedToleration) + } +} diff --git a/pkg/util/helper/workstatus_test.go b/pkg/util/helper/workstatus_test.go index 3dec98487..de763c165 100644 --- a/pkg/util/helper/workstatus_test.go +++ b/pkg/util/helper/workstatus_test.go @@ -3,11 +3,42 @@ package helper import ( "testing" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" + workv1alpha1 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha1" workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2" ) +func TestGenerateFullyAppliedCondition(t *testing.T) { + spec := workv1alpha2.ResourceBindingSpec{ + Clusters: []workv1alpha2.TargetCluster{ + {Name: "cluster1"}, + {Name: "cluster2"}, + }, + } + statuses := []workv1alpha2.AggregatedStatusItem{ + {ClusterName: "cluster1", Applied: true, Health: workv1alpha2.ResourceHealthy}, + {ClusterName: "cluster2", Applied: true, Health: workv1alpha2.ResourceUnknown}, + } + + expectedTrue := metav1.ConditionTrue + expectedFalse := metav1.ConditionFalse + + resultTrue := generateFullyAppliedCondition(spec, statuses) + if resultTrue.Status != expectedTrue { + t.Errorf("generateFullyAppliedCondition with fully applied statuses returned %v, expected %v", resultTrue, expectedTrue) + } + + resultFalse := generateFullyAppliedCondition(spec, statuses[:1]) + if resultFalse.Status != expectedFalse { + t.Errorf("generateFullyAppliedCondition with partially applied statuses returned %v, expected %v", resultFalse, expectedFalse) + } +} + func TestWorksFullyApplied(t *testing.T) { type args struct { aggregatedStatuses []workv1alpha2.AggregatedStatusItem @@ -108,3 +139,150 @@ func TestWorksFullyApplied(t *testing.T) { }) } } + +func TestGetManifestIndex(t *testing.T) { + manifest1 := workv1alpha1.Manifest{ + RawExtension: runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"v1","kind":"Service","metadata":{"name":"test-service","namespace":"test-namespace"}}`), + }, + } + manifest2 := workv1alpha1.Manifest{ + RawExtension: runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"test-deployment","namespace":"test-namespace"}}`), + }, + } + manifests := []workv1alpha1.Manifest{manifest1, manifest2} + + service := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": "test-service", + "namespace": "test-namespace", + }, + }, + } + + deployment := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-deployment", + "namespace": "test-namespace", + }, + }, + } + + t.Run("Service", func(t *testing.T) { + index, err := GetManifestIndex(manifests, service) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if index != 0 { + t.Errorf("expected index 0, got %d", index) + } + }) + + t.Run("Deployment", func(t *testing.T) { + index, err := GetManifestIndex(manifests, deployment) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if index != 1 { + t.Errorf("expected index 1, got %d", index) + } + }) + + t.Run("No match", func(t *testing.T) { + _, err := GetManifestIndex(manifests, &unstructured.Unstructured{}) + if err == nil { + t.Errorf("expected error, got nil") + } + }) +} + +func TestEqualIdentifier(t *testing.T) { + testCases := []struct { + name string + target *workv1alpha1.ResourceIdentifier + ordinal int + workload *unstructured.Unstructured + expectedOutput bool + }{ + { + name: "identifiers are equal", + target: &workv1alpha1.ResourceIdentifier{ + Ordinal: 0, + Group: "apps", + Version: "v1", + Kind: "Deployment", + Namespace: "default", + Name: "test-deployment", + }, + ordinal: 0, + workload: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "namespace": "default", + "name": "test-deployment", + }, + }, + }, + expectedOutput: true, + }, + { + name: "identifiers are not equal", + target: &workv1alpha1.ResourceIdentifier{ + Ordinal: 1, + Group: "apps", + Version: "v1", + Kind: "Deployment", + Namespace: "default", + Name: "test-deployment", + }, + ordinal: 0, + workload: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "namespace": "default", + "name": "test-deployment", + }, + }, + }, + expectedOutput: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + equal, err := equalIdentifier(tc.target, tc.ordinal, tc.workload) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if equal != tc.expectedOutput { + t.Errorf("expected %v, got %v", tc.expectedOutput, equal) + } + }) + } +} + +func TestIsResourceApplied(t *testing.T) { + // Create a WorkStatus struct with a WorkApplied condition set to True + workStatus := &workv1alpha1.WorkStatus{ + Conditions: []metav1.Condition{ + { + Type: workv1alpha1.WorkApplied, + Status: metav1.ConditionTrue, + }, + }, + } + + // Call IsResourceApplied and assert that it returns true + assert.True(t, IsResourceApplied(workStatus)) +} diff --git a/pkg/util/interpreter/rule_test.go b/pkg/util/interpreter/rule_test.go new file mode 100644 index 000000000..2b9e11752 --- /dev/null +++ b/pkg/util/interpreter/rule_test.go @@ -0,0 +1,828 @@ +package interpreter + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" +) + +func TestRetentionRule_Name(t *testing.T) { + r := &retentionRule{} + expected := string(configv1alpha1.InterpreterOperationRetain) + actual := r.Name() + assert.Equal(t, expected, actual, "Name should return %v", expected) +} + +func TestRetentionRule_Document(t *testing.T) { + rule := &retentionRule{} + expected := `This rule is used to retain runtime values to the desired specification. +The script should implement a function as follows: +function Retain(desiredObj, observedObj) + desiredObj.spec.fieldFoo = observedObj.spec.fieldFoo + return desiredObj +end` + assert.Equal(t, expected, rule.Document()) +} + +func TestRetentionRule_GetScript(t *testing.T) { + t.Run("WithScript", func(t *testing.T) { + customization := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + Retention: &configv1alpha1.LocalValueRetention{ + LuaScript: "hello world", + }, + }, + }, + } + + rule := &retentionRule{} + script := rule.GetScript(customization) + + expectedScript := customization.Spec.Customizations.Retention.LuaScript + if script != expectedScript { + t.Errorf("GetScript() returned %q, expected %q", script, expectedScript) + } + }) + + t.Run("WithoutScript", func(t *testing.T) { + customization := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + Retention: nil, + }, + }, + } + + rule := &retentionRule{} + script := rule.GetScript(customization) + + if script != "" { + t.Errorf("GetScript() returned %q, expected an empty string", script) + } + }) +} + +func TestRetentionRule_SetScript(t *testing.T) { + t.Run("Set non-empty script", func(t *testing.T) { + customization := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + Retention: &configv1alpha1.LocalValueRetention{}, + }, + }, + } + + rule := &retentionRule{} + script := "function Retain(desiredObj, observedObj)\n desiredObj.spec.fieldFoo = observedObj.spec.fieldFoo\n return desiredObj\nend" + + rule.SetScript(customization, script) + if customization.Spec.Customizations.Retention.LuaScript != script { + t.Errorf("Expected script to be set to %q, but got %q", script, customization.Spec.Customizations.Retention.LuaScript) + } + }) + + t.Run("Set empty script", func(t *testing.T) { + customization := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + Retention: nil, + }, + }, + } + + rule := &retentionRule{} + script := "" + + rule.SetScript(customization, script) + if customization.Spec.Customizations.Retention != nil { + t.Errorf("Expected retention to be nil, but got %v", customization.Spec.Customizations.Retention) + } + }) + + t.Run("Set empty Retention", func(t *testing.T) { + customization := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + Retention: nil, + }, + }, + } + + rule := &retentionRule{} + script := "function Retain(desiredObj, observedObj)\n desiredObj.spec.fieldFoo = observedObj.spec.fieldFoo\n return desiredObj\nend" + + rule.SetScript(customization, script) + if customization.Spec.Customizations.Retention.LuaScript != script { + t.Errorf("Expected script to be set to %q, but got %q", script, customization.Spec.Customizations.Retention.LuaScript) + } + }) +} + +func TestReplicaResourceRule_Name(t *testing.T) { + r := &replicaResourceRule{} + expected := string(configv1alpha1.InterpreterOperationInterpretReplica) + actual := r.Name() + assert.Equal(t, expected, actual, "Name should return %v", expected) +} + +func TestReplicaResourceRule_Document(t *testing.T) { + rule := &replicaResourceRule{} + expected := `This rule is used to discover the resource's replica as well as resource requirements. +The script should implement a function as follows: +function GetReplicas(desiredObj) + replica = desiredObj.spec.replicas + nodeClaim = {} + nodeClaim.hardNodeAffinity = {} + nodeClaim.nodeSelector = {} + nodeClaim.tolerations = {} + return replica, nodeClaim +end` + assert.Equal(t, expected, rule.Document()) +} + +func TestReplicaResourceRule_GetScript(t *testing.T) { + t.Run("WithScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{ + LuaScript: "return 'test script'", + }, + }, + }, + } + r := &replicaResourceRule{} + script := r.GetScript(c) + expectedScript := c.Spec.Customizations.ReplicaResource.LuaScript + if script != expectedScript { + t.Errorf("GetScript() returned %q, expected %q", script, expectedScript) + } + }) + + t.Run("WithoutScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + ReplicaResource: nil, + }, + }, + } + + rule := &replicaResourceRule{} + script := rule.GetScript(c) + + if script != "" { + t.Errorf("GetScript() returned %q, expected an empty string", script) + } + }) +} + +func TestReplicaResourceRule_SetScript(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{}, + }, + }, + } + r := &replicaResourceRule{} + + t.Run("Empty Script", func(t *testing.T) { + r.SetScript(c, "") + if c.Spec.Customizations.ReplicaResource != nil { + t.Errorf("Expected ReplicaResource to be nil, but got %v", c.Spec.Customizations.ReplicaResource) + } + }) + + t.Run("Non-Empty Script", func(t *testing.T) { + c.Spec.Customizations.ReplicaResource = nil + script := "test script" + r.SetScript(c, script) + if c.Spec.Customizations.ReplicaResource == nil { + t.Errorf("Expected ReplicaResource to be non-nil, but got nil") + } + if c.Spec.Customizations.ReplicaResource.LuaScript != "test script" { + t.Errorf("Expected ReplicaResource.LuaScript to be %s, but got %s", script, c.Spec.Customizations.ReplicaResource.LuaScript) + } + }) +} + +func TestReplicaRevisionRule_Name(t *testing.T) { + r := &replicaRevisionRule{} + expected := string(configv1alpha1.InterpreterOperationReviseReplica) + actual := r.Name() + assert.Equal(t, expected, actual, "Name should return %v", expected) +} + +func TestReplicaRevisionRule_Document(t *testing.T) { + rule := &replicaRevisionRule{} + expected := `This rule is used to revise replicas in the desired specification. +The script should implement a function as follows: +function ReviseReplica(desiredObj, desiredReplica) + desiredObj.spec.replicas = desiredReplica + return desiredObj +end` + assert.Equal(t, expected, rule.Document()) +} + +func TestReplicaRevisionRule_GetScript(t *testing.T) { + t.Run("WithScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + ReplicaRevision: &configv1alpha1.ReplicaRevision{ + LuaScript: "return 'test script'", + }, + }, + }, + } + r := &replicaRevisionRule{} + script := r.GetScript(c) + expectedScript := c.Spec.Customizations.ReplicaRevision.LuaScript + if script != expectedScript { + t.Errorf("GetScript() returned %q, expected %q", script, expectedScript) + } + }) + + t.Run("WithoutScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + ReplicaRevision: nil, + }, + }, + } + + rule := &replicaRevisionRule{} + script := rule.GetScript(c) + + if script != "" { + t.Errorf("GetScript() returned %q, expected an empty string", script) + } + }) +} + +func TestReplicaRevisionRule_SetScript(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + ReplicaRevision: &configv1alpha1.ReplicaRevision{}, + }, + }, + } + r := &replicaRevisionRule{} + + t.Run("Empty Script", func(t *testing.T) { + r.SetScript(c, "") + if c.Spec.Customizations.ReplicaRevision != nil { + t.Errorf("Expected ReplicaRevision to be nil, but got %v", c.Spec.Customizations.ReplicaRevision) + } + }) + + t.Run("Non-Empty Script", func(t *testing.T) { + c.Spec.Customizations.ReplicaRevision = nil + script := "test script" + r.SetScript(c, script) + if c.Spec.Customizations.ReplicaRevision == nil { + t.Errorf("Expected ReplicaRevision to be non-nil, but got nil") + } + if c.Spec.Customizations.ReplicaRevision.LuaScript != "test script" { + t.Errorf("Expected ReplicaRevision.LuaScript to be %s, but got %s", script, c.Spec.Customizations.ReplicaRevision.LuaScript) + } + }) +} + +func TestStatusReflectionRule_Name(t *testing.T) { + r := &statusReflectionRule{} + expected := string(configv1alpha1.InterpreterOperationInterpretStatus) + actual := r.Name() + assert.Equal(t, expected, actual, "Name should return %v", expected) +} + +func TestStatusReflectionRule_Document(t *testing.T) { + rule := &statusReflectionRule{} + expected := `This rule is used to get the status from the observed specification. +The script should implement a function as follows: +function ReflectStatus(observedObj) + status = {} + status.readyReplicas = observedObj.status.observedObj + return status +end` + assert.Equal(t, expected, rule.Document()) +} + +func TestStatusReflectionRule_GetScript(t *testing.T) { + t.Run("WithScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + StatusReflection: &configv1alpha1.StatusReflection{ + LuaScript: "return 'test script'", + }, + }, + }, + } + r := &statusReflectionRule{} + script := r.GetScript(c) + expectedScript := c.Spec.Customizations.StatusReflection.LuaScript + if script != expectedScript { + t.Errorf("GetScript() returned %q, expected %q", script, expectedScript) + } + }) + + t.Run("WithoutScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + StatusReflection: nil, + }, + }, + } + + rule := &statusReflectionRule{} + script := rule.GetScript(c) + + if script != "" { + t.Errorf("GetScript() returned %q, expected an empty string", script) + } + }) +} + +func TestStatusReflectionRule_SetScript(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + StatusReflection: &configv1alpha1.StatusReflection{}, + }, + }, + } + r := &statusReflectionRule{} + + t.Run("Empty Script", func(t *testing.T) { + r.SetScript(c, "") + if c.Spec.Customizations.StatusReflection != nil { + t.Errorf("Expected StatusReflection to be nil, but got %v", c.Spec.Customizations.StatusReflection) + } + }) + + t.Run("Non-Empty Script", func(t *testing.T) { + c.Spec.Customizations.StatusReflection = nil + script := "test script" + r.SetScript(c, script) + if c.Spec.Customizations.StatusReflection == nil { + t.Errorf("Expected StatusReflection to be non-nil, but got nil") + } + if c.Spec.Customizations.StatusReflection.LuaScript != "test script" { + t.Errorf("Expected StatusReflection.LuaScript to be %s, but got %s", script, c.Spec.Customizations.StatusReflection.LuaScript) + } + }) +} + +func TestStatusAggregationRule_Name(t *testing.T) { + r := &statusAggregationRule{} + expected := string(configv1alpha1.InterpreterOperationAggregateStatus) + actual := r.Name() + assert.Equal(t, expected, actual, "Name should return %v", expected) +} + +func TestStatusAggregationRule_Document(t *testing.T) { + rule := &statusAggregationRule{} + expected := `This rule is used to aggregate decentralized statuses to the desired specification. +The script should implement a function as follows: +function AggregateStatus(desiredObj, statusItems) + for i = 1, #items do + desiredObj.status.readyReplicas = desiredObj.status.readyReplicas + items[i].readyReplicas + end + return desiredObj +end` + assert.Equal(t, expected, rule.Document()) +} + +func TestStatusAggregationRule_GetScript(t *testing.T) { + t.Run("WithScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + StatusAggregation: &configv1alpha1.StatusAggregation{ + LuaScript: "return 'test script'", + }, + }, + }, + } + r := &statusAggregationRule{} + script := r.GetScript(c) + expectedScript := c.Spec.Customizations.StatusAggregation.LuaScript + if script != expectedScript { + t.Errorf("GetScript() returned %q, expected %q", script, expectedScript) + } + }) + + t.Run("WithoutScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + StatusAggregation: nil, + }, + }, + } + + rule := &statusAggregationRule{} + script := rule.GetScript(c) + + if script != "" { + t.Errorf("GetScript() returned %q, expected an empty string", script) + } + }) +} + +func TestStatusAggregationRule_SetScript(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + StatusAggregation: &configv1alpha1.StatusAggregation{}, + }, + }, + } + r := &statusAggregationRule{} + + t.Run("Empty Script", func(t *testing.T) { + r.SetScript(c, "") + if c.Spec.Customizations.StatusAggregation != nil { + t.Errorf("Expected StatusAggregation to be nil, but got %v", c.Spec.Customizations.StatusAggregation) + } + }) + + t.Run("Non-Empty Script", func(t *testing.T) { + c.Spec.Customizations.StatusAggregation = nil + script := "test script" + r.SetScript(c, script) + if c.Spec.Customizations.StatusAggregation == nil { + t.Errorf("Expected Customizations to be non-nil, but got nil") + } + if c.Spec.Customizations.StatusAggregation.LuaScript != "test script" { + t.Errorf("Expected StatusAggregation.LuaScript to be %s, but got %s", script, c.Spec.Customizations.StatusAggregation.LuaScript) + } + }) +} + +func TestHealthInterpretationRule_Name(t *testing.T) { + r := &healthInterpretationRule{} + expected := string(configv1alpha1.InterpreterOperationInterpretHealth) + actual := r.Name() + assert.Equal(t, expected, actual, "Name should return %v", expected) +} + +func TestHealthInterpretationRule_Document(t *testing.T) { + rule := &healthInterpretationRule{} + expected := `This rule is used to assess the health state of a specific resource. +The script should implement a function as follows: +luaScript: > +function InterpretHealth(observedObj) + if observedObj.status.readyReplicas == observedObj.spec.replicas then + return true + end +end` + assert.Equal(t, expected, rule.Document()) +} + +func TestHealthInterpretationRule_GetScript(t *testing.T) { + t.Run("WithScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + HealthInterpretation: &configv1alpha1.HealthInterpretation{ + LuaScript: "return 'test script'", + }, + }, + }, + } + r := &healthInterpretationRule{} + script := r.GetScript(c) + expectedScript := c.Spec.Customizations.HealthInterpretation.LuaScript + if script != expectedScript { + t.Errorf("GetScript() returned %q, expected %q", script, expectedScript) + } + }) + + t.Run("WithoutScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + HealthInterpretation: nil, + }, + }, + } + + rule := &healthInterpretationRule{} + script := rule.GetScript(c) + + if script != "" { + t.Errorf("GetScript() returned %q, expected an empty string", script) + } + }) +} + +func TestHealthInterpretationRule_SetScript(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + HealthInterpretation: &configv1alpha1.HealthInterpretation{}, + }, + }, + } + r := &healthInterpretationRule{} + + t.Run("Empty Script", func(t *testing.T) { + r.SetScript(c, "") + if c.Spec.Customizations.HealthInterpretation != nil { + t.Errorf("Expected HealthInterpretation to be nil, but got %v", c.Spec.Customizations.HealthInterpretation) + } + }) + + t.Run("Non-Empty Script", func(t *testing.T) { + c.Spec.Customizations.HealthInterpretation = nil + script := "test script" + r.SetScript(c, script) + if c.Spec.Customizations.HealthInterpretation == nil { + t.Errorf("Expected HealthInterpretation to be non-nil, but got nil") + } + if c.Spec.Customizations.HealthInterpretation.LuaScript != "test script" { + t.Errorf("Expected HealthInterpretation.LuaScript to be %s, but got %s", script, c.Spec.Customizations.HealthInterpretation.LuaScript) + } + }) +} + +func TestDependencyInterpretationRule_Name(t *testing.T) { + r := &dependencyInterpretationRule{} + expected := string(configv1alpha1.InterpreterOperationInterpretDependency) + actual := r.Name() + assert.Equal(t, expected, actual, "Name should return %v", expected) +} + +func TestDependencyInterpretationRule_Document(t *testing.T) { + rule := &dependencyInterpretationRule{} + expected := ` This rule is used to interpret the dependencies of a specific resource. +The script should implement a function as follows: +function GetDependencies(desiredObj) + dependencies = {} + if desiredObj.spec.serviceAccountName ~= "" and desiredObj.spec.serviceAccountName ~= "default" then + dependency = {} + dependency.apiVersion = "v1" + dependency.kind = "ServiceAccount" + dependency.name = desiredObj.spec.serviceAccountName + dependency.namespace = desiredObj.namespace + dependencies[0] = {} + dependencies[0] = dependency + end + return dependencies +end` + assert.Equal(t, expected, rule.Document()) +} + +func TestDependencyInterpretationRule_GetScript(t *testing.T) { + t.Run("WithScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + DependencyInterpretation: &configv1alpha1.DependencyInterpretation{ + LuaScript: "return 'test script'", + }, + }, + }, + } + r := &dependencyInterpretationRule{} + script := r.GetScript(c) + expectedScript := c.Spec.Customizations.DependencyInterpretation.LuaScript + if script != expectedScript { + t.Errorf("GetScript() returned %q, expected %q", script, expectedScript) + } + }) + + t.Run("WithoutScript", func(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + DependencyInterpretation: nil, + }, + }, + } + + rule := &dependencyInterpretationRule{} + script := rule.GetScript(c) + + if script != "" { + t.Errorf("GetScript() returned %q, expected an empty string", script) + } + }) +} + +func TestDependencyInterpretationRule_SetScript(t *testing.T) { + c := &configv1alpha1.ResourceInterpreterCustomization{ + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Customizations: configv1alpha1.CustomizationRules{ + DependencyInterpretation: &configv1alpha1.DependencyInterpretation{}, + }, + }, + } + r := &dependencyInterpretationRule{} + + t.Run("Empty Script", func(t *testing.T) { + r.SetScript(c, "") + if c.Spec.Customizations.DependencyInterpretation != nil { + t.Errorf("Expected DependencyInterpretation to be nil, but got %v", c.Spec.Customizations.DependencyInterpretation) + } + }) + + t.Run("Non-Empty Script", func(t *testing.T) { + c.Spec.Customizations.DependencyInterpretation = nil + script := "test script" + r.SetScript(c, script) + if c.Spec.Customizations.DependencyInterpretation == nil { + t.Errorf("Expected DependencyInterpretation to be non-nil, but got nil") + } + if c.Spec.Customizations.DependencyInterpretation.LuaScript != "test script" { + t.Errorf("Expected DependencyInterpretation.LuaScript to be %s, but got %s", script, c.Spec.Customizations.DependencyInterpretation.LuaScript) + } + }) +} + +func TestRulesNames(t *testing.T) { + rule1 := &healthInterpretationRule{} + rule2 := &dependencyInterpretationRule{} + rules := Rules{rule1, rule2} + + expectedNames := []string{rule1.Name(), rule2.Name()} + actualNames := rules.Names() + + if !reflect.DeepEqual(actualNames, expectedNames) { + t.Errorf("Expected names %v, but got %v", expectedNames, actualNames) + } +} + +func TestGetByOperation(t *testing.T) { + rules := Rules{&healthInterpretationRule{}, &dependencyInterpretationRule{}} + + tests := []struct { + name string + operation string + expectedRule Rule + }{ + { + name: "valid operation name", + operation: "InterpretHealth", + expectedRule: &healthInterpretationRule{}, + }, + { + name: "invalid operation name", + operation: "invalid", + expectedRule: nil, + }, + { + name: "empty operation name", + operation: "", + expectedRule: nil, + }, + { + name: "case-insensitive operation namee", + operation: "InterpretDEPendency", + expectedRule: &dependencyInterpretationRule{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualRule := rules.GetByOperation(tt.operation) + if actualRule != tt.expectedRule { + t.Errorf("Expected rule %v, but got %v", tt.expectedRule, actualRule) + } + }) + } +} + +func TestRules_Get(t *testing.T) { + rule1 := &healthInterpretationRule{} + rule2 := &dependencyInterpretationRule{} + rules := Rules{rule1, rule2} + + // Test getting a rule that exists + result := rules.Get("InterpretHealth") + if result != rule1 { + t.Errorf("Expected rule1, but got %v", result) + } + + // Test getting a rule that does not exist + result = rules.Get("NonexistentRule") + if result != nil { + t.Errorf("Expected nil, but got %v", result) + } +} + +func TestGetDesiredObjectOrError(t *testing.T) { + args := RuleArgs{ + Desired: nil, + } + _, err := args.getDesiredObjectOrError() + expectedErr := "desired, desired-file options are not set" + if err == nil || err.Error() != expectedErr { + t.Errorf("getDesiredObjectOrError() returned unexpected error: %v", err) + } +} + +func TestGetObservedObjectOrError(t *testing.T) { + args := RuleArgs{} + _, err := args.getObservedObjectOrError() + if err == nil { + t.Errorf("Expected error, but got nil") + } + + obj := &unstructured.Unstructured{} + args.Observed = obj + result, err := args.getObservedObjectOrError() + if err != nil { + t.Errorf("Expected nil error, but got %v", err) + } + if result != obj { + t.Errorf("Expected %v, but got %v", obj, result) + } +} + +func TestGetObjectOrError(t *testing.T) { + desired := &unstructured.Unstructured{} + observed := &unstructured.Unstructured{} + + t.Run("Both desired and observed objects are nil", func(t *testing.T) { + args := RuleArgs{} + _, err := args.getObjectOrError() + if err == nil { + t.Errorf("Expected error, but got nil") + } + }) + + t.Run("Both desired and observed objects are set", func(t *testing.T) { + args := RuleArgs{Desired: desired, Observed: observed} + _, err := args.getObjectOrError() + if err == nil { + t.Errorf("Expected error, but got nil") + } + }) + + t.Run("Only desired object is set", func(t *testing.T) { + args := RuleArgs{Desired: desired} + obj, err := args.getObjectOrError() + if err != nil { + t.Errorf("Expected nil error, but got %v", err) + } + if obj != desired { + t.Errorf("Expected %v, but got %v", desired, obj) + } + }) + + t.Run("Only observed object is set", func(t *testing.T) { + args := RuleArgs{Observed: observed} + obj, err := args.getObjectOrError() + if err != nil { + t.Errorf("Expected nil error, but got %v", err) + } + if obj != observed { + t.Errorf("Expected %v, but got %v", observed, obj) + } + }) +} + +func TestNewRuleResult(t *testing.T) { + result := newRuleResult() + if result.Err != nil { + t.Errorf("Expected error to be nil, but got %v", result.Err) + } + if len(result.Results) != 0 { + t.Errorf("Expected results to be empty, but got %v", result.Results) + } +} + +func TestNewRuleResultWithError(t *testing.T) { + err := fmt.Errorf("test error") + result := newRuleResultWithError(err) + if result.Err != err { + t.Errorf("expected error %v, but got %v", err, result.Err) + } +} + +func TestRuleResultAdd(t *testing.T) { + r := newRuleResult() + r.add("test1", "value1") + r.add("test2", 2) + if len(r.Results) != 2 { + t.Errorf("Expected 2 results, but got %d", len(r.Results)) + } + if r.Results[0].Name != "test1" || r.Results[0].Value != "value1" { + t.Errorf("Unexpected result at index 0: %v", r.Results[0]) + } + if r.Results[1].Name != "test2" || r.Results[1].Value != 2 { + t.Errorf("Unexpected result at index 1: %v", r.Results[1]) + } +}