From b8aa4aed4ecbe437a628da86dcc7b997d43c8dff Mon Sep 17 00:00:00 2001 From: Djebran Lezzoum Date: Wed, 15 Nov 2023 20:08:13 +0200 Subject: [PATCH] updates: implement inventoryGroupDevicesInfo In the context of inventory group details view we want to update devices of an inventory group. But before that we to need to retrieve validation data and the device uuids related to this group. FIXES: https://issues.redhat.com/browse/THEEDGE-3539 --- pkg/dependencies/main.go | 3 + pkg/models/updates.go | 10 ++ pkg/models/updates_api.go | 10 ++ pkg/routes/updates.go | 97 ++++++++++++++- pkg/routes/updates_test.go | 169 ++++++++++++++++++++++++++ pkg/services/mock_services/updates.go | 15 +++ pkg/services/updates.go | 44 +++++++ pkg/services/updates_test.go | 100 +++++++++++++++ 8 files changed, 447 insertions(+), 1 deletion(-) diff --git a/pkg/dependencies/main.go b/pkg/dependencies/main.go index 9ea9d60d3..a08adbb2a 100644 --- a/pkg/dependencies/main.go +++ b/pkg/dependencies/main.go @@ -8,6 +8,7 @@ import ( "net/http" "github.com/redhatinsights/edge-api/logger" + "github.com/redhatinsights/edge-api/pkg/clients/inventorygroups" "github.com/redhatinsights/edge-api/pkg/clients/rbac" "github.com/redhatinsights/edge-api/pkg/clients/repositories" kafkacommon "github.com/redhatinsights/edge-api/pkg/common/kafka" @@ -31,6 +32,7 @@ type EdgeAPIServices struct { FilesService services.FilesService ProducerService kafkacommon.ProducerServiceInterface ConsumerService kafkacommon.ConsumerServiceInterface + InventoryGroupsService inventorygroups.ClientInterface RepositoriesService repositories.ClientInterface RbacService rbac.ClientInterface Log *log.Entry @@ -59,6 +61,7 @@ func Init(ctx context.Context) *EdgeAPIServices { FilesService: services.NewFilesService(log), ProducerService: kafkacommon.NewProducerService(), ConsumerService: kafkacommon.NewConsumerService(ctx, log), + InventoryGroupsService: inventorygroups.InitClient(ctx, log), RepositoriesService: repositories.InitClient(ctx, log), RbacService: rbac.InitClient(ctx, log), Log: log, diff --git a/pkg/models/updates.go b/pkg/models/updates.go index 57abd8e86..96ea9ec86 100644 --- a/pkg/models/updates.go +++ b/pkg/models/updates.go @@ -111,3 +111,13 @@ func (ur *UpdateTransaction) BeforeCreate(tx *gorm.DB) error { return nil } + +// InventoryGroupDevicesUpdateInfo is the inventory group update info +type InventoryGroupDevicesUpdateInfo struct { + GroupUUID string `json:"group_uuid"` + UpdateValid bool `json:"update_valid"` + ImageSetID uint `json:"image_set_id"` + ImageSetsCount int `json:"image_sets_count"` + DevicesCount int `json:"devices_count"` + DevicesUUIDS []string `json:"update_devices_uuids"` +} diff --git a/pkg/models/updates_api.go b/pkg/models/updates_api.go index 6fdd79469..c46dd7020 100644 --- a/pkg/models/updates_api.go +++ b/pkg/models/updates_api.go @@ -85,3 +85,13 @@ type RecipientNotificationAPI struct { IgnoreUserPreferences bool `json:"ignore_user_preferences" example:"false"` // notification recipient to ignore user preferences Users []string `json:"users" example:"user-id"` // notification recipient users } // @name RecipientNotification + +// InventoryGroupDevicesUpdateInfoResponseAPI is the inventory group update info +type InventoryGroupDevicesUpdateInfoResponseAPI struct { + GroupUUID string `json:"group_uuid" example:"b579a578-1a6f-48d5-8a45-21f2a656a5d4"` // the inventory group id + UpdateValid bool `json:"update_valid" example:"true"` // whether the inventory group devices update is valid + ImageSetID uint `json:"image_set_id" example:"1024" ` // the image set id common to all inventory group devices + ImageSetsCount int `json:"image_sets_count" example:"1"` // how much image set ids the inventory group devices belongs to + DevicesCount int `json:"devices_count" example:"25"` // the overall count of all devices that belongs to inventory group + DevicesUUID []string `json:"update_devices_uuids" example:"b579a578-1a6f-48d5-8a45-21f2a656a5d4,1abb288d-6d88-4e2d-bdeb-fcc536be58ec"` // the list of devices uuids that belongs to inventory group that are available to update +} diff --git a/pkg/routes/updates.go b/pkg/routes/updates.go index d72161026..5348bae42 100644 --- a/pkg/routes/updates.go +++ b/pkg/routes/updates.go @@ -11,13 +11,15 @@ import ( "time" "github.com/go-chi/chi" + "github.com/redhatinsights/edge-api/pkg/clients/inventorygroups" "github.com/redhatinsights/edge-api/pkg/db" "github.com/redhatinsights/edge-api/pkg/dependencies" "github.com/redhatinsights/edge-api/pkg/errors" "github.com/redhatinsights/edge-api/pkg/models" "github.com/redhatinsights/edge-api/pkg/routes/common" "github.com/redhatinsights/edge-api/pkg/services" - + "github.com/redhatinsights/edge-api/pkg/services/utility" + feature "github.com/redhatinsights/edge-api/unleash/features" log "github.com/sirupsen/logrus" ) @@ -32,6 +34,10 @@ func MakeUpdatesRouter(sub chi.Router) { r.Get("/update-playbook.yml", GetUpdatePlaybook) r.Get("/notify", SendNotificationForDevice) // TMP ROUTE TO SEND THE NOTIFICATION }) + sub.Route("/inventory-groups/{GroupUUID}", func(r chi.Router) { + r.Use(InventoryGroupsCtx) + r.Get("/update-info", GetInventoryGroupDevicesUpdateInfo) + }) // TODO: This is for backwards compatibility with the previous route // Once the frontend starts querying the device sub.Route("/device/", MakeDevicesRouter) @@ -79,6 +85,52 @@ func UpdateCtx(next http.Handler) http.Handler { }) } +type inventoryGroupContextKeyType string + +const inventoryGroupContextKey = inventoryGroupContextKeyType("inventory_group_key") + +// InventoryGroupsCtx a handler for updates inventory groups requests +func InventoryGroupsCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + contextServices := dependencies.ServicesFromContext(r.Context()) + orgID := readOrgID(w, r, contextServices.Log) + if orgID == "" { + return + } + + groupUUID := chi.URLParam(r, "GroupUUID") + if groupUUID == "" { + respondWithAPIError(w, contextServices.Log, errors.NewBadRequest("missing inventory group uuid")) + return + } + inventoryGroup, err := contextServices.InventoryGroupsService.GetGroupByUUID(groupUUID) + if err != nil { + var apiError errors.APIError + switch err { + case inventorygroups.ErrGroupNotFound: + apiError = errors.NewNotFound("inventory group not found") + default: + apiError = errors.NewInternalServerError() + } + respondWithAPIError(w, contextServices.Log, apiError) + return + } + ctx := context.WithValue(r.Context(), inventoryGroupContextKey, inventoryGroup) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func getInventoryGroup(w http.ResponseWriter, r *http.Request) *inventorygroups.Group { + ctx := r.Context() + ctxServices := dependencies.ServicesFromContext(ctx) + inventoryGroup, ok := ctx.Value(inventoryGroupContextKey).(*inventorygroups.Group) + if !ok { + respondWithAPIError(w, ctxServices.Log, errors.NewNotFound("inventory group not found in context")) + return nil + } + return inventoryGroup +} + // GetUpdatePlaybook returns the playbook for an update transaction // @Summary returns the playbook yaml file for a system update // @ID GetUpdatePlaybook @@ -486,3 +538,46 @@ func ValidateGetUpdatesFilterParams(next http.Handler) http.Handler { respondWithJSONBody(w, ctxServices.Log, &errs) }) } + +// GetInventoryGroupDevicesUpdateInfo returns inventory group update info +// @Summary Gets the inventory group update info +// @ID GetInventoryGroupDevicesUpdateInfo +// @Description Gets the inventory group update info +// @Tags Updates (Systems) +// @Accept json +// @Produce json +// @Param GroupUUID path string true "a unique uuid to identify the inventory group" +// @Success 200 {object} models.InventoryGroupDevicesUpdateInfoResponseAPI "The requested inventory group update info" +// @Failure 400 {object} errors.BadRequest "The request sent couldn't be processed" +// @Failure 404 {object} errors.NotFound "The requested inventory group was not found" +// @Failure 500 {object} errors.InternalServerError "There was an internal server error" +// @Router /inventory-groups/{GroupUUID}/update-info [get] +func GetInventoryGroupDevicesUpdateInfo(w http.ResponseWriter, r *http.Request) { + ctxServices := dependencies.ServicesFromContext(r.Context()) + orgID := readOrgID(w, r, ctxServices.Log) + if orgID == "" { + return + } + inventoryGroup := getInventoryGroup(w, r) + if inventoryGroup == nil { + return + } + + enforceEdgeGroups := utility.EnforceEdgeGroups(orgID) + if !feature.EdgeParityInventoryGroupsEnabled.IsEnabled() || + (feature.EdgeParityInventoryGroupsEnabled.IsEnabled() && enforceEdgeGroups) { + // return feature not available when inventory groups feature is not enabled + // or when the inventory groups feature is enabled but the org is enforced to use edge groups + respondWithAPIError(w, ctxServices.Log, errors.NewFeatureNotAvailable("inventory groups feature is not available")) + return + } + + inventoryGroupUpdateDevicesInfo, err := ctxServices.UpdateService.InventoryGroupDevicesUpdateInfo(orgID, inventoryGroup.ID) + if err != nil { + ctxServices.Log.WithFields(log.Fields{"error": err.Error(), "group-uuid": inventoryGroup.ID}).Error("error occurred while getting inventory group update validation") + respondWithAPIError(w, ctxServices.Log, errors.NewInternalServerError()) + } + + w.WriteHeader(http.StatusOK) + respondWithJSONBody(w, ctxServices.Log, inventoryGroupUpdateDevicesInfo) +} diff --git a/pkg/routes/updates_test.go b/pkg/routes/updates_test.go index 3d4b9afde..26adfc2d2 100644 --- a/pkg/routes/updates_test.go +++ b/pkg/routes/updates_test.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "os" "strings" "testing" "time" @@ -19,9 +20,12 @@ import ( apiError "github.com/redhatinsights/edge-api/pkg/errors" "github.com/bxcodec/faker/v3" + "github.com/redhatinsights/edge-api/pkg/clients/inventorygroups" + "github.com/redhatinsights/edge-api/pkg/clients/inventorygroups/mock_inventorygroups" "github.com/redhatinsights/edge-api/pkg/db" "github.com/redhatinsights/edge-api/pkg/routes/common" "github.com/redhatinsights/edge-api/pkg/services" + feature "github.com/redhatinsights/edge-api/unleash/features" "github.com/redhatinsights/platform-go-middlewares/identity" "github.com/redhatinsights/edge-api/config" @@ -1155,3 +1159,168 @@ func TestValidateGetAllUpdatesQueryParameters(t *testing.T) { } } } + +func TestInventoryGroupDevicesUpdateInfo(t *testing.T) { + + defer func() { + config.Get().Auth = false + }() + + // enable auth + config.Get().Auth = true + + orgID := faker.UUIDHyphenated() + groupUUID := faker.UUIDHyphenated() + inventoryGroup := inventorygroups.Group{Name: faker.Name(), ID: groupUUID, OrgID: orgID} + expectedError := errors.New("some expected error") + testCases := []struct { + Name string + EnforceEdgeGroups bool + EdgeParityInventoryGroupsEnabled bool + GroupUUID string + ReturnInventoryGroup *inventorygroups.Group + ReturnInventoryGroupError error + ReturnServiceError error + ReturnServiceData *models.InventoryGroupDevicesUpdateInfo + ExpectedHTTPStatus int + ExpectedHTTPErrorMessage string + }{ + { + Name: "should return InventoryGroupDevicesUpdateInfo successfully", + EdgeParityInventoryGroupsEnabled: true, + GroupUUID: groupUUID, + ReturnInventoryGroup: &inventoryGroup, + ReturnServiceData: &models.InventoryGroupDevicesUpdateInfo{UpdateValid: true, DevicesUUIDS: []string{faker.UUIDHyphenated()}}, + ExpectedHTTPStatus: http.StatusOK, + }, + { + Name: "should return bad request error when inventory group not supplied", + GroupUUID: "", + ExpectedHTTPStatus: http.StatusBadRequest, + ExpectedHTTPErrorMessage: "missing inventory group uuid", + }, + { + Name: "should return not found error when inventory group not found", + GroupUUID: groupUUID, + ReturnInventoryGroup: nil, + ReturnInventoryGroupError: inventorygroups.ErrGroupNotFound, + ExpectedHTTPStatus: http.StatusNotFound, + ExpectedHTTPErrorMessage: "inventory group not found", + }, + { + Name: "should return internal server error when inventory group return unknown error", + GroupUUID: groupUUID, + ReturnInventoryGroup: nil, + ReturnInventoryGroupError: expectedError, + ExpectedHTTPStatus: http.StatusInternalServerError, + }, + { + Name: "should return error when inventory groups feature is not in use", + EdgeParityInventoryGroupsEnabled: false, + GroupUUID: groupUUID, + ReturnInventoryGroup: &inventoryGroup, + ReturnInventoryGroupError: nil, + ExpectedHTTPStatus: http.StatusNotImplemented, + ExpectedHTTPErrorMessage: "inventory groups feature is not available", + }, + { + Name: "should return error when EdgeGroups is enforced", + EdgeParityInventoryGroupsEnabled: true, + EnforceEdgeGroups: true, + GroupUUID: groupUUID, + ReturnInventoryGroup: &inventoryGroup, + ReturnInventoryGroupError: nil, + ExpectedHTTPStatus: http.StatusNotImplemented, + ExpectedHTTPErrorMessage: "inventory groups feature is not available", + }, + { + Name: "should return error when InventoryGroupDevicesUpdateInfo fails", + EdgeParityInventoryGroupsEnabled: true, + GroupUUID: groupUUID, + ReturnInventoryGroup: &inventoryGroup, + ReturnInventoryGroupError: nil, + ReturnServiceError: expectedError, + ExpectedHTTPStatus: http.StatusInternalServerError, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + RegisterTestingT(t) + + defer func() { + _ = os.Unsetenv(feature.EnforceEdgeGroups.EnvVar) + _ = os.Unsetenv(feature.EdgeParityInventoryGroupsEnabled.EnvVar) + }() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + if testCase.EnforceEdgeGroups { + err := os.Setenv(feature.EnforceEdgeGroups.EnvVar, "true") + Expect(err).ToNot(HaveOccurred()) + } + if testCase.EdgeParityInventoryGroupsEnabled { + err := os.Setenv(feature.EdgeParityInventoryGroupsEnabled.EnvVar, "true") + Expect(err).ToNot(HaveOccurred()) + } + + var router chi.Router + var edgeAPIServices *dependencies.EdgeAPIServices + + mockUpdateService := mock_services.NewMockUpdateServiceInterface(ctrl) + mockInventoryGroupsClient := mock_inventorygroups.NewMockClientInterface(ctrl) + + router = chi.NewRouter() + router.Use(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rLog := log.NewEntry(log.StandardLogger()) + ctx := r.Context() + ctx = context.WithValue(ctx, identity.Key, identity.XRHID{Identity: identity.Identity{OrgID: orgID}}) + edgeAPIServices = &dependencies.EdgeAPIServices{ + UpdateService: mockUpdateService, + InventoryGroupsService: mockInventoryGroupsClient, + Log: rLog, + } + ctx = dependencies.ContextWithServices(ctx, edgeAPIServices) + + next.ServeHTTP(w, r.WithContext(ctx)) + }) + }) + router.Route("/updates", MakeUpdatesRouter) + + req, err := http.NewRequest( + http.MethodGet, fmt.Sprintf("/updates/inventory-groups/%s/update-info", testCase.GroupUUID), nil, + ) + Expect(err).ToNot(HaveOccurred()) + + if testCase.ReturnInventoryGroup != nil || testCase.ReturnInventoryGroupError != nil { + mockInventoryGroupsClient.EXPECT().GetGroupByUUID(testCase.GroupUUID).Return( + testCase.ReturnInventoryGroup, testCase.ReturnInventoryGroupError, + ) + } + + if testCase.ReturnServiceData != nil || testCase.ReturnServiceError != nil { + mockUpdateService.EXPECT().InventoryGroupDevicesUpdateInfo(orgID, testCase.GroupUUID).Return( + testCase.ReturnServiceData, testCase.ReturnServiceError, + ) + } + responseRecorder := httptest.NewRecorder() + router.ServeHTTP(responseRecorder, req) + respBody, err := io.ReadAll(responseRecorder.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(string(respBody)).ToNot(BeEmpty()) + + Expect(responseRecorder.Code).To(Equal(testCase.ExpectedHTTPStatus)) + if testCase.ExpectedHTTPStatus == http.StatusOK && testCase.ReturnServiceData != nil { + var responseUpdateInfo models.InventoryGroupDevicesUpdateInfo + err = json.Unmarshal(respBody, &responseUpdateInfo) + Expect(err).ToNot(HaveOccurred()) + Expect(responseUpdateInfo.UpdateValid).To(Equal(testCase.ReturnServiceData.UpdateValid)) + Expect(responseUpdateInfo.DevicesUUIDS).To(Equal(testCase.ReturnServiceData.DevicesUUIDS)) + } else if testCase.ExpectedHTTPErrorMessage != "" { + Expect(string(respBody)).To(ContainSubstring(testCase.ExpectedHTTPErrorMessage)) + } + }) + } +} diff --git a/pkg/services/mock_services/updates.go b/pkg/services/mock_services/updates.go index 6ece3a0b0..3068b8a32 100644 --- a/pkg/services/mock_services/updates.go +++ b/pkg/services/mock_services/updates.go @@ -123,6 +123,21 @@ func (mr *MockUpdateServiceInterfaceMockRecorder) GetUpdateTransactionsForDevice return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUpdateTransactionsForDevice", reflect.TypeOf((*MockUpdateServiceInterface)(nil).GetUpdateTransactionsForDevice), device) } +// InventoryGroupDevicesUpdateInfo mocks base method. +func (m *MockUpdateServiceInterface) InventoryGroupDevicesUpdateInfo(orgID, inventoryGroupUUID string) (*models.InventoryGroupDevicesUpdateInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InventoryGroupDevicesUpdateInfo", orgID, inventoryGroupUUID) + ret0, _ := ret[0].(*models.InventoryGroupDevicesUpdateInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InventoryGroupDevicesUpdateInfo indicates an expected call of InventoryGroupDevicesUpdateInfo. +func (mr *MockUpdateServiceInterfaceMockRecorder) InventoryGroupDevicesUpdateInfo(orgID, inventoryGroupUUID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InventoryGroupDevicesUpdateInfo", reflect.TypeOf((*MockUpdateServiceInterface)(nil).InventoryGroupDevicesUpdateInfo), orgID, inventoryGroupUUID) +} + // ProcessPlaybookDispatcherRunEvent mocks base method. func (m *MockUpdateServiceInterface) ProcessPlaybookDispatcherRunEvent(message []byte) error { m.ctrl.T.Helper() diff --git a/pkg/services/updates.go b/pkg/services/updates.go index ee26c3891..d7ea8b43e 100644 --- a/pkg/services/updates.go +++ b/pkg/services/updates.go @@ -51,6 +51,7 @@ type UpdateServiceInterface interface { UpdateDevicesFromUpdateTransaction(update models.UpdateTransaction) error ValidateUpdateSelection(orgID string, imageIds []uint) (bool, error) ValidateUpdateDeviceGroup(orgID string, deviceGroupID uint) (bool, error) + InventoryGroupDevicesUpdateInfo(orgID string, inventoryGroupUUID string) (*models.InventoryGroupDevicesUpdateInfo, error) } // NewUpdateService gives an instance of the main implementation of a UpdateServiceInterface @@ -1011,6 +1012,49 @@ func (s *UpdateService) ValidateUpdateDeviceGroup(orgID string, deviceGroupID ui return count == 1, nil } +// InventoryGroupDevicesUpdateInfo return the inventory group update info +func (s *UpdateService) InventoryGroupDevicesUpdateInfo(orgID string, inventoryGroupUUID string) (*models.InventoryGroupDevicesUpdateInfo, error) { + + type DeviceData struct { + DeviceUUID string `json:"device_uuid"` + UpdateAvailable bool `json:"update_available"` + ImageSetID uint `json:"image_set_id"` + } + + var inventoryGroupDevicesData []DeviceData + if err := db.Org(orgID, "devices").Model(&models.Device{}). + Select("devices.uuid as device_uuid, devices.update_available as update_available, images.image_set_id as image_set_id"). + Joins(`JOIN images ON images.id = devices.image_id`). + Where(`devices.group_uuid = ?`, inventoryGroupUUID). + Where("devices.image_id > 0"). + Where("images.deleted_at IS NULL"). + Order("devices.id ASC"). + Scan(&inventoryGroupDevicesData).Error; err != nil { + return nil, err + } + + var inventoryGroupDevicesInfo models.InventoryGroupDevicesUpdateInfo + var imageSetIDSMap = make(map[uint]bool) + for _, deviceData := range inventoryGroupDevicesData { + imageSetIDSMap[deviceData.ImageSetID] = true + inventoryGroupDevicesInfo.ImageSetID = deviceData.ImageSetID + if deviceData.UpdateAvailable && deviceData.DeviceUUID != "" { + inventoryGroupDevicesInfo.DevicesUUIDS = append(inventoryGroupDevicesInfo.DevicesUUIDS, deviceData.DeviceUUID) + } + } + inventoryGroupDevicesInfo.DevicesCount = len(inventoryGroupDevicesData) + inventoryGroupDevicesInfo.ImageSetsCount = len(imageSetIDSMap) + inventoryGroupDevicesInfo.GroupUUID = inventoryGroupUUID + if inventoryGroupDevicesInfo.ImageSetsCount == 1 && len(inventoryGroupDevicesInfo.DevicesUUIDS) > 0 { + inventoryGroupDevicesInfo.UpdateValid = true + } else { + inventoryGroupDevicesInfo.ImageSetID = 0 + inventoryGroupDevicesInfo.UpdateValid = false + } + + return &inventoryGroupDevicesInfo, nil +} + // BuildUpdateTransactions creates the update transaction to be sent to Playbook Dispatcher func (s *UpdateService) BuildUpdateTransactions(devicesUpdate *models.DevicesUpdate, orgID string, commit *models.Commit) (*[]models.UpdateTransaction, error) { diff --git a/pkg/services/updates_test.go b/pkg/services/updates_test.go index 37044e6ed..b29eb5c00 100644 --- a/pkg/services/updates_test.go +++ b/pkg/services/updates_test.go @@ -1685,4 +1685,104 @@ var _ = Describe("UpdateService Basic functions", func() { }) }) + + Describe("inventory group", func() { + + Context("InventoryGroupDevicesUpdateInfo", func() { + var orgID string + var imageName string + var groupUUID string + var groupUUID2 string + var groupUUID3 string + var imageSet models.ImageSet + var innerImageSet models.ImageSet + var innerImages []models.Image + var images []models.Image + var devices []models.Device + + var updateService services.UpdateServiceInterface + + BeforeEach(func() { + var err error + if orgID == "" { + // setup only once + updateService = services.NewUpdateService(context.Background(), log.WithField("service", "update")) + + orgID = faker.UUIDHyphenated() + imageName = faker.Name() + groupUUID = faker.UUIDHyphenated() + groupUUID2 = faker.UUIDHyphenated() + groupUUID3 = faker.UUIDHyphenated() + + imageSet = models.ImageSet{Name: imageName, OrgID: orgID} + err = db.DB.Create(&imageSet).Error + Expect(err).ToNot(HaveOccurred()) + images = []models.Image{ + {Name: imageName, OrgID: orgID, ImageSetID: &imageSet.ID, Version: 1}, + {Name: imageName, OrgID: orgID, ImageSetID: &imageSet.ID, Version: 2}, + {Name: imageName, OrgID: orgID, ImageSetID: &imageSet.ID, Version: 3}, + {Name: imageName, OrgID: orgID, ImageSetID: &imageSet.ID, Version: 4}, + } + err = db.DB.Create(&images).Error + Expect(err).ToNot(HaveOccurred()) + + innerImageSet = models.ImageSet{Name: faker.Name(), OrgID: orgID} + err = db.DB.Create(&innerImageSet).Error + Expect(err).ToNot(HaveOccurred()) + innerImages = []models.Image{ + {Name: innerImageSet.Name, OrgID: orgID, ImageSetID: &innerImageSet.ID, Version: 1}, + {Name: innerImageSet.Name, OrgID: orgID, ImageSetID: &innerImageSet.ID, Version: 2}, + } + err = db.DB.Create(&innerImages).Error + Expect(err).ToNot(HaveOccurred()) + + devices = []models.Device{ + {OrgID: orgID, Name: faker.Name(), UUID: faker.UUIDHyphenated(), GroupUUID: groupUUID, ImageID: images[0].ID, UpdateAvailable: true}, + {OrgID: orgID, Name: faker.Name(), UUID: faker.UUIDHyphenated(), GroupUUID: groupUUID, ImageID: images[1].ID, UpdateAvailable: true}, + {OrgID: orgID, Name: faker.Name(), UUID: faker.UUIDHyphenated(), GroupUUID: groupUUID, ImageID: images[2].ID, UpdateAvailable: true}, + {OrgID: orgID, Name: faker.Name(), UUID: faker.UUIDHyphenated(), GroupUUID: groupUUID, ImageID: images[3].ID, UpdateAvailable: false}, + {OrgID: orgID, Name: faker.Name(), UUID: faker.UUIDHyphenated(), GroupUUID: groupUUID2, ImageID: images[3].ID, UpdateAvailable: false}, + {OrgID: orgID, Name: faker.Name(), UUID: faker.UUIDHyphenated(), GroupUUID: groupUUID3, ImageID: images[0].ID, UpdateAvailable: true}, + {OrgID: orgID, Name: faker.Name(), UUID: faker.UUIDHyphenated(), GroupUUID: groupUUID3, ImageID: innerImages[0].ID, UpdateAvailable: true}, + } + err = db.DB.Create(&devices).Error + Expect(err).ToNot(HaveOccurred()) + } + }) + + It("inventory group devices update should be valid", func() { + expectedDevicesUUID := []string{devices[0].UUID, devices[1].UUID, devices[2].UUID} + inventoryGroupDevicesUpdateInfo, err := updateService.InventoryGroupDevicesUpdateInfo(orgID, groupUUID) + Expect(err).ToNot(HaveOccurred()) + Expect(inventoryGroupDevicesUpdateInfo.UpdateValid).To(BeTrue()) + Expect(inventoryGroupDevicesUpdateInfo.GroupUUID).To(Equal(groupUUID)) + Expect(inventoryGroupDevicesUpdateInfo.ImageSetID).To(Equal(imageSet.ID)) + Expect(inventoryGroupDevicesUpdateInfo.ImageSetsCount).To(Equal(1)) + Expect(inventoryGroupDevicesUpdateInfo.DevicesCount).To(Equal(4)) + Expect(len(inventoryGroupDevicesUpdateInfo.DevicesUUIDS)).To(Equal(3)) + Expect(inventoryGroupDevicesUpdateInfo.DevicesUUIDS).To(Equal(expectedDevicesUUID)) + }) + + It("inventory group devices update should be invalid when no device update available", func() { + inventoryGroupDevicesUpdateInfo, err := updateService.InventoryGroupDevicesUpdateInfo(orgID, groupUUID2) + Expect(err).ToNot(HaveOccurred()) + Expect(inventoryGroupDevicesUpdateInfo.UpdateValid).To(BeFalse()) + Expect(inventoryGroupDevicesUpdateInfo.GroupUUID).To(Equal(groupUUID2)) + Expect(inventoryGroupDevicesUpdateInfo.ImageSetID).To(Equal(uint(0))) + Expect(inventoryGroupDevicesUpdateInfo.ImageSetsCount).To(Equal(1)) + Expect(inventoryGroupDevicesUpdateInfo.DevicesCount).To(Equal(1)) + Expect(len(inventoryGroupDevicesUpdateInfo.DevicesUUIDS)).To(Equal(0)) + }) + It("inventory group devices update should be invalid when devices are from different images sets", func() { + inventoryGroupDevicesUpdateInfo, err := updateService.InventoryGroupDevicesUpdateInfo(orgID, groupUUID3) + Expect(err).ToNot(HaveOccurred()) + Expect(inventoryGroupDevicesUpdateInfo.UpdateValid).To(BeFalse()) + Expect(inventoryGroupDevicesUpdateInfo.GroupUUID).To(Equal(groupUUID3)) + Expect(inventoryGroupDevicesUpdateInfo.ImageSetID).To(Equal(uint(0))) + Expect(inventoryGroupDevicesUpdateInfo.ImageSetsCount).To(Equal(2)) + Expect(inventoryGroupDevicesUpdateInfo.DevicesCount).To(Equal(2)) + Expect(len(inventoryGroupDevicesUpdateInfo.DevicesUUIDS)).To(Equal(2)) + }) + }) + }) })