package services import ( "context" "net/http" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1" "edge-infra.dev/pkg/edge/device-registrar/config" api "edge-infra.dev/pkg/edge/device-registrar/api/v1alpha1" ) func GenerateMockClient(scheme *runtime.Scheme, test string) client.WithWatch { var timestamp time.Time if test == "expired" { timestamp = time.Now().Add(-time.Hour * 2) } else { timestamp = time.Now() } deviceBinding := api.DeviceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "example-device-binding", Namespace: "default", }, Spec: api.DeviceBindingSpec{ Device: api.DeviceSubject{ Name: "example-device", }, Applications: []api.ApplicationSubject{ { ID: "123e4567-e89b-12d3-a456-426614174000", Name: "example-application", Kind: "ExternalApplication", }, }, }, Status: api.DeviceBindingStatus{ ActivationCode: "F7RAXZXBFZQRPAU", Timestamp: metav1.Time{ Time: timestamp, }, }, } deviceSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "example-device-example-application-secret", Namespace: "default", }, Data: map[string][]byte{ "client_id": []byte("example-client-id"), "client_secret": []byte("example-client-secret"), }, } ienNode := &v1ien.IENode{ ObjectMeta: metav1.ObjectMeta{ Name: "mock-ienode", Namespace: "default", }, Spec: v1ien.IENodeSpec{ Role: "worker", Lane: "1", Network: []v1ien.Network{ { MacAddress: "90:74:c2:0f:d8:77", Addresses: []string{"192.168.1.2/24"}, Gateway4: "192.168.1.1", DHCP4: false, DHCP6: false, }, }, PrimaryInterface: &v1ien.PrimaryInterface{ InterfaceID: "9647e74f-aba3-447f-b327-31ecce7f8d4c", MacAddresses: []string{"90:74:c2:0f:d8:77"}, }, }, } var mockClient client.WithWatch if test == "NoSecret" { mockClient = fake.NewClientBuilder().WithScheme(scheme).WithObjects(&deviceBinding, ienNode).Build() } else { mockClient = fake.NewClientBuilder().WithScheme(scheme).WithObjects(&deviceBinding, deviceSecret, ienNode).Build() } return mockClient } func injectmockClient(router *gin.Engine, mockClient client.Client, t *testing.T) { router.Use( // Inject the mock client into the context func(c *gin.Context) { c.Set("k8sClient", mockClient) c.Next() k8sClient, exists := c.Get("k8sClient") // Assert that k8sClient is not nil and exists assert.True(t, exists, "k8sClient should exist in the context") assert.NotNil(t, k8sClient, "k8sClient should not be nil") }, ) } func TestBootstrap(t *testing.T) { router, scheme := setupTest() mockClient := GenerateMockClient(scheme, "") injectmockClient(router, mockClient, t) config.AddToScheme(scheme) // create gin context c, _ := gin.CreateTestContext(httptest.NewRecorder()) c.Params = append(c.Params, gin.Param{Key: "activationCode", Value: "F7RAXZXBFZQRPAU"}) binding, err := retrieveDeviceRegistration(context.TODO(), mockClient, c) assert.NoError(t, err) assert.Equal(t, "example-device-binding", binding.GetName()) } func TestEndpoint(t *testing.T) { router, scheme := setupTest() mockClient := GenerateMockConnectClient(scheme, "") injectmockClient(router, mockClient, t) config.AddToScheme(scheme) router.GET("/bootstrap/:activationCode", BootstrapDevice) deviceBinding := &api.DeviceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "example-device-binding", Namespace: "device-registrar-clients", }, Spec: api.DeviceBindingSpec{ Device: api.DeviceSubject{ APIGroup: api.GroupVersion.Group, Kind: "ExternalDevice", Name: "example-device", }, Applications: []api.ApplicationSubject{ { APIGroup: api.GroupVersion.Group, Kind: "ExternalApplication", Name: "example-application", ID: "app-id", }, }, }, Status: api.DeviceBindingStatus{ ActivationCode: "F7RAXZXBFZQRPAU", Timestamp: metav1.Time{ Time: time.Now(), }, CodeUsed: false, }, } err := mockClient.Create(context.TODO(), deviceBinding) assert.NoError(t, err) extDevice := &api.ExternalDevice{ ObjectMeta: metav1.ObjectMeta{ Name: "example-device", Namespace: "device-registrar-clients", }, Spec: api.ExternalDeviceSpec{ Name: "example-device", Description: "test device", SN: "examplesn", }, } err = mockClient.Create(context.TODO(), extDevice) assert.NoError(t, err) req, _ := http.NewRequest(http.MethodGet, "/bootstrap/F7RAXZXBFZQRPAU", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Check the status code assert.Equal(t, http.StatusOK, w.Code) } func TestBadActivation(t *testing.T) { router, scheme := setupTest() mockClient := GenerateMockClient(scheme, "") injectmockClient(router, mockClient, t) config.AddToScheme(scheme) router.GET("/bootstrap/:activationCode", BootstrapDevice) req, _ := http.NewRequest(http.MethodGet, "/bootstrap/WRONGCODE", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Check the status code assert.Equal(t, http.StatusUnauthorized, w.Code) // Check the response body assert.JSONEq(t, `{"error":"invalid activation code"}`, w.Body.String()) } func TestNoSecret(t *testing.T) { router, scheme := setupTest() mockClient := GenerateMockClient(scheme, "NoSecret") injectmockClient(router, mockClient, t) config.AddToScheme(scheme) router.GET("/bootstrap/:activationCode", BootstrapDevice) req, _ := http.NewRequest(http.MethodGet, "/bootstrap/F7RAXZXBFZQRPAU", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Check the status code assert.Equal(t, http.StatusInternalServerError, w.Code) // Check the response body assert.JSONEq(t, `{"error":"failed to find EdgeID client secret: secrets \"example-device-example-application-secret\" not found"}`, w.Body.String()) } func TestExpiredActivation(t *testing.T) { router, scheme := setupTest() mockClient := GenerateMockClient(scheme, "expired") injectmockClient(router, mockClient, t) config.AddToScheme(scheme) router.GET("/bootstrap/:activationCode", BootstrapDevice) req, _ := http.NewRequest(http.MethodGet, "/bootstrap/F7RAXZXBFZQRPAU", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Check the status code assert.Equal(t, http.StatusUnauthorized, w.Code) // Check the response body assert.JSONEq(t, `{"error":"activation code has expired"}`, w.Body.String()) } func TestMultipleBinding(t *testing.T) { router, scheme := setupTest() mockClient := GenerateMockClient(scheme, "") injectmockClient(router, mockClient, t) config.AddToScheme(scheme) // create gin context c, _ := gin.CreateTestContext(httptest.NewRecorder()) deviceBinding2 := api.DeviceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "device2", Namespace: "default", }, Spec: api.DeviceBindingSpec{ Device: api.DeviceSubject{ Name: "example-device2", }, Applications: []api.ApplicationSubject{ { ID: "123e4567-e89b-12d3-a456-426614174000", Name: "example-application", Kind: "ExternalApplication", }, }, }, Status: api.DeviceBindingStatus{ ActivationCode: "F7RAXZXBFZQTEST", Timestamp: metav1.Time{ Time: time.Now(), }, }, } deviceSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "example-device2-example-application-secret", Namespace: "default", }, Data: map[string][]byte{ "client_id": []byte("example-client-id"), "client_secret": []byte("example-client-secret"), }, } // Add the new object to the mock client err := mockClient.Create(context.TODO(), &deviceBinding2) assert.NoError(t, err) err = mockClient.Create(context.TODO(), deviceSecret) assert.NoError(t, err) c.Params = append(c.Params, gin.Param{Key: "activationCode", Value: "F7RAXZXBFZQRPAU"}) binding, err := retrieveDeviceRegistration(context.TODO(), mockClient, c) assert.NoError(t, err) assert.Equal(t, "example-device-binding", binding.GetName()) // reset c.Param for the next activation code c.Params = []gin.Param{} c.Params = append(c.Params, gin.Param{Key: "activationCode", Value: "F7RAXZXBFZQTEST"}) binding2, err := retrieveDeviceRegistration(context.TODO(), mockClient, c) assert.NoError(t, err) assert.Equal(t, "device2", binding2.GetName()) }