Skip to content

Commit d7c3bab

Browse files
authored
Merge pull request #288 from minmzzhang/unit-test-coverage
2 parents b77652d + 9b78468 commit d7c3bab

File tree

8 files changed

+1007
-120
lines changed

8 files changed

+1007
-120
lines changed

controllers/cidr_handler_test.go

Lines changed: 506 additions & 24 deletions
Large diffs are not rendered by default.

controllers/safe_cache_handler.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,9 @@ func (s *SafeCache) GetSize() int {
6262
defer s.mu.RUnlock()
6363
return len(s.cache)
6464
}
65+
66+
func (s *SafeCache) Clear() {
67+
s.mu.Lock()
68+
defer s.mu.Unlock()
69+
s.cache = make(map[string]interface{})
70+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ require (
9191
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
9292
google.golang.org/grpc v1.65.0 // indirect
9393
google.golang.org/protobuf v1.35.1 // indirect
94+
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
9495
gopkg.in/inf.v0 v0.9.1 // indirect
9596
gopkg.in/yaml.v2 v2.4.0 // indirect
9697
gopkg.in/yaml.v3 v3.0.1 // indirect

internal/plugin/dynamic_test.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package plugin_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
. "github.com/foundation-model-stack/multi-nic-cni/internal/plugin"
8+
. "github.com/onsi/ginkgo/v2"
9+
. "github.com/onsi/gomega"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12+
"k8s.io/apimachinery/pkg/runtime/schema"
13+
"k8s.io/apimachinery/pkg/types"
14+
"k8s.io/apimachinery/pkg/watch"
15+
"k8s.io/client-go/dynamic"
16+
)
17+
18+
// Mock resource interface
19+
type mockResourceInterface struct {
20+
items []map[string]interface{}
21+
namespace string
22+
}
23+
24+
// List implements dynamic.ResourceInterface
25+
func (m *mockResourceInterface) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
26+
if len(m.items) == 0 {
27+
return &unstructured.UnstructuredList{}, nil
28+
}
29+
30+
list := &unstructured.UnstructuredList{
31+
Items: make([]unstructured.Unstructured, len(m.items)),
32+
}
33+
34+
for i, item := range m.items {
35+
list.Items[i] = unstructured.Unstructured{
36+
Object: item,
37+
}
38+
}
39+
return list, nil
40+
}
41+
42+
// Get implements dynamic.ResourceInterface
43+
func (m *mockResourceInterface) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
44+
return nil, fmt.Errorf("not implemented")
45+
}
46+
47+
// Create implements dynamic.ResourceInterface
48+
func (m *mockResourceInterface) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
49+
return nil, fmt.Errorf("not implemented")
50+
}
51+
52+
// Update implements dynamic.ResourceInterface
53+
func (m *mockResourceInterface) Update(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
54+
return nil, fmt.Errorf("not implemented")
55+
}
56+
57+
// UpdateStatus implements dynamic.ResourceInterface
58+
func (m *mockResourceInterface) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
59+
return nil, fmt.Errorf("not implemented")
60+
}
61+
62+
// Delete implements dynamic.ResourceInterface
63+
func (m *mockResourceInterface) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
64+
return fmt.Errorf("not implemented")
65+
}
66+
67+
// DeleteCollection implements dynamic.ResourceInterface
68+
func (m *mockResourceInterface) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
69+
return fmt.Errorf("not implemented")
70+
}
71+
72+
// Watch implements dynamic.ResourceInterface
73+
func (m *mockResourceInterface) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
74+
return nil, fmt.Errorf("not implemented")
75+
}
76+
77+
// Patch implements dynamic.ResourceInterface
78+
func (m *mockResourceInterface) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
79+
return nil, fmt.Errorf("not implemented")
80+
}
81+
82+
// Apply implements dynamic.ResourceInterface
83+
func (m *mockResourceInterface) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) {
84+
return nil, fmt.Errorf("not implemented")
85+
}
86+
87+
// ApplyStatus implements dynamic.ResourceInterface
88+
func (m *mockResourceInterface) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) {
89+
return nil, fmt.Errorf("not implemented")
90+
}
91+
92+
// Namespace implements dynamic.ResourceInterface
93+
func (m *mockResourceInterface) Namespace(namespace string) dynamic.ResourceInterface {
94+
m.namespace = namespace
95+
return m
96+
}
97+
98+
type mockDynamicClient struct {
99+
resources map[string]*mockResourceInterface
100+
}
101+
102+
func (m *mockDynamicClient) Resource(gvr schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
103+
if ri, exists := m.resources["default"]; exists {
104+
return ri
105+
}
106+
return &mockResourceInterface{items: []map[string]interface{}{}}
107+
}
108+
109+
var _ = Describe("DynamicHandler", func() {
110+
var (
111+
handler *DynamicHandler
112+
client *mockDynamicClient
113+
gvr schema.GroupVersionResource
114+
)
115+
116+
BeforeEach(func() {
117+
client = &mockDynamicClient{
118+
resources: make(map[string]*mockResourceInterface),
119+
}
120+
121+
gvr = schema.GroupVersionResource{
122+
Group: "test.group",
123+
Version: "v1",
124+
Resource: "testresources",
125+
}
126+
127+
handler = &DynamicHandler{
128+
DYN: client,
129+
GVR: gvr,
130+
}
131+
})
132+
133+
Context("GetFirst", func() {
134+
It("should return the first item when items exist", func() {
135+
client.resources["default"] = &mockResourceInterface{
136+
items: []map[string]interface{}{
137+
{
138+
"apiVersion": "test.group/v1",
139+
"kind": "TestResource",
140+
"metadata": map[string]interface{}{
141+
"name": "test1",
142+
"namespace": "default",
143+
},
144+
"spec": map[string]interface{}{
145+
"key": "value1",
146+
},
147+
},
148+
{
149+
"apiVersion": "test.group/v1",
150+
"kind": "TestResource",
151+
"metadata": map[string]interface{}{
152+
"name": "test2",
153+
"namespace": "default",
154+
},
155+
"spec": map[string]interface{}{
156+
"key": "value2",
157+
},
158+
},
159+
},
160+
}
161+
162+
var result map[string]interface{}
163+
err := handler.GetFirst("default", &result)
164+
Expect(err).NotTo(HaveOccurred())
165+
Expect(result["metadata"].(map[string]interface{})["name"]).To(Equal("test1"))
166+
})
167+
168+
It("should return error when no items exist", func() {
169+
client.resources["default"] = &mockResourceInterface{
170+
items: []map[string]interface{}{},
171+
}
172+
173+
var result map[string]interface{}
174+
err := handler.GetFirst("default", &result)
175+
Expect(err).To(HaveOccurred())
176+
Expect(err.Error()).To(Equal("no item"))
177+
})
178+
179+
It("should handle unmarshalling errors", func() {
180+
client.resources["default"] = &mockResourceInterface{
181+
items: []map[string]interface{}{
182+
{
183+
"metadata": map[string]interface{}{
184+
"invalid": make(chan int),
185+
},
186+
},
187+
},
188+
}
189+
190+
var result map[string]interface{}
191+
err := handler.GetFirst("default", &result)
192+
Expect(err).To(HaveOccurred())
193+
})
194+
})
195+
})

