This is an automated email from the ASF dual-hosted git repository.

vishesh pushed a commit to branch add-unit-tests
in repository 
https://gitbox.apache.org/repos/asf/cloudstack-kubernetes-provider.git

commit 1bc4513e5c4448e281ba8965728c7745c25243f4
Author: vishesh92 <[email protected]>
AuthorDate: Fri Dec 5 16:10:27 2025 +0530

    Add some unit tests
---
 cloudstack_instances_test.go    | 176 +++++++++++++++
 cloudstack_loadbalancer_test.go | 480 ++++++++++++++++++++++++++++++++++++++++
 protocol_test.go                | 259 ++++++++++++++++++++++
 3 files changed, 915 insertions(+)

diff --git a/cloudstack_instances_test.go b/cloudstack_instances_test.go
new file mode 100644
index 00000000..4210e305
--- /dev/null
+++ b/cloudstack_instances_test.go
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package cloudstack
+
+import (
+       "strings"
+       "testing"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       corev1 "k8s.io/api/core/v1"
+)
+
+func TestNodeAddresses(t *testing.T) {
+       cs := &CSCloud{}
+
+       tests := []struct {
+               name        string
+               instance    *cloudstack.VirtualMachine
+               wantAddrs   []corev1.NodeAddress
+               wantErr     bool
+               errContains string
+       }{
+               {
+                       name: "instance with internal IP only",
+                       instance: &cloudstack.VirtualMachine{
+                               Id:   "vm-1",
+                               Name: "test-vm",
+                               Nic: []cloudstack.Nic{
+                                       {Ipaddress: "10.0.0.1"},
+                               },
+                       },
+                       wantAddrs: []corev1.NodeAddress{
+                               {Type: corev1.NodeInternalIP, Address: 
"10.0.0.1"},
+                       },
+                       wantErr: false,
+               },
+               {
+                       name: "instance with internal IP and hostname",
+                       instance: &cloudstack.VirtualMachine{
+                               Id:       "vm-1",
+                               Name:     "test-vm",
+                               Hostname: "test-hostname",
+                               Nic: []cloudstack.Nic{
+                                       {Ipaddress: "10.0.0.1"},
+                               },
+                       },
+                       wantAddrs: []corev1.NodeAddress{
+                               {Type: corev1.NodeInternalIP, Address: 
"10.0.0.1"},
+                               {Type: corev1.NodeHostName, Address: 
"test-hostname"},
+                       },
+                       wantErr: false,
+               },
+               {
+                       name: "instance with internal IP and public IP",
+                       instance: &cloudstack.VirtualMachine{
+                               Id:       "vm-1",
+                               Name:     "test-vm",
+                               Publicip: "203.0.113.1",
+                               Nic: []cloudstack.Nic{
+                                       {Ipaddress: "10.0.0.1"},
+                               },
+                       },
+                       wantAddrs: []corev1.NodeAddress{
+                               {Type: corev1.NodeInternalIP, Address: 
"10.0.0.1"},
+                               {Type: corev1.NodeExternalIP, Address: 
"203.0.113.1"},
+                       },
+                       wantErr: false,
+               },
+               {
+                       name: "instance with all address types",
+                       instance: &cloudstack.VirtualMachine{
+                               Id:       "vm-1",
+                               Name:     "test-vm",
+                               Hostname: "test-hostname",
+                               Publicip: "203.0.113.1",
+                               Nic: []cloudstack.Nic{
+                                       {Ipaddress: "10.0.0.1"},
+                               },
+                       },
+                       wantAddrs: []corev1.NodeAddress{
+                               {Type: corev1.NodeInternalIP, Address: 
"10.0.0.1"},
+                               {Type: corev1.NodeHostName, Address: 
"test-hostname"},
+                               {Type: corev1.NodeExternalIP, Address: 
"203.0.113.1"},
+                       },
+                       wantErr: false,
+               },
+               {
+                       name: "instance with no NICs returns error",
+                       instance: &cloudstack.VirtualMachine{
+                               Id:   "vm-1",
+                               Name: "test-vm",
+                               Nic:  []cloudstack.Nic{},
+                       },
+                       wantAddrs:   nil,
+                       wantErr:     true,
+                       errContains: "does not have an internal IP",
+               },
+               {
+                       name: "instance with nil NICs returns error",
+                       instance: &cloudstack.VirtualMachine{
+                               Id:   "vm-1",
+                               Name: "test-vm",
+                               Nic:  nil,
+                       },
+                       wantAddrs:   nil,
+                       wantErr:     true,
+                       errContains: "does not have an internal IP",
+               },
+               {
+                       name: "instance with multiple NICs uses first",
+                       instance: &cloudstack.VirtualMachine{
+                               Id:   "vm-1",
+                               Name: "test-vm",
+                               Nic: []cloudstack.Nic{
+                                       {Ipaddress: "10.0.0.1"},
+                                       {Ipaddress: "10.0.0.2"},
+                               },
+                       },
+                       wantAddrs: []corev1.NodeAddress{
+                               {Type: corev1.NodeInternalIP, Address: 
"10.0.0.1"},
+                       },
+                       wantErr: false,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       gotAddrs, err := cs.nodeAddresses(tt.instance)
+
+                       if tt.wantErr {
+                               if err == nil {
+                                       t.Errorf("nodeAddresses() expected 
error, got nil")
+                                       return
+                               }
+                               if tt.errContains != "" && 
!strings.Contains(err.Error(), tt.errContains) {
+                                       t.Errorf("nodeAddresses() error = %v, 
want error containing %q", err, tt.errContains)
+                               }
+                               return
+                       }
+
+                       if err != nil {
+                               t.Errorf("nodeAddresses() unexpected error: 
%v", err)
+                               return
+                       }
+
+                       if len(gotAddrs) != len(tt.wantAddrs) {
+                               t.Errorf("nodeAddresses() returned %d 
addresses, want %d", len(gotAddrs), len(tt.wantAddrs))
+                               return
+                       }
+
+                       for i, want := range tt.wantAddrs {
+                               if gotAddrs[i].Type != want.Type || 
gotAddrs[i].Address != want.Address {
+                                       t.Errorf("nodeAddresses()[%d] = {%v, 
%v}, want {%v, %v}",
+                                               i, gotAddrs[i].Type, 
gotAddrs[i].Address, want.Type, want.Address)
+                               }
+                       }
+               })
+       }
+}
diff --git a/cloudstack_loadbalancer_test.go b/cloudstack_loadbalancer_test.go
new file mode 100644
index 00000000..bbd63066
--- /dev/null
+++ b/cloudstack_loadbalancer_test.go
@@ -0,0 +1,480 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package cloudstack
+
+import (
+       "sort"
+       "testing"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestCompareStringSlice(t *testing.T) {
+       tests := []struct {
+               name string
+               x    []string
+               y    []string
+               want bool
+       }{
+               {
+                       name: "equal slices same order",
+                       x:    []string{"a", "b", "c"},
+                       y:    []string{"a", "b", "c"},
+                       want: true,
+               },
+               {
+                       name: "equal slices different order",
+                       x:    []string{"a", "b", "c"},
+                       y:    []string{"c", "a", "b"},
+                       want: true,
+               },
+               {
+                       name: "different lengths",
+                       x:    []string{"a", "b"},
+                       y:    []string{"a", "b", "c"},
+                       want: false,
+               },
+               {
+                       name: "same length different elements",
+                       x:    []string{"a", "b", "c"},
+                       y:    []string{"a", "b", "d"},
+                       want: false,
+               },
+               {
+                       name: "both empty",
+                       x:    []string{},
+                       y:    []string{},
+                       want: true,
+               },
+               {
+                       name: "both nil",
+                       x:    nil,
+                       y:    nil,
+                       want: true,
+               },
+               {
+                       name: "one nil one empty",
+                       x:    nil,
+                       y:    []string{},
+                       want: true,
+               },
+               {
+                       name: "one empty one non-empty",
+                       x:    []string{},
+                       y:    []string{"a"},
+                       want: false,
+               },
+               {
+                       name: "duplicate elements equal",
+                       x:    []string{"a", "a", "b"},
+                       y:    []string{"a", "b", "a"},
+                       want: true,
+               },
+               {
+                       name: "duplicate elements not equal - different counts",
+                       x:    []string{"a", "a", "b"},
+                       y:    []string{"a", "b", "b"},
+                       want: false,
+               },
+               {
+                       name: "single element equal",
+                       x:    []string{"a"},
+                       y:    []string{"a"},
+                       want: true,
+               },
+               {
+                       name: "single element not equal",
+                       x:    []string{"a"},
+                       y:    []string{"b"},
+                       want: false,
+               },
+               {
+                       name: "CIDR list comparison - typical use case",
+                       x:    []string{"10.0.0.0/8", "192.168.0.0/16"},
+                       y:    []string{"192.168.0.0/16", "10.0.0.0/8"},
+                       want: true,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if got := compareStringSlice(tt.x, tt.y); got != 
tt.want {
+                               t.Errorf("compareStringSlice(%v, %v) = %v, want 
%v", tt.x, tt.y, got, tt.want)
+                       }
+               })
+       }
+}
+
+func TestSymmetricDifference(t *testing.T) {
+       tests := []struct {
+               name        string
+               hostIDs     []string
+               lbInstances []*cloudstack.VirtualMachine
+               wantAssign  []string
+               wantRemove  []string
+       }{
+               {
+                       name:        "no hosts no instances",
+                       hostIDs:     []string{},
+                       lbInstances: []*cloudstack.VirtualMachine{},
+                       wantAssign:  nil,
+                       wantRemove:  nil,
+               },
+               {
+                       name:        "all new hosts",
+                       hostIDs:     []string{"host1", "host2", "host3"},
+                       lbInstances: []*cloudstack.VirtualMachine{},
+                       wantAssign:  []string{"host1", "host2", "host3"},
+                       wantRemove:  nil,
+               },
+               {
+                       name:    "all hosts to remove",
+                       hostIDs: []string{},
+                       lbInstances: []*cloudstack.VirtualMachine{
+                               {Id: "host1"},
+                               {Id: "host2"},
+                       },
+                       wantAssign: nil,
+                       wantRemove: []string{"host1", "host2"},
+               },
+               {
+                       name:    "exact match - nothing to do",
+                       hostIDs: []string{"host1", "host2"},
+                       lbInstances: []*cloudstack.VirtualMachine{
+                               {Id: "host1"},
+                               {Id: "host2"},
+                       },
+                       wantAssign: nil,
+                       wantRemove: nil,
+               },
+               {
+                       name:    "partial overlap - some to add some to remove",
+                       hostIDs: []string{"host1", "host3"},
+                       lbInstances: []*cloudstack.VirtualMachine{
+                               {Id: "host1"},
+                               {Id: "host2"},
+                       },
+                       wantAssign: []string{"host3"},
+                       wantRemove: []string{"host2"},
+               },
+               {
+                       name:    "add one host",
+                       hostIDs: []string{"host1", "host2", "host3"},
+                       lbInstances: []*cloudstack.VirtualMachine{
+                               {Id: "host1"},
+                               {Id: "host2"},
+                       },
+                       wantAssign: []string{"host3"},
+                       wantRemove: nil,
+               },
+               {
+                       name:    "remove one host",
+                       hostIDs: []string{"host1"},
+                       lbInstances: []*cloudstack.VirtualMachine{
+                               {Id: "host1"},
+                               {Id: "host2"},
+                       },
+                       wantAssign: nil,
+                       wantRemove: []string{"host2"},
+               },
+               {
+                       name:        "nil instances",
+                       hostIDs:     []string{"host1"},
+                       lbInstances: nil,
+                       wantAssign:  []string{"host1"},
+                       wantRemove:  nil,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       gotAssign, gotRemove := symmetricDifference(tt.hostIDs, 
tt.lbInstances)
+
+                       // Sort slices for comparison since map iteration order 
is not guaranteed
+                       sort.Strings(gotAssign)
+                       sort.Strings(tt.wantAssign)
+                       sort.Strings(gotRemove)
+                       sort.Strings(tt.wantRemove)
+
+                       if !compareStringSlice(gotAssign, tt.wantAssign) {
+                               t.Errorf("symmetricDifference() assign = %v, 
want %v", gotAssign, tt.wantAssign)
+                       }
+                       if !compareStringSlice(gotRemove, tt.wantRemove) {
+                               t.Errorf("symmetricDifference() remove = %v, 
want %v", gotRemove, tt.wantRemove)
+                       }
+               })
+       }
+}
+
+func TestIsFirewallSupported(t *testing.T) {
+       tests := []struct {
+               name     string
+               services []cloudstack.NetworkServiceInternal
+               want     bool
+       }{
+               {
+                       name:     "empty services",
+                       services: []cloudstack.NetworkServiceInternal{},
+                       want:     false,
+               },
+               {
+                       name:     "nil services",
+                       services: nil,
+                       want:     false,
+               },
+               {
+                       name: "firewall present",
+                       services: []cloudstack.NetworkServiceInternal{
+                               {Name: "Dhcp"},
+                               {Name: "Firewall"},
+                               {Name: "Dns"},
+                       },
+                       want: true,
+               },
+               {
+                       name: "firewall not present",
+                       services: []cloudstack.NetworkServiceInternal{
+                               {Name: "Dhcp"},
+                               {Name: "Dns"},
+                               {Name: "Lb"},
+                       },
+                       want: false,
+               },
+               {
+                       name: "only firewall",
+                       services: []cloudstack.NetworkServiceInternal{
+                               {Name: "Firewall"},
+                       },
+                       want: true,
+               },
+               {
+                       name: "case sensitive - lowercase firewall",
+                       services: []cloudstack.NetworkServiceInternal{
+                               {Name: "firewall"},
+                       },
+                       want: false,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if got := isFirewallSupported(tt.services); got != 
tt.want {
+                               t.Errorf("isFirewallSupported() = %v, want %v", 
got, tt.want)
+                       }
+               })
+       }
+}
+
+func TestIsNetworkACLSupported(t *testing.T) {
+       tests := []struct {
+               name     string
+               services []cloudstack.NetworkServiceInternal
+               want     bool
+       }{
+               {
+                       name:     "empty services",
+                       services: []cloudstack.NetworkServiceInternal{},
+                       want:     false,
+               },
+               {
+                       name:     "nil services",
+                       services: nil,
+                       want:     false,
+               },
+               {
+                       name: "NetworkACL present",
+                       services: []cloudstack.NetworkServiceInternal{
+                               {Name: "Dhcp"},
+                               {Name: "NetworkACL"},
+                               {Name: "Dns"},
+                       },
+                       want: true,
+               },
+               {
+                       name: "NetworkACL not present",
+                       services: []cloudstack.NetworkServiceInternal{
+                               {Name: "Dhcp"},
+                               {Name: "Dns"},
+                               {Name: "Firewall"},
+                       },
+                       want: false,
+               },
+               {
+                       name: "only NetworkACL",
+                       services: []cloudstack.NetworkServiceInternal{
+                               {Name: "NetworkACL"},
+                       },
+                       want: true,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if got := isNetworkACLSupported(tt.services); got != 
tt.want {
+                               t.Errorf("isNetworkACLSupported() = %v, want 
%v", got, tt.want)
+                       }
+               })
+       }
+}
+
+func TestGetStringFromServiceAnnotation(t *testing.T) {
+       tests := []struct {
+               name           string
+               annotations    map[string]string
+               annotationKey  string
+               defaultSetting string
+               want           string
+       }{
+               {
+                       name:           "annotation present",
+                       annotations:    map[string]string{"key1": "value1"},
+                       annotationKey:  "key1",
+                       defaultSetting: "default",
+                       want:           "value1",
+               },
+               {
+                       name:           "annotation not present - use default",
+                       annotations:    map[string]string{"other": "value"},
+                       annotationKey:  "key1",
+                       defaultSetting: "default",
+                       want:           "default",
+               },
+               {
+                       name:           "annotation present but empty - return 
empty",
+                       annotations:    map[string]string{"key1": ""},
+                       annotationKey:  "key1",
+                       defaultSetting: "default",
+                       want:           "",
+               },
+               {
+                       name:           "nil annotations - use default",
+                       annotations:    nil,
+                       annotationKey:  "key1",
+                       defaultSetting: "default",
+                       want:           "default",
+               },
+               {
+                       name:           "empty default when not found",
+                       annotations:    map[string]string{},
+                       annotationKey:  "key1",
+                       defaultSetting: "",
+                       want:           "",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       service := &corev1.Service{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:        "test-service",
+                                       Namespace:   "default",
+                                       Annotations: tt.annotations,
+                               },
+                       }
+                       if got := getStringFromServiceAnnotation(service, 
tt.annotationKey, tt.defaultSetting); got != tt.want {
+                               t.Errorf("getStringFromServiceAnnotation() = 
%v, want %v", got, tt.want)
+                       }
+               })
+       }
+}
+
+func TestGetBoolFromServiceAnnotation(t *testing.T) {
+       tests := []struct {
+               name           string
+               annotations    map[string]string
+               annotationKey  string
+               defaultSetting bool
+               want           bool
+       }{
+               {
+                       name:           "annotation true",
+                       annotations:    map[string]string{"key1": "true"},
+                       annotationKey:  "key1",
+                       defaultSetting: false,
+                       want:           true,
+               },
+               {
+                       name:           "annotation false",
+                       annotations:    map[string]string{"key1": "false"},
+                       annotationKey:  "key1",
+                       defaultSetting: true,
+                       want:           false,
+               },
+               {
+                       name:           "annotation not present - use default 
true",
+                       annotations:    map[string]string{},
+                       annotationKey:  "key1",
+                       defaultSetting: true,
+                       want:           true,
+               },
+               {
+                       name:           "annotation not present - use default 
false",
+                       annotations:    map[string]string{},
+                       annotationKey:  "key1",
+                       defaultSetting: false,
+                       want:           false,
+               },
+               {
+                       name:           "invalid value - use default true",
+                       annotations:    map[string]string{"key1": "invalid"},
+                       annotationKey:  "key1",
+                       defaultSetting: true,
+                       want:           true,
+               },
+               {
+                       name:           "invalid value - use default false",
+                       annotations:    map[string]string{"key1": "yes"},
+                       annotationKey:  "key1",
+                       defaultSetting: false,
+                       want:           false,
+               },
+               {
+                       name:           "empty value - use default",
+                       annotations:    map[string]string{"key1": ""},
+                       annotationKey:  "key1",
+                       defaultSetting: true,
+                       want:           true,
+               },
+               {
+                       name:           "nil annotations - use default",
+                       annotations:    nil,
+                       annotationKey:  "key1",
+                       defaultSetting: true,
+                       want:           true,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       service := &corev1.Service{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:        "test-service",
+                                       Namespace:   "default",
+                                       Annotations: tt.annotations,
+                               },
+                       }
+                       if got := getBoolFromServiceAnnotation(service, 
tt.annotationKey, tt.defaultSetting); got != tt.want {
+                               t.Errorf("getBoolFromServiceAnnotation() = %v, 
want %v", got, tt.want)
+                       }
+               })
+       }
+}
diff --git a/protocol_test.go b/protocol_test.go
new file mode 100644
index 00000000..84ff78a3
--- /dev/null
+++ b/protocol_test.go
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package cloudstack
+
+import (
+       "testing"
+
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestLoadBalancerProtocol_CSProtocol(t *testing.T) {
+       tests := []struct {
+               name     string
+               protocol LoadBalancerProtocol
+               want     string
+       }{
+               {
+                       name:     "TCP protocol",
+                       protocol: LoadBalancerProtocolTCP,
+                       want:     "tcp",
+               },
+               {
+                       name:     "UDP protocol",
+                       protocol: LoadBalancerProtocolUDP,
+                       want:     "udp",
+               },
+               {
+                       name:     "TCP Proxy protocol",
+                       protocol: LoadBalancerProtocolTCPProxy,
+                       want:     "tcp-proxy",
+               },
+               {
+                       name:     "Invalid protocol",
+                       protocol: LoadBalancerProtocolInvalid,
+                       want:     "",
+               },
+               {
+                       name:     "Unknown protocol value",
+                       protocol: LoadBalancerProtocol(999),
+                       want:     "",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if got := tt.protocol.CSProtocol(); got != tt.want {
+                               t.Errorf("CSProtocol() = %v, want %v", got, 
tt.want)
+                       }
+               })
+       }
+}
+
+func TestLoadBalancerProtocol_IPProtocol(t *testing.T) {
+       tests := []struct {
+               name     string
+               protocol LoadBalancerProtocol
+               want     string
+       }{
+               {
+                       name:     "TCP protocol maps to tcp",
+                       protocol: LoadBalancerProtocolTCP,
+                       want:     "tcp",
+               },
+               {
+                       name:     "TCP Proxy protocol also maps to tcp",
+                       protocol: LoadBalancerProtocolTCPProxy,
+                       want:     "tcp",
+               },
+               {
+                       name:     "UDP protocol maps to udp",
+                       protocol: LoadBalancerProtocolUDP,
+                       want:     "udp",
+               },
+               {
+                       name:     "Invalid protocol returns empty",
+                       protocol: LoadBalancerProtocolInvalid,
+                       want:     "",
+               },
+               {
+                       name:     "Unknown protocol value returns empty",
+                       protocol: LoadBalancerProtocol(999),
+                       want:     "",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if got := tt.protocol.IPProtocol(); got != tt.want {
+                               t.Errorf("IPProtocol() = %v, want %v", got, 
tt.want)
+                       }
+               })
+       }
+}
+
+func TestLoadBalancerProtocol_String(t *testing.T) {
+       // String() should return the same as CSProtocol()
+       protocols := []LoadBalancerProtocol{
+               LoadBalancerProtocolTCP,
+               LoadBalancerProtocolUDP,
+               LoadBalancerProtocolTCPProxy,
+               LoadBalancerProtocolInvalid,
+       }
+
+       for _, p := range protocols {
+               if got, want := p.String(), p.CSProtocol(); got != want {
+                       t.Errorf("String() = %v, want %v (same as CSProtocol)", 
got, want)
+               }
+       }
+}
+
+func TestProtocolFromLoadBalancer(t *testing.T) {
+       tests := []struct {
+               name     string
+               protocol string
+               want     LoadBalancerProtocol
+       }{
+               {
+                       name:     "tcp string",
+                       protocol: "tcp",
+                       want:     LoadBalancerProtocolTCP,
+               },
+               {
+                       name:     "udp string",
+                       protocol: "udp",
+                       want:     LoadBalancerProtocolUDP,
+               },
+               {
+                       name:     "tcp-proxy string",
+                       protocol: "tcp-proxy",
+                       want:     LoadBalancerProtocolTCPProxy,
+               },
+               {
+                       name:     "empty string returns invalid",
+                       protocol: "",
+                       want:     LoadBalancerProtocolInvalid,
+               },
+               {
+                       name:     "unknown protocol returns invalid",
+                       protocol: "icmp",
+                       want:     LoadBalancerProtocolInvalid,
+               },
+               {
+                       name:     "uppercase TCP returns invalid",
+                       protocol: "TCP",
+                       want:     LoadBalancerProtocolInvalid,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if got := ProtocolFromLoadBalancer(tt.protocol); got != 
tt.want {
+                               t.Errorf("ProtocolFromLoadBalancer(%q) = %v, 
want %v", tt.protocol, got, tt.want)
+                       }
+               })
+       }
+}
+
+func TestProtocolFromServicePort(t *testing.T) {
+       tests := []struct {
+               name        string
+               port        corev1.ServicePort
+               annotations map[string]string
+               want        LoadBalancerProtocol
+       }{
+               {
+                       name: "TCP port without proxy annotation",
+                       port: corev1.ServicePort{
+                               Protocol: corev1.ProtocolTCP,
+                               Port:     80,
+                       },
+                       annotations: nil,
+                       want:        LoadBalancerProtocolTCP,
+               },
+               {
+                       name: "TCP port with proxy annotation true",
+                       port: corev1.ServicePort{
+                               Protocol: corev1.ProtocolTCP,
+                               Port:     80,
+                       },
+                       annotations: map[string]string{
+                               ServiceAnnotationLoadBalancerProxyProtocol: 
"true",
+                       },
+                       want: LoadBalancerProtocolTCPProxy,
+               },
+               {
+                       name: "TCP port with proxy annotation false",
+                       port: corev1.ServicePort{
+                               Protocol: corev1.ProtocolTCP,
+                               Port:     80,
+                       },
+                       annotations: map[string]string{
+                               ServiceAnnotationLoadBalancerProxyProtocol: 
"false",
+                       },
+                       want: LoadBalancerProtocolTCP,
+               },
+               {
+                       name: "UDP port",
+                       port: corev1.ServicePort{
+                               Protocol: corev1.ProtocolUDP,
+                               Port:     53,
+                       },
+                       annotations: nil,
+                       want:        LoadBalancerProtocolUDP,
+               },
+               {
+                       name: "UDP port ignores proxy annotation",
+                       port: corev1.ServicePort{
+                               Protocol: corev1.ProtocolUDP,
+                               Port:     53,
+                       },
+                       annotations: map[string]string{
+                               ServiceAnnotationLoadBalancerProxyProtocol: 
"true",
+                       },
+                       want: LoadBalancerProtocolUDP,
+               },
+               {
+                       name: "SCTP port returns invalid",
+                       port: corev1.ServicePort{
+                               Protocol: corev1.ProtocolSCTP,
+                               Port:     80,
+                       },
+                       annotations: nil,
+                       want:        LoadBalancerProtocolInvalid,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       service := &corev1.Service{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:        "test-service",
+                                       Namespace:   "default",
+                                       Annotations: tt.annotations,
+                               },
+                       }
+                       if got := ProtocolFromServicePort(tt.port, service); 
got != tt.want {
+                               t.Errorf("ProtocolFromServicePort() = %v, want 
%v", got, tt.want)
+                       }
+               })
+       }
+}

Reply via email to