Skip to content

Commit f165f2c

Browse files
committed
Add tests
1 parent 359f208 commit f165f2c

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed

cns/service/main_test.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@ package main
33
import (
44
"bytes"
55
"context"
6+
"encoding/json"
67
"io"
78
"net/http"
9+
"net/http/httptest"
10+
"strings"
811
"testing"
12+
"time"
913

1014
"github.com/Azure/azure-container-networking/cns"
1115
"github.com/Azure/azure-container-networking/cns/fakes"
1216
"github.com/Azure/azure-container-networking/cns/logger"
17+
"github.com/Azure/azure-container-networking/crd/multitenancy/api/v1alpha1"
1318
"github.com/stretchr/testify/assert"
19+
corev1 "k8s.io/api/core/v1"
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"k8s.io/client-go/rest"
1422
)
1523

1624
// MockHTTPClient is a mock implementation of HTTPClient
@@ -69,3 +77,181 @@ func TestSendRegisterNodeRequest_StatusAccepted(t *testing.T) {
6977

7078
assert.Error(t, sendRegisterNodeRequest(ctx, mockClient, httpServiceFake, nodeRegisterReq, url))
7179
}
80+
81+
func TestCreateOrUpdateNodeInfoCRD_WithTestifyMock_DirectCall(t *testing.T) {
82+
// Create mock IMDS server
83+
mockIMDSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
84+
if strings.Contains(r.URL.Path, "/metadata/instance/compute") {
85+
w.Header().Set("Content-Type", "application/json")
86+
w.WriteHeader(http.StatusOK)
87+
response := map[string]interface{}{
88+
"vmId": "test-vm-unique-id-12345",
89+
"name": "test-vm",
90+
"resourceGroupName": "test-rg",
91+
}
92+
json.NewEncoder(w).Encode(response)
93+
return
94+
}
95+
w.WriteHeader(http.StatusNotFound)
96+
}))
97+
defer mockIMDSServer.Close()
98+
99+
// Create mock CNS server
100+
mockCNSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
101+
if strings.Contains(r.URL.Path, "/homeaz") || strings.Contains(r.URL.Path, "homeaz") {
102+
w.Header().Set("Content-Type", "application/json")
103+
w.WriteHeader(http.StatusOK)
104+
response := map[string]interface{}{
105+
"ReturnCode": 0,
106+
"Message": "",
107+
"HomeAzResponse": map[string]interface{}{
108+
"IsSupported": true,
109+
"HomeAz": uint(2),
110+
},
111+
}
112+
json.NewEncoder(w).Encode(response)
113+
return
114+
}
115+
w.WriteHeader(http.StatusNotFound)
116+
}))
117+
defer mockCNSServer.Close()
118+
119+
// Set up HTTP transport to mock IMDS and CNS
120+
originalTransport := http.DefaultTransport
121+
defer func() { http.DefaultTransport = originalTransport }()
122+
123+
http.DefaultTransport = &mockTransport{
124+
imdsServer: mockIMDSServer,
125+
cnsServer: mockCNSServer,
126+
original: originalTransport,
127+
}
128+
129+
// Create a mock Kubernetes server that captures the NodeInfo being created
130+
var capturedNodeInfo *v1alpha1.NodeInfo
131+
132+
mockK8sServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
133+
// Handle specific API group discovery - multitenancy.acn.azure.com
134+
if r.URL.Path == "/apis/multitenancy.acn.azure.com/v1alpha1" && r.Method == "GET" {
135+
w.Header().Set("Content-Type", "application/json")
136+
w.WriteHeader(http.StatusOK)
137+
json.NewEncoder(w).Encode(map[string]interface{}{
138+
"kind": "APIResourceList",
139+
"groupVersion": "multitenancy.acn.azure.com/v1alpha1",
140+
"resources": []map[string]interface{}{
141+
{
142+
"name": "nodeinfos",
143+
"singularName": "nodeinfo",
144+
"namespaced": false,
145+
"kind": "NodeInfo",
146+
"verbs": []string{"create", "delete", "get", "list", "patch", "update", "watch"},
147+
},
148+
},
149+
})
150+
return
151+
}
152+
153+
// Handle NodeInfo resource requests
154+
if strings.Contains(r.URL.Path, "nodeinfos") || strings.Contains(r.URL.Path, "multitenancy") {
155+
if r.Method == "POST" || r.Method == "PATCH" || r.Method == "PUT" {
156+
body, _ := io.ReadAll(r.Body)
157+
158+
// Try to parse the NodeInfo from the request
159+
var nodeInfo v1alpha1.NodeInfo
160+
if err := json.Unmarshal(body, &nodeInfo); err == nil {
161+
capturedNodeInfo = &nodeInfo
162+
}
163+
164+
w.Header().Set("Content-Type", "application/json")
165+
w.WriteHeader(http.StatusOK)
166+
// Return the created NodeInfo
167+
json.NewEncoder(w).Encode(map[string]interface{}{
168+
"apiVersion": "multitenancy.acn.azure.com/v1alpha1",
169+
"kind": "NodeInfo",
170+
"metadata": map[string]interface{}{
171+
"name": "test-node",
172+
},
173+
"spec": map[string]interface{}{
174+
"vmUniqueID": "test-vm-unique-id-12345",
175+
"homeAZ": "AZ02",
176+
},
177+
})
178+
return
179+
}
180+
181+
// Handle GET requests (checking if NodeInfo exists)
182+
if r.Method == "GET" {
183+
w.Header().Set("Content-Type", "application/json")
184+
w.WriteHeader(http.StatusNotFound) // Simulate NodeInfo doesn't exist yet
185+
json.NewEncoder(w).Encode(map[string]interface{}{
186+
"kind": "Status",
187+
"status": "Failure",
188+
"code": 404,
189+
})
190+
return
191+
}
192+
}
193+
194+
// Default success response for any other API calls
195+
w.Header().Set("Content-Type", "application/json")
196+
w.WriteHeader(http.StatusOK)
197+
json.NewEncoder(w).Encode(map[string]interface{}{
198+
"kind": "Status",
199+
"status": "Success",
200+
})
201+
}))
202+
defer mockK8sServer.Close()
203+
204+
// Test the function with mocked dependencies
205+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
206+
defer cancel()
207+
208+
// Point to our mock Kubernetes server
209+
restConfig := &rest.Config{
210+
Host: mockK8sServer.URL,
211+
}
212+
213+
node := &corev1.Node{
214+
ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
215+
}
216+
217+
// Call the createOrUpdateNodeInfoCRD function
218+
err := createOrUpdateNodeInfoCRD(ctx, restConfig, node)
219+
220+
// Verify the function succeeded
221+
assert.NoError(t, err, "Function should succeed with mocked dependencies")
222+
223+
// Verify the captured values
224+
assert.NotNil(t, capturedNodeInfo, "NodeInfo should have been captured from K8s API call")
225+
if capturedNodeInfo != nil {
226+
assert.Equal(t, "test-node", capturedNodeInfo.Name, "NodeInfo name should match")
227+
assert.Equal(t, "test-vm-unique-id-12345", capturedNodeInfo.Spec.VMUniqueID, "VMUniqueID should be from IMDS")
228+
assert.Equal(t, "AZ02", capturedNodeInfo.Spec.HomeAZ, "HomeAZ should be formatted from CNS response")
229+
}
230+
}
231+
232+
// mockTransport redirects HTTP requests to mock servers for testing.
233+
// It intercepts requests to IMDS and CNS endpoints and routes them to local test servers.
234+
type mockTransport struct {
235+
imdsServer *httptest.Server
236+
cnsServer *httptest.Server
237+
original http.RoundTripper
238+
}
239+
240+
func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
241+
// Redirect IMDS calls to mock IMDS server
242+
if req.URL.Host == "169.254.169.254" {
243+
req.URL.Scheme = "http"
244+
req.URL.Host = strings.TrimPrefix(m.imdsServer.URL, "http://")
245+
return m.original.RoundTrip(req)
246+
}
247+
248+
// Redirect CNS calls to mock CNS server
249+
if req.URL.Host == "localhost:10090" || strings.Contains(req.URL.Host, "10090") {
250+
req.URL.Scheme = "http"
251+
req.URL.Host = strings.TrimPrefix(m.cnsServer.URL, "http://")
252+
return m.original.RoundTrip(req)
253+
}
254+
255+
// All other calls go through original transport
256+
return m.original.RoundTrip(req)
257+
}

0 commit comments

Comments
 (0)