internal/plugin/mellanox_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2022- IBM Inc. All rights reserved
3+
* SPDX-License-Identifier: Apache2.0
4+
*/
5+
6+
package plugin_test
7+
8+
import (
9+
. "github.com/foundation-model-stack/multi-nic-cni/internal/plugin"
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
"k8s.io/apimachinery/pkg/runtime/schema"
13+
"k8s.io/client-go/rest"
14+
)
15+
16+
var _ = Describe("Mellanox Plugin", func() {
17+
var (
18+
plugin *MellanoxPlugin
19+
testConfig *rest.Config
20+
)
21+
22+
BeforeEach(func() {
23+
plugin = &MellanoxPlugin{}
24+
testConfig = &rest.Config{
25+
Host: "https://localhost:8443",
26+
TLSClientConfig: rest.TLSClientConfig{
27+
Insecure: true,
28+
},
29+
APIPath: "api",
30+
ContentConfig: rest.ContentConfig{
31+
GroupVersion: &schema.GroupVersion{Group: "networking.k8s.io", Version: "v1"},
32+
NegotiatedSerializer: nil,
33+
},
34+
}
35+
// Use the existing config if available
36+
if Cfg != nil {
37+
testConfig = Cfg
38+
}
39+
})
40+
41+
Context("Init", Ordered, func() {
42+
It("should initialize successfully with valid config", func() {
43+
err := plugin.Init(testConfig)
44+
Expect(err).NotTo(HaveOccurred())
45+
46+
By("checking MellanoxNetworkHandler initialization")
47+
Expect(plugin.MellanoxNetworkHandler).NotTo(BeNil())
48+
mellanoxNetwork, _ := schema.ParseResourceArg(MELLANOX_NETWORK_RESOURCE)
49+
Expect(mellanoxNetwork).NotTo(BeNil())
50+
Expect(plugin.MellanoxNetworkHandler.GVR).To(Equal(*mellanoxNetwork))
51+
Expect(plugin.MellanoxNetworkHandler.DYN).NotTo(BeNil())
52+
53+
By("checking MellanoxNicClusterPolicyHandler initialization")
54+
Expect(plugin.MellanoxNicClusterPolicyHandler).NotTo(BeNil())
55+
nicClusterPolicy, _ := schema.ParseResourceArg(MELLANOX_NIC_CLUSTER_POLICY_RESOURCE)
56+
Expect(nicClusterPolicy).NotTo(BeNil())
57+
Expect(plugin.MellanoxNicClusterPolicyHandler.GVR).To(Equal(*nicClusterPolicy))
58+
Expect(plugin.MellanoxNicClusterPolicyHandler.DYN).NotTo(BeNil())
59+
})
60+
61+
When("config is invalid", func() {
62+
It("should return error", func() {
63+
invalidConfig := &rest.Config{
64+
Host: "https://invalid-host:8443",
65+
// Make TLS config invalid
66+
TLSClientConfig: rest.TLSClientConfig{
67+
CertFile: "non-existent-cert.pem",
68+
KeyFile: "non-existent-key.pem",
69+
},
70+
}
71+
err := plugin.Init(invalidConfig)
72+
Expect(err).To(HaveOccurred())
73+
Expect(err.Error()).To(ContainSubstring("open non-existent-cert.pem: no such file or directory"))
74+
})
75+
})
76+
})
77+
})

0 commit comments

Comments
 (0)