1 package services
2
3 import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "testing"
8 "time"
9
10 "github.com/gin-gonic/gin"
11 "github.com/stretchr/testify/assert"
12 corev1 "k8s.io/api/core/v1"
13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14 "k8s.io/apimachinery/pkg/runtime"
15 "sigs.k8s.io/controller-runtime/pkg/client"
16 "sigs.k8s.io/controller-runtime/pkg/client/fake"
17
18 v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
19
20 "edge-infra.dev/pkg/edge/device-registrar/config"
21
22 api "edge-infra.dev/pkg/edge/device-registrar/api/v1alpha1"
23 )
24
25 func GenerateMockClient(scheme *runtime.Scheme, test string) client.WithWatch {
26 var timestamp time.Time
27 if test == "expired" {
28 timestamp = time.Now().Add(-time.Hour * 2)
29 } else {
30 timestamp = time.Now()
31 }
32 deviceBinding := api.DeviceBinding{
33 ObjectMeta: metav1.ObjectMeta{
34 Name: "example-device-binding",
35 Namespace: "default",
36 },
37 Spec: api.DeviceBindingSpec{
38 Device: api.DeviceSubject{
39 Name: "example-device",
40 },
41 Applications: []api.ApplicationSubject{
42 {
43 ID: "123e4567-e89b-12d3-a456-426614174000",
44 Name: "example-application",
45 Kind: "ExternalApplication",
46 },
47 },
48 },
49 Status: api.DeviceBindingStatus{
50 ActivationCode: "F7RAXZXBFZQRPAU",
51 Timestamp: metav1.Time{
52 Time: timestamp,
53 },
54 },
55 }
56
57 deviceSecret := &corev1.Secret{
58 ObjectMeta: metav1.ObjectMeta{
59 Name: "example-device-example-application-secret",
60 Namespace: "default",
61 },
62 Data: map[string][]byte{
63 "client_id": []byte("example-client-id"),
64 "client_secret": []byte("example-client-secret"),
65 },
66 }
67
68 ienNode := &v1ien.IENode{
69 ObjectMeta: metav1.ObjectMeta{
70 Name: "mock-ienode",
71 Namespace: "default",
72 },
73 Spec: v1ien.IENodeSpec{
74 Role: "worker",
75 Lane: "1",
76 Network: []v1ien.Network{
77 {
78 MacAddress: "90:74:c2:0f:d8:77",
79 Addresses: []string{"192.168.1.2/24"},
80 Gateway4: "192.168.1.1",
81 DHCP4: false,
82 DHCP6: false,
83 },
84 },
85 PrimaryInterface: &v1ien.PrimaryInterface{
86 InterfaceID: "9647e74f-aba3-447f-b327-31ecce7f8d4c",
87 MacAddresses: []string{"90:74:c2:0f:d8:77"},
88 },
89 },
90 }
91
92 var mockClient client.WithWatch
93 if test == "NoSecret" {
94 mockClient = fake.NewClientBuilder().WithScheme(scheme).WithObjects(&deviceBinding, ienNode).Build()
95 } else {
96 mockClient = fake.NewClientBuilder().WithScheme(scheme).WithObjects(&deviceBinding, deviceSecret, ienNode).Build()
97 }
98
99 return mockClient
100 }
101
102 func injectmockClient(router *gin.Engine, mockClient client.Client, t *testing.T) {
103 router.Use(
104
105 func(c *gin.Context) {
106 c.Set("k8sClient", mockClient)
107 c.Next()
108
109 k8sClient, exists := c.Get("k8sClient")
110
111
112 assert.True(t, exists, "k8sClient should exist in the context")
113 assert.NotNil(t, k8sClient, "k8sClient should not be nil")
114 },
115 )
116 }
117
118 func TestBootstrap(t *testing.T) {
119 router, scheme := setupTest()
120 mockClient := GenerateMockClient(scheme, "")
121 injectmockClient(router, mockClient, t)
122
123 config.AddToScheme(scheme)
124
125
126 c, _ := gin.CreateTestContext(httptest.NewRecorder())
127 c.Params = append(c.Params, gin.Param{Key: "activationCode", Value: "F7RAXZXBFZQRPAU"})
128
129 binding, err := retrieveDeviceRegistration(context.TODO(), mockClient, c)
130 assert.NoError(t, err)
131 assert.Equal(t, "example-device-binding", binding.GetName())
132 }
133
134 func TestEndpoint(t *testing.T) {
135 router, scheme := setupTest()
136 mockClient := GenerateMockConnectClient(scheme, "")
137 injectmockClient(router, mockClient, t)
138
139 config.AddToScheme(scheme)
140
141 router.GET("/bootstrap/:activationCode", BootstrapDevice)
142
143 deviceBinding := &api.DeviceBinding{
144 ObjectMeta: metav1.ObjectMeta{
145 Name: "example-device-binding",
146 Namespace: "device-registrar-clients",
147 },
148 Spec: api.DeviceBindingSpec{
149 Device: api.DeviceSubject{
150 APIGroup: api.GroupVersion.Group,
151 Kind: "ExternalDevice",
152 Name: "example-device",
153 },
154 Applications: []api.ApplicationSubject{
155 {
156 APIGroup: api.GroupVersion.Group,
157 Kind: "ExternalApplication",
158 Name: "example-application",
159 ID: "app-id",
160 },
161 },
162 },
163 Status: api.DeviceBindingStatus{
164 ActivationCode: "F7RAXZXBFZQRPAU",
165 Timestamp: metav1.Time{
166 Time: time.Now(),
167 },
168 CodeUsed: false,
169 },
170 }
171 err := mockClient.Create(context.TODO(), deviceBinding)
172 assert.NoError(t, err)
173
174 extDevice := &api.ExternalDevice{
175 ObjectMeta: metav1.ObjectMeta{
176 Name: "example-device",
177 Namespace: "device-registrar-clients",
178 },
179 Spec: api.ExternalDeviceSpec{
180 Name: "example-device",
181 Description: "test device",
182 SN: "examplesn",
183 },
184 }
185 err = mockClient.Create(context.TODO(), extDevice)
186 assert.NoError(t, err)
187
188 req, _ := http.NewRequest(http.MethodGet, "/bootstrap/F7RAXZXBFZQRPAU", nil)
189 w := httptest.NewRecorder()
190 router.ServeHTTP(w, req)
191
192
193 assert.Equal(t, http.StatusOK, w.Code)
194 }
195
196 func TestBadActivation(t *testing.T) {
197 router, scheme := setupTest()
198 mockClient := GenerateMockClient(scheme, "")
199 injectmockClient(router, mockClient, t)
200
201 config.AddToScheme(scheme)
202
203 router.GET("/bootstrap/:activationCode", BootstrapDevice)
204
205 req, _ := http.NewRequest(http.MethodGet, "/bootstrap/WRONGCODE", nil)
206 w := httptest.NewRecorder()
207 router.ServeHTTP(w, req)
208
209
210 assert.Equal(t, http.StatusUnauthorized, w.Code)
211
212
213 assert.JSONEq(t, `{"error":"invalid activation code"}`, w.Body.String())
214 }
215
216 func TestNoSecret(t *testing.T) {
217 router, scheme := setupTest()
218 mockClient := GenerateMockClient(scheme, "NoSecret")
219 injectmockClient(router, mockClient, t)
220
221 config.AddToScheme(scheme)
222
223 router.GET("/bootstrap/:activationCode", BootstrapDevice)
224
225 req, _ := http.NewRequest(http.MethodGet, "/bootstrap/F7RAXZXBFZQRPAU", nil)
226 w := httptest.NewRecorder()
227 router.ServeHTTP(w, req)
228
229
230 assert.Equal(t, http.StatusInternalServerError, w.Code)
231
232
233 assert.JSONEq(t, `{"error":"failed to find EdgeID client secret: secrets \"example-device-example-application-secret\" not found"}`, w.Body.String())
234 }
235
236 func TestExpiredActivation(t *testing.T) {
237 router, scheme := setupTest()
238 mockClient := GenerateMockClient(scheme, "expired")
239 injectmockClient(router, mockClient, t)
240
241 config.AddToScheme(scheme)
242
243 router.GET("/bootstrap/:activationCode", BootstrapDevice)
244
245 req, _ := http.NewRequest(http.MethodGet, "/bootstrap/F7RAXZXBFZQRPAU", nil)
246 w := httptest.NewRecorder()
247 router.ServeHTTP(w, req)
248
249 assert.Equal(t, http.StatusUnauthorized, w.Code)
250
251
252 assert.JSONEq(t, `{"error":"activation code has expired"}`, w.Body.String())
253 }
254
255 func TestMultipleBinding(t *testing.T) {
256 router, scheme := setupTest()
257 mockClient := GenerateMockClient(scheme, "")
258 injectmockClient(router, mockClient, t)
259
260 config.AddToScheme(scheme)
261
262
263 c, _ := gin.CreateTestContext(httptest.NewRecorder())
264
265 deviceBinding2 := api.DeviceBinding{
266 ObjectMeta: metav1.ObjectMeta{
267 Name: "device2",
268 Namespace: "default",
269 },
270 Spec: api.DeviceBindingSpec{
271 Device: api.DeviceSubject{
272 Name: "example-device2",
273 },
274 Applications: []api.ApplicationSubject{
275 {
276 ID: "123e4567-e89b-12d3-a456-426614174000",
277 Name: "example-application",
278 Kind: "ExternalApplication",
279 },
280 },
281 },
282 Status: api.DeviceBindingStatus{
283 ActivationCode: "F7RAXZXBFZQTEST",
284 Timestamp: metav1.Time{
285 Time: time.Now(),
286 },
287 },
288 }
289
290 deviceSecret := &corev1.Secret{
291 ObjectMeta: metav1.ObjectMeta{
292 Name: "example-device2-example-application-secret",
293 Namespace: "default",
294 },
295 Data: map[string][]byte{
296 "client_id": []byte("example-client-id"),
297 "client_secret": []byte("example-client-secret"),
298 },
299 }
300
301
302 err := mockClient.Create(context.TODO(), &deviceBinding2)
303 assert.NoError(t, err)
304
305 err = mockClient.Create(context.TODO(), deviceSecret)
306 assert.NoError(t, err)
307
308 c.Params = append(c.Params, gin.Param{Key: "activationCode", Value: "F7RAXZXBFZQRPAU"})
309 binding, err := retrieveDeviceRegistration(context.TODO(), mockClient, c)
310 assert.NoError(t, err)
311 assert.Equal(t, "example-device-binding", binding.GetName())
312
313
314 c.Params = []gin.Param{}
315 c.Params = append(c.Params, gin.Param{Key: "activationCode", Value: "F7RAXZXBFZQTEST"})
316 binding2, err := retrieveDeviceRegistration(context.TODO(), mockClient, c)
317 assert.NoError(t, err)
318 assert.Equal(t, "device2", binding2.GetName())
319 }
320
View as plain text