diff --git a/codebeaver.yml b/codebeaver.yml new file mode 100644 index 000000000..fb530bb23 --- /dev/null +++ b/codebeaver.yml @@ -0,0 +1,2 @@ +from:go-1.23 +# This file was generated automatically by CodeBeaver based on your repository. Learn how to customize it here: https://docs.codebeaver.ai/configuration/ \ No newline at end of file diff --git a/pkg/apis/flagger/v1beta1/canary_test.go b/pkg/apis/flagger/v1beta1/canary_test.go new file mode 100644 index 000000000..e7f59ed4b --- /dev/null +++ b/pkg/apis/flagger/v1beta1/canary_test.go @@ -0,0 +1,427 @@ +package v1beta1 + +import ( + "testing" + "time" + "encoding/json" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + gatewayv1beta1 "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1beta1" +) + +// TestHTTPRewriteGetType tests the GetType method of HTTPRewrite. +func TestHTTPRewriteGetType(t *testing.T) { + // Test when r.Type is set to PrefixMatchHTTPPathModifier. + rewrite := &HTTPRewrite{ + Type: string(gatewayv1beta1.PrefixMatchHTTPPathModifier), + } + if got := rewrite.GetType(); got != string(gatewayv1beta1.PrefixMatchHTTPPathModifier) { + t.Errorf("HTTPRewrite.GetType() = %v, want %v", got, string(gatewayv1beta1.PrefixMatchHTTPPathModifier)) + } + + // Test when r.Type is set to a non-prefix value. Expect default to FullPathHTTPPathModifier. + rewrite.Type = "non-prefix" + if got := rewrite.GetType(); got != string(gatewayv1beta1.FullPathHTTPPathModifier) { + t.Errorf("HTTPRewrite.GetType() = %v, want %v", got, string(gatewayv1beta1.FullPathHTTPPathModifier)) + } +} + +// TestCanaryServiceGetIstioRewrite tests the CanaryService.GetIstioRewrite method. +func TestCanaryServiceGetIstioRewrite(t *testing.T) { + // When Rewrite is nil, expect nil result. + service := CanaryService{} + if res := service.GetIstioRewrite(); res != nil { + t.Error("Expected nil IstioRewrite when Rewrite is nil") + } + + // When Rewrite is provided, expect matching authority and uri values. + service.Rewrite = &HTTPRewrite{ + Authority: "example.com", + Uri: "/new", + } + istioRewrite := service.GetIstioRewrite() + if istioRewrite == nil { + t.Fatal("Expected non-nil IstioRewrite") + } + if istioRewrite.Authority != "example.com" || istioRewrite.Uri != "/new" { + t.Errorf("Got IstioRewrite = %+v, want Authority: example.com, Uri: /new", istioRewrite) + } +} + +// TestSessionAffinityGetMaxAge tests the GetMaxAge method of SessionAffinity. +func TestSessionAffinityGetMaxAge(t *testing.T) { + // When MaxAge is 0, default should be 86400 (24 hours). + affinity := &SessionAffinity{} + if got := affinity.GetMaxAge(); got != 86400 { + t.Errorf("SessionAffinity.GetMaxAge() = %d, want %d", got, 86400) + } + + // When MaxAge is set, it should return that value. + affinity.MaxAge = 3600 + if got := affinity.GetMaxAge(); got != 3600 { + t.Errorf("SessionAffinity.GetMaxAge() = %d, want %d", got, 3600) + } +} + +// TestGetServiceNames tests Canary.GetServiceNames method. +func TestGetServiceNames(t *testing.T) { + canary := &Canary{ + Spec: CanarySpec{ + TargetRef: LocalObjectReference{ + Name: "foo", + }, + }, + } + // When Service.Name is not set, apex should be TargetRef.Name. + apex, primary, canaryName := canary.GetServiceNames() + if apex != "foo" { + t.Errorf("Expected apex name 'foo', got '%s'", apex) + } + if primary != "foo-primary" { + t.Errorf("Expected primary name 'foo-primary', got '%s'", primary) + } + if canaryName != "foo-canary" { + t.Errorf("Expected canary name 'foo-canary', got '%s'", canaryName) + } + + // When Service.Name is provided, it should override the apex name. + canary.Spec.Service.Name = "bar" + apex, primary, canaryName = canary.GetServiceNames() + if apex != "bar" { + t.Errorf("Expected apex name 'bar', got '%s'", apex) + } + if primary != "bar-primary" { + t.Errorf("Expected primary name 'bar-primary', got '%s'", primary) + } + if canaryName != "bar-canary" { + t.Errorf("Expected canary name 'bar-canary', got '%s'", canaryName) + } +} + +// TestGetProgressDeadlineSeconds tests Canary.GetProgressDeadlineSeconds. +func TestGetProgressDeadlineSeconds(t *testing.T) { + // When ProgressDeadlineSeconds is set, it should be returned. + seconds := int32(300) + canary := &Canary{ + Spec: CanarySpec{ + ProgressDeadlineSeconds: &seconds, + }, + } + if got := canary.GetProgressDeadlineSeconds(); got != 300 { + t.Errorf("GetProgressDeadlineSeconds() = %d, want %d", got, 300) + } + + // When not set, it should return the default ProgressDeadlineSeconds. + canary.Spec.ProgressDeadlineSeconds = nil + if got := canary.GetProgressDeadlineSeconds(); got != ProgressDeadlineSeconds { + t.Errorf("GetProgressDeadlineSeconds() = %d, want %d", got, ProgressDeadlineSeconds) + } +} + +// TestGetAnalysis tests the GetAnalysis method of Canary. +func TestGetAnalysis(t *testing.T) { + // When Analysis is not nil, it should return Analysis. + analysis := &CanaryAnalysis{ + Interval: "2m", + } + canary := &Canary{ + Spec: CanarySpec{ + Analysis: analysis, + }, + } + if got := canary.GetAnalysis(); got != analysis { + t.Errorf("GetAnalysis() = %v, want %v", got, analysis) + } + + // When Analysis is nil but deprecated CanaryAnalysis is provided. + deprecated := &CanaryAnalysis{ + Interval: "3m", + } + canary.Spec.Analysis = nil + canary.Spec.CanaryAnalysis = deprecated + if got := canary.GetAnalysis(); got != deprecated { + t.Errorf("GetAnalysis() = %v, want %v", got, deprecated) + } +} + +// TestGetAnalysisInterval tests the GetAnalysisInterval method. +func TestGetAnalysisInterval(t *testing.T) { + // Case 1: Empty interval should return default AnalysisInterval (60s) + analysis := &CanaryAnalysis{ + Interval: "", + } + canary := &Canary{ + Spec: CanarySpec{ + Analysis: analysis, + }, + } + if d := canary.GetAnalysisInterval(); d != AnalysisInterval { + t.Errorf("GetAnalysisInterval() = %v, want %v", d, AnalysisInterval) + } + + // Case 2: An invalid duration string should return default AnalysisInterval. + analysis.Interval = "invalid" + if d := canary.GetAnalysisInterval(); d != AnalysisInterval { + t.Errorf("GetAnalysisInterval() with invalid value = %v, want %v", d, AnalysisInterval) + } + + // Case 3: A duration less than 10 seconds should be clamped to 10 seconds. + analysis.Interval = "5s" + if d := canary.GetAnalysisInterval(); d != 10*time.Second { + t.Errorf("GetAnalysisInterval() with low value = %v, want %v", d, 10*time.Second) + } + + // Case 4: A valid duration greater or equal to 10 seconds should be returned. + analysis.Interval = "15s" + if d := canary.GetAnalysisInterval(); d != 15*time.Second { + t.Errorf("GetAnalysisInterval() = %v, want %v", d, 15*time.Second) + } +} + +// TestGetAnalysisThreshold tests the GetAnalysisThreshold method. +func TestGetAnalysisThreshold(t *testing.T) { + analysis := &CanaryAnalysis{ + Threshold: 5, + } + canary := &Canary{ + Spec: CanarySpec{ + Analysis: analysis, + }, + } + if got := canary.GetAnalysisThreshold(); got != 5 { + t.Errorf("GetAnalysisThreshold() = %d, want %d", got, 5) + } + + // When Threshold is 0, should default to 1. + analysis.Threshold = 0 + if got := canary.GetAnalysisThreshold(); got != 1 { + t.Errorf("GetAnalysisThreshold() = %d, want %d", got, 1) + } +} + +// TestGetAnalysisPrimaryReadyThreshold tests the primary ready threshold getter. +func TestGetAnalysisPrimaryReadyThreshold(t *testing.T) { + // When set, should return the provided value. + threshold := 80 + analysis := &CanaryAnalysis{ + PrimaryReadyThreshold: &threshold, + } + canary := &Canary{ + Spec: CanarySpec{ + Analysis: analysis, + }, + } + if got := canary.GetAnalysisPrimaryReadyThreshold(); got != 80 { + t.Errorf("GetAnalysisPrimaryReadyThreshold() = %d, want %d", got, 80) + } + + // When not set, should return the constant PrimaryReadyThreshold. + analysis.PrimaryReadyThreshold = nil + if got := canary.GetAnalysisPrimaryReadyThreshold(); got != PrimaryReadyThreshold { + t.Errorf("GetAnalysisPrimaryReadyThreshold() = %d, want %d", got, PrimaryReadyThreshold) + } +} + +// TestGetAnalysisCanaryReadyThreshold tests the canary ready threshold getter. +func TestGetAnalysisCanaryReadyThreshold(t *testing.T) { + // When set, should return the provided value. + threshold := 75 + analysis := &CanaryAnalysis{ + CanaryReadyThreshold: &threshold, + } + canary := &Canary{ + Spec: CanarySpec{ + Analysis: analysis, + }, + } + if got := canary.GetAnalysisCanaryReadyThreshold(); got != 75 { + t.Errorf("GetAnalysisCanaryReadyThreshold() = %d, want %d", got, 75) + } + + // When not set, should return the constant CanaryReadyThreshold. + analysis.CanaryReadyThreshold = nil + if got := canary.GetAnalysisCanaryReadyThreshold(); got != CanaryReadyThreshold { + t.Errorf("GetAnalysisCanaryReadyThreshold() = %d, want %d", got, CanaryReadyThreshold) + } +} + +// TestGetMetricInterval tests that the metric interval is always "1m". +func TestGetMetricInterval(t *testing.T) { + canary := &Canary{} + if got := canary.GetMetricInterval(); got != MetricInterval { + t.Errorf("GetMetricInterval() = %s, want %s", got, MetricInterval) + } +} + +// TestSkipAnalysis tests the SkipAnalysis method. +func TestSkipAnalysis(t *testing.T) { + // Case 1: When both Analysis and CanaryAnalysis are nil, should skip analysis. + canary := &Canary{ + Spec: CanarySpec{}, + } + if !canary.SkipAnalysis() { + t.Error("SkipAnalysis() = false, want true when both Analysis and CanaryAnalysis are nil") + } + + // Case 2: When either Analysis or CanaryAnalysis is provided without SkipAnalysis true, should not skip. + analysis := &CanaryAnalysis{ + Interval: "1m", + } + canary.Spec.Analysis = analysis + canary.Spec.SkipAnalysis = false + if canary.SkipAnalysis() { + t.Error("SkipAnalysis() = true, want false when Analysis is provided and SkipAnalysis is false") + } + + // Case 3: When SkipAnalysis is explicitly set to true. + canary.Spec.SkipAnalysis = true + if !canary.SkipAnalysis() { + t.Error("SkipAnalysis() = false, want true when SkipAnalysis is true") + } +} +// TestHTTPRewriteGetTypeEmpty tests the GetType method of HTTPRewrite when the type is empty. +func TestHTTPRewriteGetTypeEmpty(t *testing.T) { + rewrite := &HTTPRewrite{ + Type: "", + } + want := string(gatewayv1beta1.FullPathHTTPPathModifier) + if got := rewrite.GetType(); got != want { + t.Errorf("HTTPRewrite.GetType() with empty type = %v, want %v", got, want) + } +} + +// TestGetAnalysisWithBothProvided tests that when both Analysis and deprecated CanaryAnalysis are provided, +// GetAnalysis returns the Analysis field (i.e. the non-deprecated one). +func TestGetAnalysisWithBothProvided(t *testing.T) { + analysis := &CanaryAnalysis{ + Interval: "5m", + } + deprecated := &CanaryAnalysis{ + Interval: "4m", + } + canary := &Canary{ + Spec: CanarySpec{ + Analysis: analysis, + CanaryAnalysis: deprecated, + }, + } + if got := canary.GetAnalysis(); got != analysis { + t.Errorf("GetAnalysis() with both fields provided = %v, want %v", got, analysis) + } +} +// TestCanaryJSONMarshalling tests JSON marshalling and unmarshalling of the Canary struct, +func TestCanaryJSONMarshalling(t *testing.T) { + // Create a Canary object with various fields set, including nested structures. + canary := Canary{ + TypeMeta: metav1.TypeMeta{ + Kind: "Canary", + APIVersion: "flagger.app/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-canary", + Namespace: "default", + }, + Spec: CanarySpec{ + Provider: "istio", + TargetRef: LocalObjectReference{ + Name: "foo", + }, + Service: CanaryService{ + Name: "bar", + Port: 8080, + PortName: "http", + TargetPort: intstr.FromInt(8080), + }, + Analysis: &CanaryAnalysis{ + Interval: "30s", + Threshold: 3, + Metrics: []CanaryMetric{ + { + Name: "error-rate", + Interval: "30s", + Threshold: 0.1, + }, + }, + }, + }, + } + + // Marshal the Canary object to JSON. + data, err := json.Marshal(canary) + if err != nil { + t.Errorf("Failed to marshal Canary: %v", err) + } + + // Unmarshal the JSON back into an object. + var newCanary Canary + err = json.Unmarshal(data, &newCanary) + if err != nil { + t.Errorf("Failed to unmarshal Canary: %v", err) + } + + // Verify that key fields are unmarshalled correctly. + if newCanary.ObjectMeta.Name != "test-canary" { + t.Errorf("Expected name 'test-canary', got '%s'", newCanary.ObjectMeta.Name) + } + if newCanary.Spec.Provider != "istio" { + t.Errorf("Expected provider 'istio', got '%s'", newCanary.Spec.Provider) + } + if newCanary.Spec.Service.Port != 8080 { + t.Errorf("Expected service port 8080, got %d", newCanary.Spec.Service.Port) + } + if newCanary.Spec.Analysis == nil || newCanary.Spec.Analysis.Threshold != 3 { + t.Errorf("Expected analysis threshold 3, got %v", newCanary.Spec.Analysis) + } +} +// TestGetAnalysisNil tests that GetAnalysis returns nil when both Analysis and CanaryAnalysis are nil. +func TestGetAnalysisNil(t *testing.T) { + canary := &Canary{ + Spec: CanarySpec{}, + } + if analysis := canary.GetAnalysis(); analysis != nil { + t.Errorf("Expected GetAnalysis() to return nil when both Analysis and CanaryAnalysis are nil") + } +} + +// TestSessionAffinityNegativeMaxAge tests that a negative MaxAge is returned as set. +func TestSessionAffinityNegativeMaxAge(t *testing.T) { + affinity := &SessionAffinity{MaxAge: -5} + if got := affinity.GetMaxAge(); got != -5 { + t.Errorf("Expected GetMaxAge() to return -5, got %d", got) + } +} + +// int32Ptr is a helper function that returns a pointer to an int32 value. +func int32Ptr(i int32) *int32 { + return &i +} + +// TestAutoscalerReferenceJSONMarshalling tests JSON marshalling and unmarshalling of AutoscalerRefernce. +func TestAutoscalerReferenceJSONMarshalling(t *testing.T) { + autoscaler := AutoscalerRefernce{ + APIVersion: "v1", + Kind: "HorizontalPodAutoscaler", + Name: "hpa-test", + PrimaryScalerQueries: map[string]string{ + "q1": "query1", + }, + PrimaryScalerReplicas: &ScalerReplicas{ + MinReplicas: int32Ptr(1), + MaxReplicas: int32Ptr(5), + }, + } + data, err := json.Marshal(autoscaler) + if err != nil { + t.Fatalf("Failed to marshal autoscaler: %v", err) + } + + var newAutoscaler AutoscalerRefernce + err = json.Unmarshal(data, &newAutoscaler) + if err != nil { + t.Fatalf("Failed to unmarshal autoscaler: %v", err) + } + + if newAutoscaler.Name != "hpa-test" || newAutoscaler.APIVersion != "v1" { + t.Errorf("Unmarshalled autoscaler does not match expected values") + } +} \ No newline at end of file diff --git a/pkg/apis/flagger/v1beta1/zz_generated.deepcopy_test.go b/pkg/apis/flagger/v1beta1/zz_generated.deepcopy_test.go new file mode 100644 index 000000000..dced2aa30 --- /dev/null +++ b/pkg/apis/flagger/v1beta1/zz_generated.deepcopy_test.go @@ -0,0 +1,952 @@ +package v1beta1 + +import ( + "reflect" + "testing" + "time" + + istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gatewayapiv1 "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1" + gatewayapiv1beta1 "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1beta1" +) + +// TestDeepCopy_Canary tests the DeepCopy, DeepCopyInto, and DeepCopyObject functions on Canary. +func TestDeepCopy_Canary(t *testing.T) { + // Create a sample Canary instance + now := metav1.NewTime(time.Now()) + orig := &Canary{ + TypeMeta: metav1.TypeMeta{Kind: "Canary", APIVersion: "flagger.app/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-canary", + Namespace: "default", + CreationTimestamp: now, + }, + Spec: CanarySpec{ + TargetRef: LocalObjectReference{Name: "test-target"}, + Service: CanaryService{ + Gateways: []string{"gw1", "gw2"}, + Hosts: []string{"example.com"}, + }, + }, + Status: CanaryStatus{ + TrackedConfigs: &map[string]string{"config1": "v1"}, + Conditions: []CanaryCondition{ + { + Type: "Ready", + Status: "True", + LastTransitionTime: now, + }, + }, + }, + } + + // Make a deep copy of the Canary + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil") + } + + // Check DeepCopyObject + var objCopy interface{} = orig.DeepCopyObject() + if objCopy == nil { + t.Fatalf("DeepCopyObject returned nil") + } + + // Verify that the copy is equal + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy did not produce an equal object") + } + + // Mutate the original and make sure the copy does not change to ensure deep copy + orig.Spec.TargetRef.Name = "modified-target" + (*orig.Status.TrackedConfigs)["config1"] = "modified" + orig.Spec.Service.Gateways[0] = "modified-gw" + + // The copy should remain unchanged + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy is not independent from the original") + } +} + +// TestDeepCopy_AlertProvider tests the deep copy functions on AlertProvider. +func TestDeepCopy_AlertProvider(t *testing.T) { + now := metav1.NewTime(time.Now()) + orig := &AlertProvider{ + TypeMeta: metav1.TypeMeta{Kind: "AlertProvider", APIVersion: "flagger.app/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-alert", + Namespace: "default", + CreationTimestamp: now, + }, + Spec: AlertProviderSpec{ + SecretRef: &v1.LocalObjectReference{Name: "secret-ref"}, + }, + Status: AlertProviderStatus{ + Conditions: []AlertProviderCondition{ + { + LastUpdateTime: now, + LastTransitionTime: now, + }, + }, + }, + } + + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for AlertProvider") + } + + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for AlertProvider did not produce an equal object") + } + + // Mutate original + orig.Spec.SecretRef.Name = "modified-secret" + orig.Status.Conditions[0].LastUpdateTime = metav1.NewTime(time.Now().Add(2 * time.Hour)) + + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for AlertProvider is not independent from the original") + } +} + +// TestDeepCopy_MetricTemplate tests the deep copy functions on MetricTemplate. +func TestDeepCopy_MetricTemplate(t *testing.T) { + now := metav1.NewTime(time.Now()) + orig := &MetricTemplate{ + TypeMeta: metav1.TypeMeta{Kind: "MetricTemplate", APIVersion: "flagger.app/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-metric", + Namespace: "default", + CreationTimestamp: now, + }, + Spec: MetricTemplateSpec{ + Provider: MetricTemplateProvider{ + SecretRef: &v1.LocalObjectReference{Name: "provider-secret"}, + }, + }, + Status: MetricTemplateStatus{ + Conditions: []MetricTemplateCondition{ + { + LastUpdateTime: now, + LastTransitionTime: now, + }, + }, + }, + } + + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for MetricTemplate") + } + + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for MetricTemplate did not produce an equal object") + } + + // Change some fields on the original to ensure the copy is deep + orig.Spec.Provider.SecretRef.Name = "modified-provider-secret" + orig.Status.Conditions[0].LastTransitionTime = metav1.NewTime(time.Now().Add(3 * time.Hour)) + + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for MetricTemplate is not independent from the original") + } +} + +// TestDeepCopy_ScalerReplicas tests the deep copy functions on ScalerReplicas. +func TestDeepCopy_ScalerReplicas(t *testing.T) { + orig := &ScalerReplicas{ + MinReplicas: new(int32), + MaxReplicas: new(int32), + } + *orig.MinReplicas = 1 + *orig.MaxReplicas = 10 + + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for ScalerReplicas") + } + + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for ScalerReplicas did not produce an equal object") + } + + // Mutate original + *orig.MinReplicas = 2 + *orig.MaxReplicas = 20 + + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for ScalerReplicas is not independent from the original") + } +} +// TestDeepCopy_CanaryAlert tests the deep copy functions on CanaryAlert. +func TestDeepCopy_CanaryAlert(t *testing.T) { + // Create a sample CanaryAlert instance + orig := &CanaryAlert{ + ProviderRef: CrossNamespaceObjectReference{Name: "alert-provider"}, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for CanaryAlert") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for CanaryAlert did not produce an equal object") + } + // Mutate the original to check that the copy is independent + orig.ProviderRef.Name = "modified-alert-provider" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for CanaryAlert is not independent from the original") + } +} + +// TestDeepCopy_CanaryAnalysis tests the deep copy functions on CanaryAnalysis. +func TestDeepCopy_CanaryAnalysis(t *testing.T) { + // Prepare pointer values for thresholds + prt := new(int) + *prt = 5 + crt := new(int) + *crt = 7 + + // Create a sample CanaryAnalysis with non-nil slices and pointer fields. + orig := &CanaryAnalysis{ + StepWeights: []int{10, 20}, + PrimaryReadyThreshold: prt, + CanaryReadyThreshold: crt, + Alerts: []CanaryAlert{ + {ProviderRef: CrossNamespaceObjectReference{Name: "alert"}}, + }, + Metrics: []CanaryMetric{ + { + ThresholdRange: &CanaryThresholdRange{ + Min: func() *float64 { f := 0.1; return &f }(), + Max: func() *float64 { f := 1.0; return &f }(), + }, + TemplateRef: &CrossNamespaceObjectReference{Name: "ref", Namespace: "ns"}, + TemplateVariables: map[string]string{ + "key": "val", + }, + }, + }, + Webhooks: []CanaryWebhook{ + {Metadata: func() *map[string]string { m := map[string]string{"hook": "value"}; return &m }()}, + }, + Match: []istiov1beta1.HTTPMatchRequest{ + {}, + }, + SessionAffinity: &SessionAffinity{}, + } + + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for CanaryAnalysis") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for CanaryAnalysis did not produce an equal object") + } + + // Mutate the original to test deep copy isolation. + orig.StepWeights[0] = 99 + *orig.PrimaryReadyThreshold = 50 + orig.Alerts[0].ProviderRef.Name = "modified-alert" + orig.Metrics[0].TemplateVariables["key"] = "modified-val" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for CanaryAnalysis is not independent from the original") + } +} + +// TestDeepCopy_CanaryList tests the deep copy functions on CanaryList. +func TestDeepCopy_CanaryList(t *testing.T) { + // Create a sample CanaryList with two Canary items. + now := metav1.NewTime(time.Now()) + orig := &CanaryList{ + TypeMeta: metav1.TypeMeta{Kind: "Canary", APIVersion: "flagger.app/v1beta1"}, + ListMeta: metav1.ListMeta{ResourceVersion: "v1"}, + Items: []Canary{ + { + ObjectMeta: metav1.ObjectMeta{Name: "canary-1", CreationTimestamp: now}, + Spec: CanarySpec{TargetRef: LocalObjectReference{Name: "target-1"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "canary-2", CreationTimestamp: now}, + Spec: CanarySpec{TargetRef: LocalObjectReference{Name: "target-2"}}, + }, + }, + } + + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for CanaryList") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for CanaryList did not produce an equal object") + } + + // Mutate original and verify change does not affect the copy. + orig.Items[0].ObjectMeta.Name = "modified-canary-1" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for CanaryList is not independent from the original") + } +} + +// TestDeepCopy_CanaryMetric tests the deep copy functions on CanaryMetric. +func TestDeepCopy_CanaryMetric(t *testing.T) { + // Create a sample CanaryMetric instance + orig := &CanaryMetric{ + ThresholdRange: &CanaryThresholdRange{ + Min: func() *float64 { f := 0.5; return &f }(), + Max: func() *float64 { f := 2.5; return &f }(), + }, + TemplateRef: &CrossNamespaceObjectReference{Name: "metric-ref", Namespace: "default"}, + TemplateVariables: map[string]string{ + "var1": "value1", + }, + } + + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for CanaryMetric") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for CanaryMetric did not produce an equal object") + } + + // Mutate original to ensure independency. + orig.ThresholdRange.Min = func() *float64 { f := 1.0; return &f }() + orig.TemplateVariables["var1"] = "modified-value1" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for CanaryMetric is not independent from the original") + } +} +// TestDeepCopy_AutoscalerRefernce tests deep copy functions for AutoscalerRefernce. +func TestDeepCopy_AutoscalerRefernce(t *testing.T) { + orig := &AutoscalerRefernce{ + PrimaryScalerQueries: map[string]string{"query": "value"}, + PrimaryScalerReplicas: &ScalerReplicas{ + MinReplicas: func(i int32) *int32 { x := i; return &x }(1), + MaxReplicas: func(i int32) *int32 { x := i; return &x }(5), + }, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for AutoscalerRefernce") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for AutoscalerRefernce did not produce an equal object") + } + // Modify original to ensure a deep copy. + orig.PrimaryScalerQueries["query"] = "modified" + *orig.PrimaryScalerReplicas.MinReplicas = 10 + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for AutoscalerRefernce is not independent from the original") + } +} + +// TestDeepCopy_CustomBackend tests deep copy on CustomBackend. +func TestDeepCopy_CustomBackend(t *testing.T) { + orig := &CustomBackend{ + BackendObjectReference: &gatewayapiv1.BackendObjectReference{}, + Filters: []gatewayapiv1.HTTPRouteFilter{ + {}, + }, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for CustomBackend") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for CustomBackend did not produce an equal object") + } + // Change original and check that copy stays unchanged. + grp := "grp" + kindVal := "kind" + nameVal := "name" + orig.BackendObjectReference = &gatewayapiv1.BackendObjectReference{ + Group: &grp, + Kind: &kindVal, + Name: &nameVal, + } + orig.Filters = append(orig.Filters, gatewayapiv1.HTTPRouteFilter{}) + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for CustomBackend is not independent from the original") + } +} + +// TestDeepCopy_CustomMetadata tests deep copy on CustomMetadata. +func TestDeepCopy_CustomMetadata(t *testing.T) { + orig := &CustomMetadata{ + Labels: map[string]string{"key1": "value1"}, + Annotations: map[string]string{"anno1": "valueA"}, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for CustomMetadata") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for CustomMetadata did not produce an equal object") + } + orig.Labels["key1"] = "modified" + orig.Annotations["anno1"] = "modified" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for CustomMetadata is not independent from the original") + } +} + +// TestDeepCopy_LocalObjectReference tests deep copy on LocalObjectReference. +func TestDeepCopy_LocalObjectReference(t *testing.T) { + orig := &LocalObjectReference{Name: "test"} + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for LocalObjectReference") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for LocalObjectReference did not produce an equal object") + } + orig.Name = "modified" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for LocalObjectReference is not independent from the original") + } +} + +// TestDeepCopy_HTTPRewrite tests deep copy on HTTPRewrite. +func TestDeepCopy_HTTPRewrite(t *testing.T) { + orig := &HTTPRewrite{} + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for HTTPRewrite") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for HTTPRewrite did not produce an equal object") + } +} + +// TestDeepCopy_MetricTemplateList tests deep copy on MetricTemplateList. +func TestDeepCopy_MetricTemplateList(t *testing.T) { + now := metav1.NewTime(time.Now()) + orig := &MetricTemplateList{ + TypeMeta: metav1.TypeMeta{Kind: "MetricTemplate", APIVersion: "flagger.app/v1beta1"}, + ListMeta: metav1.ListMeta{ResourceVersion: "v1"}, + Items: []MetricTemplate{ + { + ObjectMeta: metav1.ObjectMeta{Name: "metric1", CreationTimestamp: now}, + Spec: MetricTemplateSpec{ + Provider: MetricTemplateProvider{ + SecretRef: &v1.LocalObjectReference{Name: "secret1"}, + }, + }, + Status: MetricTemplateStatus{}, + }, + }, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for MetricTemplateList") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for MetricTemplateList did not produce an equal object") + } + orig.Items[0].ObjectMeta.Name = "modified-metric1" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for MetricTemplateList is not independent from the original") + } +} + +// TestDeepCopy_MetricTemplateModel tests deep copy on MetricTemplateModel. +func TestDeepCopy_MetricTemplateModel(t *testing.T) { + orig := &MetricTemplateModel{ + Variables: map[string]string{"var": "val"}, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for MetricTemplateModel") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for MetricTemplateModel did not produce an equal object") + } + orig.Variables["var"] = "modified" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for MetricTemplateModel is not independent from the original") + } +} + +// TestDeepCopy_MetricTemplateProvider tests deep copy on MetricTemplateProvider. +func TestDeepCopy_MetricTemplateProvider(t *testing.T) { + orig := &MetricTemplateProvider{ + SecretRef: &v1.LocalObjectReference{Name: "provider"}, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for MetricTemplateProvider") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for MetricTemplateProvider did not produce an equal object") + } + orig.SecretRef.Name = "modified-provider" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for MetricTemplateProvider is not independent from the original") + } +} + +// TestDeepCopy_MetricTemplateStatus tests deep copy on MetricTemplateStatus. +func TestDeepCopy_MetricTemplateStatus(t *testing.T) { + now := metav1.NewTime(time.Now()) + orig := &MetricTemplateStatus{ + Conditions: []MetricTemplateCondition{ + { + LastUpdateTime: now, + LastTransitionTime: now, + }, + }, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for MetricTemplateStatus") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for MetricTemplateStatus did not produce an equal object") + } + orig.Conditions[0].LastUpdateTime = metav1.NewTime(time.Now().Add(1 * time.Hour)) + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for MetricTemplateStatus is not independent from the original") + } +} + +// TestDeepCopy_SessionAffinity tests deep copy on SessionAffinity. +func TestDeepCopy_SessionAffinity(t *testing.T) { + orig := &SessionAffinity{} + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for SessionAffinity") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for SessionAffinity did not produce an equal object") + } +} + +// TestDeepCopy_CanaryCondition tests deep copy on CanaryCondition. +func TestDeepCopy_CanaryCondition(t *testing.T) { + now := metav1.NewTime(time.Now()) + orig := &CanaryCondition{ + Type: "TestCondition", + Status: "True", + LastUpdateTime: now, + LastTransitionTime: now, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for CanaryCondition") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for CanaryCondition did not produce an equal object") + } + orig.Status = "False" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for CanaryCondition is not independent from the original") + } +} + +// TestDeepCopy_AlertProviderCondition tests deep copy on AlertProviderCondition. +func TestDeepCopy_AlertProviderCondition(t *testing.T) { + now := metav1.NewTime(time.Now()) + orig := &AlertProviderCondition{ + LastUpdateTime: now, + LastTransitionTime: now, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for AlertProviderCondition") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for AlertProviderCondition did not produce an equal object") + } + cp.LastUpdateTime = metav1.NewTime(time.Now().Add(1 * time.Hour)) + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for AlertProviderCondition is not independent from the original") + } +} + +// TestDeepCopy_CanaryWebhook tests deep copy on CanaryWebhook. +func TestDeepCopy_CanaryWebhook(t *testing.T) { + orig := &CanaryWebhook{ + Metadata: func() *map[string]string { + m := map[string]string{"hook": "orig"} + return &m + }(), + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for CanaryWebhook") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for CanaryWebhook did not produce an equal object") + } + // Modify original metadata. + (*orig.Metadata)["hook"] = "modified" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for CanaryWebhook is not independent from the original") + } +} + +// TestDeepCopy_CanaryWebhookPayload tests deep copy on CanaryWebhookPayload. +func TestDeepCopy_CanaryWebhookPayload(t *testing.T) { + orig := &CanaryWebhookPayload{ + Metadata: map[string]string{"key": "value"}, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for CanaryWebhookPayload") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for CanaryWebhookPayload did not produce an equal object") + } + orig.Metadata["key"] = "modified" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for CanaryWebhookPayload is not independent from the original") + } +} +// TestDeepCopy_BackendObjectReference tests deep copy for BackendObjectReference in gatewayapi v1beta1 package +func TestDeepCopy_BackendObjectReference(t *testing.T) { + // Setup original BackendObjectReference with pointer fields + group := gatewayapiv1beta1.Group("group1") + kind := gatewayapiv1beta1.Kind("kind1") + namespace := gatewayapiv1beta1.Namespace("ns1") + port := gatewayapiv1beta1.PortNumber(8080) + orig := &gatewayapiv1beta1.BackendObjectReference{ + Group: &group, + Kind: &kind, + Namespace: &namespace, + Port: &port, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for BackendObjectReference") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy did not produce an equal BackendObjectReference") + } + // Mutate original and check independence + newGroup := gatewayapiv1beta1.Group("modified-group") + orig.Group = &newGroup + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for BackendObjectReference is not independent from the original") + } +} + +// TestDeepCopy_HTTPRouteFilter tests deep copy for HTTPRouteFilter in gatewayapi v1beta1 package +func TestDeepCopy_HTTPRouteFilter(t *testing.T) { + // Setup original HTTPRouteFilter with various nested fields + orig := &gatewayapiv1beta1.HTTPRouteFilter{ + RequestHeaderModifier: &gatewayapiv1beta1.HTTPHeaderFilter{ + Set: []gatewayapiv1beta1.HTTPHeader{{Name: "X-Req", Value: "val1"}}, + Add: []gatewayapiv1beta1.HTTPHeader{{Name: "X-Add", Value: "val2"}}, + Remove: []string{"X-Remove"}, + }, + ResponseHeaderModifier: &gatewayapiv1beta1.HTTPHeaderFilter{ + Set: []gatewayapiv1beta1.HTTPHeader{{Name: "X-Resp", Value: "val3"}}, + }, + RequestMirror: &gatewayapiv1beta1.HTTPRequestMirrorFilter{ + BackendRef: gatewayapiv1beta1.BackendRef{ + BackendObjectReference: gatewayapiv1beta1.BackendObjectReference{}, + }, + }, + RequestRedirect: &gatewayapiv1beta1.HTTPRequestRedirectFilter{ + Scheme: func() *string { s := "http"; return &s }(), + Hostname: func() *gatewayapiv1beta1.PreciseHostname { s := gatewayapiv1beta1.PreciseHostname("host1"); return &s }(), + }, + URLRewrite: &gatewayapiv1beta1.HTTPURLRewriteFilter{ + Hostname: func() *gatewayapiv1beta1.PreciseHostname { s := gatewayapiv1beta1.PreciseHostname("rewrite-host"); return &s }(), + Path: &gatewayapiv1beta1.HTTPPathModifier{ + ReplaceFullPath: func() *string { s := "/full"; return &s }(), + }, + }, + ExtensionRef: &gatewayapiv1beta1.LocalObjectReference{Name: "ext-ref"}, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for HTTPRouteFilter") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy did not produce an equal HTTPRouteFilter") + } + // Mutate a nested field in the original + orig.RequestHeaderModifier.Set[0].Value = "modified-val" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for HTTPRouteFilter is not independent from the original") + } +} + +// TestDeepCopy_HTTPPathMatch tests deep copy for HTTPPathMatch in gatewayapi v1beta1 package +func TestDeepCopy_HTTPPathMatch(t *testing.T) { + // Setup original HTTPPathMatch with Type and Value + matchType := gatewayapiv1beta1.PathMatchType("Exact") + val := "/home" + orig := &gatewayapiv1beta1.HTTPPathMatch{ + Type: &matchType, + Value: &val, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for HTTPPathMatch") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy did not produce an equal HTTPPathMatch") + } + // Mutate original and ensure the copy remains unchanged + newVal := "/modified" + orig.Value = &newVal + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for HTTPPathMatch is not independent from the original") + } +} +// TestDeepCopy_BackendRef tests deep copy functions on BackendRef. +func TestDeepCopy_BackendRef(t *testing.T) { + // Create a sample BackendRef with a pointer field Weight. + weight := int32(100) + orig := &gatewayapiv1beta1.BackendRef{ + BackendObjectReference: gatewayapiv1beta1.BackendObjectReference{ + Name: "backend", + }, + Weight: &weight, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for BackendRef") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for BackendRef did not produce an equal object") + } + // Modify the original and check that the copy stays unchanged. + *orig.Weight = 200 + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for BackendRef is not independent from the original") + } +} + +// TestDeepCopy_HTTPHeaderMatch tests deep copy functions on HTTPHeaderMatch. +func TestDeepCopy_HTTPHeaderMatch(t *testing.T) { + matchType := gatewayapiv1beta1.HeaderMatchType("Exact") + orig := &gatewayapiv1beta1.HTTPHeaderMatch{ + Name: "X-Test", + Value: "value1", + Type: &matchType, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for HTTPHeaderMatch") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for HTTPHeaderMatch did not produce an equal object") + } + // Change the original and ensure the copy remains unchanged. + orig.Value = "modified" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for HTTPHeaderMatch is not independent from the original") + } +} + +// TestDeepCopy_HTTPPathModifier tests deep copy functions on HTTPPathModifier. +func TestDeepCopy_HTTPPathModifier(t *testing.T) { + orig := &gatewayapiv1beta1.HTTPPathModifier{ + ReplaceFullPath: func() *string { s := "/full"; return &s }(), + ReplacePrefixMatch: func() *string { s := "/prefix"; return &s }(), + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for HTTPPathModifier") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for HTTPPathModifier did not produce an equal object") + } + // Modify the original. + orig.ReplaceFullPath = func() *string { s := "/modified"; return &s }() + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for HTTPPathModifier is not independent from the original") + } +} + +// TestDeepCopy_HTTPQueryParamMatch tests deep copy functions on HTTPQueryParamMatch. +func TestDeepCopy_HTTPQueryParamMatch(t *testing.T) { + matchType := gatewayapiv1beta1.QueryParamMatchType("Exact") + orig := &gatewayapiv1beta1.HTTPQueryParamMatch{ + Name: "param", + Value: "1", + Type: &matchType, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for HTTPQueryParamMatch") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for HTTPQueryParamMatch did not produce an equal object") + } + // Modify the original. + orig.Value = "2" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for HTTPQueryParamMatch is not independent from the original") + } +} + +// TestDeepCopy_HTTPRequestMirrorFilter tests deep copy functions on HTTPRequestMirrorFilter. +func TestDeepCopy_HTTPRequestMirrorFilter(t *testing.T) { + orig := &gatewayapiv1beta1.HTTPRequestMirrorFilter{ + BackendRef: gatewayapiv1beta1.BackendRef{ + BackendObjectReference: gatewayapiv1beta1.BackendObjectReference{ + Name: "mirror-backend", + }, + }, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for HTTPRequestMirrorFilter") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for HTTPRequestMirrorFilter did not produce an equal object") + } + // Modify the original. + orig.BackendRef.Name = "modified-backend" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for HTTPRequestMirrorFilter is not independent from the original") + } +} + +// TestDeepCopy_HTTPRequestRedirectFilter tests deep copy functions on HTTPRequestRedirectFilter. +func TestDeepCopy_HTTPRequestRedirectFilter(t *testing.T) { + scheme := "http" + hostname := gatewayapiv1beta1.PreciseHostname("host") + port := gatewayapiv1beta1.PortNumber(80) + orig := &gatewayapiv1beta1.HTTPRequestRedirectFilter{ + Scheme: &scheme, + Hostname: &hostname, + Port: &port, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for HTTPRequestRedirectFilter") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for HTTPRequestRedirectFilter did not produce an equal object") + } + // Modify the original. + newScheme := "https" + orig.Scheme = &newScheme + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for HTTPRequestRedirectFilter is not independent from the original") + } +} + +// TestDeepCopy_ParentReference tests deep copy functions on ParentReference. +func TestDeepCopy_ParentReference(t *testing.T) { + sectionName := "section" + port := gatewayapiv1beta1.PortNumber(8080) + orig := &gatewayapiv1beta1.ParentReference{ + Group: func() *gatewayapiv1beta1.Group { g := gatewayapiv1beta1.Group("group"); return &g }(), + Kind: func() *gatewayapiv1beta1.Kind { k := gatewayapiv1beta1.Kind("kind"); return &k }(), + Namespace: func() *gatewayapiv1beta1.Namespace { n := gatewayapiv1beta1.Namespace("default"); return &n }(), + SectionName: §ionName, + Port: &port, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for ParentReference") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for ParentReference did not produce an equal object") + } + // Modify the original. + newSection := "modified-section" + orig.SectionName = &newSection + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for ParentReference is not independent from the original") + } +} + +// TestDeepCopy_ReferenceGrant tests deep copy functions on ReferenceGrant. +func TestDeepCopy_ReferenceGrant(t *testing.T) { + orig := &gatewayapiv1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "refgrant", + }, + Spec: gatewayapiv1beta1.ReferenceGrantSpec{ + From: []gatewayapiv1beta1.ReferenceGrantFrom{ + {}, + }, + To: []gatewayapiv1beta1.ReferenceGrantTo{ + { + Name: func() *gatewayapiv1beta1.ObjectName { n := gatewayapiv1beta1.ObjectName("obj"); return &n }(), + }, + }, + }, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for ReferenceGrant") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for ReferenceGrant did not produce an equal object") + } + // Modify the original. + orig.Spec.To[0].Name = func() *gatewayapiv1beta1.ObjectName { n := gatewayapiv1beta1.ObjectName("modified"); return &n }() + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for ReferenceGrant is not independent from the original") + } +} + +// TestDeepCopy_RouteParentStatus tests deep copy functions on RouteParentStatus. +func TestDeepCopy_RouteParentStatus(t *testing.T) { + now := metav1.NewTime(time.Now()) + orig := &gatewayapiv1beta1.RouteParentStatus{ + ParentRef: gatewayapiv1beta1.ParentReference{ + Group: func() *gatewayapiv1beta1.Group { g := gatewayapiv1beta1.Group("group"); return &g }(), + }, + Conditions: []metav1.Condition{ + { + Type: "Ready", + Status: "True", + }, + }, + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for RouteParentStatus") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for RouteParentStatus did not produce an equal object") + } + // Modify the original. + orig.Conditions[0].Status = "False" + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for RouteParentStatus is not independent from the original") + } +} + +// TestDeepCopy_SecretObjectReference tests deep copy functions on SecretObjectReference. +func TestDeepCopy_SecretObjectReference(t *testing.T) { + orig := &gatewayapiv1beta1.SecretObjectReference{ + Name: "secret", + Group: func() *gatewayapiv1beta1.Group { + g := gatewayapiv1beta1.Group("grp") + return &g + }(), + Kind: func() *gatewayapiv1beta1.Kind { + k := gatewayapiv1beta1.Kind("kind") + return &k + }(), + Namespace: func() *gatewayapiv1beta1.Namespace { + n := gatewayapiv1beta1.Namespace("ns") + return &n + }(), + } + cp := orig.DeepCopy() + if cp == nil { + t.Fatalf("DeepCopy returned nil for SecretObjectReference") + } + if !reflect.DeepEqual(orig, cp) { + t.Error("DeepCopy for SecretObjectReference did not produce an equal object") + } + // Modify the original. + newGroup := gatewayapiv1beta1.Group("modified-grp") + orig.Group = &newGroup + if reflect.DeepEqual(orig, cp) { + t.Error("Deep copy for SecretObjectReference is not independent from the original") + } +} \ No newline at end of file diff --git a/pkg/apis/gatewayapi/v1beta1/register_test.go b/pkg/apis/gatewayapi/v1beta1/register_test.go new file mode 100644 index 000000000..26f11944a --- /dev/null +++ b/pkg/apis/gatewayapi/v1beta1/register_test.go @@ -0,0 +1,193 @@ +package v1beta1 + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// TestResourceFunction tests that the Resource function returns a properly +// qualified GroupResource with the correct group and resource name. +func TestResourceFunction(t *testing.T) { + resourceName := "testroute" + gr := Resource(resourceName) + + // Verify that the group part is correctly set. + expectedGroup := SchemeGroupVersion.Group + if gr.Group != expectedGroup { + t.Errorf("expected group %s, got %s", expectedGroup, gr.Group) + } + + // Verify that the resource part is correctly set. + if gr.Resource != resourceName { + t.Errorf("expected resource %s, got %s", resourceName, gr.Resource) + } +} + +// TestAddKnownTypes verifies that AddToScheme properly registers the expected types in the scheme. +func TestAddKnownTypes(t *testing.T) { + scheme := runtime.NewScheme() + + // AddToScheme should register the types without error. + if err := AddToScheme(scheme); err != nil { + t.Errorf("AddToScheme returned an unexpected error: %v", err) + } + + // Retrieve the known types for our SchemeGroupVersion. + knownTypes := scheme.KnownTypes(SchemeGroupVersion) + + // Expected types that should be registered. + expectedTypes := []string{"HTTPRoute", "HTTPRouteList", "ReferenceGrant", "ReferenceGrantList"} + + // Check that each expected type is registered. + for _, typeName := range expectedTypes { + if _, exists := knownTypes[typeName]; !exists { + t.Errorf("expected type %s to be registered", typeName) + } + } + + // Verify the group version added by metav1. + gv := schema.GroupVersion{Group: SchemeGroupVersion.Group, Version: SchemeGroupVersion.Version} + if !reflect.DeepEqual(gv, SchemeGroupVersion) { + t.Errorf("group version mismatch: expected %+v, got %+v", SchemeGroupVersion, gv) + } +} +// TestResourceFunctionEmpty tests that Resource() returns the expected GroupResource when given an empty resource string. +func TestResourceFunctionEmpty(t *testing.T) { + resourceName := "" + gr := Resource(resourceName) + + // Verify group part + expectedGroup := SchemeGroupVersion.Group + if gr.Group != expectedGroup { + t.Errorf("expected group %s, got %s", expectedGroup, gr.Group) + } + + // Verify resource part is empty. + if gr.Resource != resourceName { + t.Errorf("expected empty resource, got %s", gr.Resource) + } +} + +// TestResourceFunctionSpecialChars tests that Resource() returns the expected GroupResource +// when given a resource string containing special characters. +func TestResourceFunctionSpecialChars(t *testing.T) { + resourceName := "route-test_special" + gr := Resource(resourceName) + + // Verify that the group part is correct. + expectedGroup := SchemeGroupVersion.Group + if gr.Group != expectedGroup { + t.Errorf("expected group %s, got %s", expectedGroup, gr.Group) + } + + // Verify that the resource part is correctly set with special characters. + if gr.Resource != resourceName { + t.Errorf("expected resource %s, got %s", resourceName, gr.Resource) + } +} + +// TestAddKnownTypesNilScheme tests that addKnownTypes panics if passed a nil scheme. +func TestAddKnownTypesNilScheme(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("expected panic when passing nil scheme to addKnownTypes, but no panic occurred") + } + }() + + // Calling addKnownTypes with nil should cause a panic. + addKnownTypes(nil) +} +// TestResourceFunctionWithSpaces verifies that Resource() +func TestResourceFunctionWithSpaces(t *testing.T) { + resourceName := "test route with spaces" + gr := Resource(resourceName) + + // Verify that the group part is correctly set. + if gr.Group != SchemeGroupVersion.Group { + t.Errorf("expected group %s, got %s", SchemeGroupVersion.Group, gr.Group) + } + // Verify that the resource part is exactly the provided string. + if gr.Resource != resourceName { + t.Errorf("expected resource %q, got %q", resourceName, gr.Resource) + } +} + +// TestAddToSchemeMultiple verifies that calling AddToScheme multiple times +// is idempotent and does not cause errors or duplicate registrations. +func TestAddToSchemeMultiple(t *testing.T) { + scheme := runtime.NewScheme() + // Call AddToScheme three times. + for i := 0; i < 3; i++ { + if err := AddToScheme(scheme); err != nil { + t.Errorf("call %d: AddToScheme returned an unexpected error: %v", i, err) + } + } + + // Retrieve the known types for our SchemeGroupVersion. + knownTypes := scheme.KnownTypes(SchemeGroupVersion) + // Expected types that should be registered. + expectedTypes := []string{"HTTPRoute", "HTTPRouteList", "ReferenceGrant", "ReferenceGrantList"} + + // Verify each type is registered. + for _, typeName := range expectedTypes { + if _, exists := knownTypes[typeName]; !exists { + t.Errorf("expected type %s to be registered", typeName) + } + } +} + +// TestSchemeGroupVersionString verifies that the String method on SchemeGroupVersion +// returns the expected group/version string. +func TestSchemeGroupVersionString(t *testing.T) { + expected := SchemeGroupVersion.Group + "/" + SchemeGroupVersion.Version + if SchemeGroupVersion.String() != expected { + t.Errorf("expected SchemeGroupVersion string %q, got %q", expected, SchemeGroupVersion.String()) + } +} +// TestResourceFunctionUnicode tests that Resource() correctly handles Unicode resource names. +func TestResourceFunctionUnicode(t *testing.T) { + resourceName := "ルート" // Japanese for "route" + gr := Resource(resourceName) + + // Verify that the group part is correctly set. + if gr.Group != SchemeGroupVersion.Group { + t.Errorf("expected group %s, got %s", SchemeGroupVersion.Group, gr.Group) + } + // Verify that the resource part matches the Unicode input. + if gr.Resource != resourceName { + t.Errorf("expected resource %s, got %s", resourceName, gr.Resource) + } +} + +// TestResourceFunctionNumeric tests that Resource() correctly handles numeric resource names. +func TestResourceFunctionNumeric(t *testing.T) { + resourceName := "1234" + gr := Resource(resourceName) + + // Verify that the group part is correctly set. + if gr.Group != SchemeGroupVersion.Group { + t.Errorf("expected group %s, got %s", SchemeGroupVersion.Group, gr.Group) + } + // Verify that the resource part matches the numeric string. + if gr.Resource != resourceName { + t.Errorf("expected resource %s, got %s", resourceName, gr.Resource) + } +} + +// TestResourceFunctionNewlineTab tests that Resource() correctly handles resource names containing newline and tab characters. +func TestResourceFunctionNewlineTab(t *testing.T) { + resourceName := "route\n\t test" + gr := Resource(resourceName) + + // Verify that the group part is correctly set. + if gr.Group != SchemeGroupVersion.Group { + t.Errorf("expected group %s, got %s", SchemeGroupVersion.Group, gr.Group) + } + // Verify that the resource part retains the newline and tab characters. + if gr.Resource != resourceName { + t.Errorf("expected resource %q, got %q", resourceName, gr.Resource) + } +} \ No newline at end of file