...

Source file src/edge-infra.dev/pkg/edge/device-registrar/services/device_bootstrap_service_test.go

Documentation: edge-infra.dev/pkg/edge/device-registrar/services

     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  		// Inject the mock client into the context
   105  		func(c *gin.Context) {
   106  			c.Set("k8sClient", mockClient)
   107  			c.Next()
   108  
   109  			k8sClient, exists := c.Get("k8sClient")
   110  
   111  			// Assert that k8sClient is not nil and exists
   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  	// create gin context
   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  	// Check the status code
   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  	// Check the status code
   210  	assert.Equal(t, http.StatusUnauthorized, w.Code)
   211  
   212  	// Check the response body
   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  	// Check the status code
   230  	assert.Equal(t, http.StatusInternalServerError, w.Code)
   231  
   232  	// Check the response body
   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  	// Check the status code
   249  	assert.Equal(t, http.StatusUnauthorized, w.Code)
   250  
   251  	// Check the response body
   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  	// create gin context
   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  	// Add the new object to the mock client
   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  	// reset c.Param for the next activation code
   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