...

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

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

     1  package services
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/gin-gonic/gin"
    12  	"github.com/stretchr/testify/assert"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/runtime"
    15  	"sigs.k8s.io/controller-runtime/pkg/client"
    16  
    17  	"edge-infra.dev/pkg/edge/device-registrar/config"
    18  
    19  	api "edge-infra.dev/pkg/edge/device-registrar/api/v1alpha1"
    20  	test "edge-infra.dev/pkg/edge/device-registrar/utils/test"
    21  	iamApi "edge-infra.dev/pkg/edge/iam/api/v1alpha1"
    22  	ienApi "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
    23  )
    24  
    25  // to test: go test edge-infra.dev/pkg/edge/device-registrar/services
    26  
    27  func TestListApplications(t *testing.T) {
    28  	router, scheme := setupTest()
    29  	app1, app2 := getApps()
    30  
    31  	mockClient := test.NewErrorInjectingFakeClient(scheme, false, &app1, &app2)
    32  	router.Use(injectMockClientMiddleware(t, mockClient))
    33  
    34  	w := serveApplicationRequest(t, router)
    35  
    36  	// Confirm 200 status code
    37  	assert.Equal(t, http.StatusOK, w.Code)
    38  
    39  	var applications []map[string]interface{}
    40  	err := json.Unmarshal(w.Body.Bytes(), &applications)
    41  	assert.NoError(t, err)
    42  
    43  	// Check that the app1 and app2 names are returned
    44  	appNames := make(map[string]bool)
    45  	for _, app := range applications {
    46  		appNames[app["name"].(string)] = true
    47  	}
    48  
    49  	assert.True(t, appNames["app1"], "app1 should be in the response")
    50  	assert.True(t, appNames["app2"], "app2 should be in the response")
    51  
    52  	// assert that the application ID is returned
    53  	assert.Contains(t, w.Body.String(), "123e4567-e89b-12d3-a456-426614174000")
    54  	assert.Contains(t, w.Body.String(), "123e4567-e89b-12d3-a456-426614174001")
    55  
    56  	// assert that the application name is returned
    57  	assert.Contains(t, w.Body.String(), "app1")
    58  	assert.Contains(t, w.Body.String(), "app2")
    59  
    60  	// assert that the application description is returned
    61  	assert.Contains(t, w.Body.String(), "Connected associates mobile application definition")
    62  	assert.Contains(t, w.Body.String(), "Connected associates mobile application 2 definition")
    63  }
    64  
    65  func TestListApplicationsNoApps(t *testing.T) {
    66  	router, scheme := setupTest()
    67  
    68  	mockClient := test.NewErrorInjectingFakeClient(scheme, false)
    69  	router.Use(injectMockClientMiddleware(t, mockClient))
    70  
    71  	w := serveApplicationRequest(t, router)
    72  
    73  	// Confirm 200 status code
    74  	assert.Equal(t, http.StatusOK, w.Code)
    75  
    76  	// Check that the response is empty
    77  	assert.Equal(t, "[]", w.Body.String())
    78  }
    79  
    80  func TestListApplicationsWithErrors(t *testing.T) {
    81  	router, scheme := setupTest()
    82  
    83  	mockClient := test.NewErrorInjectingFakeClient(scheme, false)
    84  
    85  	// Create an error to inject into the client
    86  	mockClient.SetFailOnList()
    87  	router.Use(injectMockClientMiddleware(t, mockClient))
    88  
    89  	// register endpoint
    90  	w := serveApplicationRequest(t, router)
    91  
    92  	// Confirm 500 status code
    93  	assert.Equal(t, http.StatusInternalServerError, w.Code)
    94  
    95  	// Check that the response has the correct error message
    96  	assert.Contains(t, w.Body.String(), "failed to retrieve applications: unsupported list type")
    97  }
    98  
    99  func TestRegisterDeviceNoVIP(t *testing.T) {
   100  	router, scheme := setupTest()
   101  	config.VIP = "" // Reset VIP
   102  
   103  	app1, _ := getApps()
   104  	mockClient := test.NewErrorInjectingFakeClient(scheme, true, &app1)
   105  
   106  	router.Use(injectMockClientMiddleware(t, mockClient))
   107  	w := serveRegistrationRequest(t, router, "")
   108  
   109  	// status code 500
   110  	assert.Equal(t, http.StatusInternalServerError, w.Code, "Expected status code 500")
   111  
   112  	// assert that the response contains an error message
   113  	assert.Contains(t, w.Body.String(), "no IENodes found")
   114  }
   115  
   116  func TestRegisterDevice(t *testing.T) {
   117  	router, scheme := setupTest()
   118  
   119  	app1, _ := getApps()
   120  
   121  	node := ienApi.IENode{
   122  		ObjectMeta: metav1.ObjectMeta{
   123  			Name:      "test-node",
   124  			Namespace: "device-registrar-clients",
   125  		},
   126  		Spec: ienApi.IENodeSpec{
   127  			NetworkServices: ienApi.NetworkServices{
   128  				KubeVip: "172.0.0.1",
   129  			},
   130  		},
   131  	}
   132  
   133  	mockClient := test.NewErrorInjectingFakeClient(scheme, true, &app1, &node)
   134  
   135  	router.Use(injectMockClientMiddleware(t, mockClient))
   136  	w := serveRegistrationRequest(t, router, "")
   137  
   138  	// status code 200
   139  	assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
   140  
   141  	// external device is created
   142  	extDev := &api.ExternalDevice{}
   143  	err := mockClient.Get(context.TODO(), client.ObjectKey{Name: "test-device-name", Namespace: "device-registrar-clients"}, extDev)
   144  	assert.NoError(t, err)
   145  
   146  	// assert extDev has a deviceID
   147  	assert.NotEmpty(t, extDev.Spec.Name)
   148  
   149  	// device binding is created
   150  	devBinding := &api.DeviceBinding{}
   151  	err = mockClient.Get(context.TODO(), client.ObjectKey{Name: "test-device-name-device-binding", Namespace: "device-registrar-clients"}, devBinding)
   152  	assert.NoError(t, err)
   153  
   154  	// assert devBinding has correct fields
   155  	assert.Equal(t, "test-device-name", devBinding.Spec.Device.Name)
   156  	assert.Equal(t, "123e4567-e89b-12d3-a456-426614174000", devBinding.Spec.Applications[0].ID)
   157  	assert.Equal(t, "ExternalApplication", devBinding.Spec.Applications[0].Kind)
   158  	assert.Equal(t, "app1", devBinding.Spec.Applications[0].Name)
   159  	assert.NotEmpty(t, devBinding.Status.ActivationCode)
   160  
   161  	// List IAM clients to find the one with prefix test-device-name-app1-client-[RANDOM STRING]
   162  	var iamClients iamApi.ClientList
   163  	err = mockClient.List(context.TODO(), &iamClients, client.InNamespace("device-registrar-clients"))
   164  	assert.NoError(t, err)
   165  
   166  	var foundIAMClient *iamApi.Client
   167  	for _, iamClient := range iamClients.Items {
   168  		if strings.HasPrefix(iamClient.Name, "test-device-name-app1-client") {
   169  			foundIAMClient = &iamClient
   170  			break
   171  		}
   172  	}
   173  
   174  	assert.NotNil(t, foundIAMClient, "IAMClient with the specified prefix not found")
   175  
   176  	// assert edge-id client secret name
   177  	assert.Equal(t, "test-device-name-app1-secret", foundIAMClient.Spec.SecretName)
   178  
   179  	// return body contains a bootstrapURL and hostmapping
   180  	assert.Contains(t, w.Body.String(), "bootstrapURL")
   181  	assert.Contains(t, w.Body.String(), "hostMapping")
   182  
   183  	// assert contains BootstrapHost
   184  	assert.Contains(t, w.Body.String(), "edge-bootstrap.store.ncr.corp")
   185  
   186  	// assert that bootsrapURL is correct- contains same activation code as deviceBinding
   187  	assert.Contains(t, w.Body.String(), devBinding.Status.ActivationCode)
   188  
   189  	// assert that vip address is correct- contains same as node kubeVIP address
   190  	assert.Contains(t, w.Body.String(), node.Spec.NetworkServices.KubeVip)
   191  }
   192  
   193  func TestRegisterDeviceWithCreateError(t *testing.T) {
   194  	router, scheme := setupTest()
   195  
   196  	app1, _ := getApps()
   197  
   198  	mockClient := test.NewErrorInjectingFakeClient(scheme, true, &app1)
   199  	mockClient.SetFailOnCreate()
   200  
   201  	router.Use(injectMockClientMiddleware(t, mockClient))
   202  	w := serveRegistrationRequest(t, router, "")
   203  
   204  	// status code 500
   205  	assert.Equal(t, http.StatusInternalServerError, w.Code)
   206  
   207  	// confirm response contains the error message
   208  	assert.Contains(t, w.Body.String(), "failed to create external device")
   209  }
   210  
   211  func TestRegisterDeviceWithGetError(t *testing.T) {
   212  	router, scheme := setupTest()
   213  	app1, _ := getApps()
   214  
   215  	mockClient := test.NewErrorInjectingFakeClient(scheme, true, &app1)
   216  	mockClient.SetFailOnGet()
   217  	router.Use(injectMockClientMiddleware(t, mockClient))
   218  
   219  	w := serveRegistrationRequest(t, router, "")
   220  
   221  	// status code 500
   222  	assert.Equal(t, http.StatusInternalServerError, w.Code)
   223  
   224  	// confirm response contains the error message
   225  	assert.Contains(t, w.Body.String(), "failed to create external device: unsupported get type")
   226  }
   227  
   228  func TestGetExternalApplicationByID(t *testing.T) {
   229  	app1, _ := getApps()
   230  
   231  	scheme := runtime.NewScheme()
   232  	config.AddToScheme(scheme)
   233  
   234  	mockClient := test.NewErrorInjectingFakeClient(scheme, true, &app1)
   235  
   236  	// create gin context
   237  	c, _ := gin.CreateTestContext(httptest.NewRecorder())
   238  
   239  	app, err := getExternalApplicationByID(context.TODO(), c, mockClient, "123e4567-e89b-12d3-a456-426614174000")
   240  	assert.NoError(t, err)
   241  	assert.Equal(t, "app1", app.GetName())
   242  
   243  	// test for non-existent application
   244  	_, err = getExternalApplicationByID(context.TODO(), c, mockClient, "123")
   245  	assert.Error(t, err)
   246  }
   247  
   248  func TestRegisterDeviceWithStatusUpdateError(t *testing.T) {
   249  	router, scheme := setupTest()
   250  
   251  	app1, _ := getApps()
   252  
   253  	mockClient := test.NewErrorInjectingFakeClient(scheme, true, &app1)
   254  	mockClient.SetFailOnStatus()
   255  	mockClient.NewCustomSubResourceWriter().SetFailOnUpdate()
   256  
   257  	router.Use(injectMockClientMiddleware(t, mockClient))
   258  	w := serveRegistrationRequest(t, router, "")
   259  
   260  	// status code 500
   261  	assert.Equal(t, http.StatusInternalServerError, w.Code)
   262  
   263  	// confirm response contains the error message
   264  	assert.Contains(t, w.Body.String(), "failed to update deviceBinding status: update error")
   265  }
   266  
   267  func TestRegisterDevice_InvalidJSON(t *testing.T) {
   268  	router := gin.Default()
   269  
   270  	// Invalid JSON payload
   271  	payload := `{
   272          "device": "test-device-name",
   273          "applicationIDs": ["123e4567-e89b-12d3-a456-426614174000",`
   274  
   275  	w := serveRegistrationRequest(t, router, payload)
   276  
   277  	// status code 400
   278  	assert.Equal(t, http.StatusBadRequest, w.Code)
   279  	assert.Contains(t, w.Body.String(), "error")
   280  }
   281  
   282  func getApps() (api.ExternalApplication, api.ExternalApplication) {
   283  	app1 := api.ExternalApplication{
   284  		ObjectMeta: metav1.ObjectMeta{
   285  			Name:      "app1",
   286  			Namespace: "device-registrar-clients",
   287  		},
   288  		Spec: api.ExternalApplicationSpec{
   289  			Description: "Connected associates mobile application definition",
   290  			ID:          "123e4567-e89b-12d3-a456-426614174000",
   291  		},
   292  	}
   293  	app2 := api.ExternalApplication{
   294  		ObjectMeta: metav1.ObjectMeta{
   295  			Name:      "app2",
   296  			Namespace: "device-registrar-clients",
   297  		},
   298  		Spec: api.ExternalApplicationSpec{
   299  			Description: "Connected associates mobile application 2 definition",
   300  			ID:          "123e4567-e89b-12d3-a456-426614174001",
   301  		},
   302  	}
   303  	return app1, app2
   304  }
   305  
   306  // InjectMockClientMiddleware returns a Gin middleware function that injects the mock client into the context
   307  func injectMockClientMiddleware(t *testing.T, mockClient client.Client) gin.HandlerFunc {
   308  	return func(c *gin.Context) {
   309  		c.Set("k8sClient", mockClient)
   310  		c.Next()
   311  
   312  		k8sClient, exists := c.Get("k8sClient")
   313  
   314  		// Assert that k8sClient is not nil and exists
   315  		assert.True(t, exists, "k8sClient should exist in the context")
   316  		assert.NotNil(t, k8sClient, "k8sClient should not be nil")
   317  	}
   318  }
   319  
   320  func serveRegistrationRequest(t *testing.T, router *gin.Engine, payload string) *httptest.ResponseRecorder {
   321  	// register endpoint
   322  	router.POST("/register", RegisterDevice)
   323  
   324  	// if payload is empty, create a default payload
   325  	if payload == "" {
   326  		payload = `{
   327  			"device": {
   328  				"name": "test-device-name", 
   329  				"description": "my test device", 
   330  				"serialNumber": "DSFE7DSCXX90" 
   331  			},
   332  			"applicationIDs": ["123e4567-e89b-12d3-a456-426614174000"]
   333  		}`
   334  	}
   335  
   336  	// Create a new HTTP request with the payload
   337  	req, err := http.NewRequest(http.MethodPost, "/register", strings.NewReader(payload))
   338  	assert.NoError(t, err)
   339  	req.Header.Set("Content-Type", "application/json")
   340  
   341  	w := httptest.NewRecorder()
   342  	router.ServeHTTP(w, req)
   343  
   344  	return w
   345  }
   346  
   347  func serveApplicationRequest(t *testing.T, router *gin.Engine) *httptest.ResponseRecorder {
   348  	// register endpoint
   349  	router.GET("/applications", ListApplications)
   350  
   351  	// Create a new HTTP request
   352  	req, err := http.NewRequest(http.MethodGet, "/applications", nil)
   353  	assert.NoError(t, err)
   354  	w := httptest.NewRecorder()
   355  	router.ServeHTTP(w, req)
   356  
   357  	return w
   358  }
   359  

View as plain text