diff --git a/API/models/attributes.go b/API/models/attributes.go index fb333ceb..087d417d 100644 --- a/API/models/attributes.go +++ b/API/models/attributes.go @@ -10,6 +10,8 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) +const RACKUNIT = 0.04445 //meter + func validateAttributes(entity int, data, parent map[string]any) *u.Error { attributes := data["attributes"].(map[string]any) switch entity { @@ -21,8 +23,11 @@ func validateAttributes(entity int, data, parent map[string]any) *u.Error { return err } case u.DEVICE: - var deviceSlots []string var err *u.Error + if err = checkSizeUAndHeight(attributes); err != nil { + return err + } + var deviceSlots []string if deviceSlots, err = slotToValidSlice(attributes); err != nil { return err } @@ -67,8 +72,6 @@ func validateDeviceSlots(deviceSlots []string, deviceName, deviceParentd string) Message: "Invalid slot: one or more requested slots are already in use"} } } - } else { - // fmt.Println(err) } } return nil @@ -152,3 +155,34 @@ func setCorridorColor(attributes map[string]any) { attributes["color"] = "000099" } } + +// Check if sizeU and height are coherents +func checkSizeUAndHeight(attributes map[string]any) *u.Error { + if attributes["sizeU"] == nil || attributes["height"] == nil { + return nil + } + + sizeU, err := u.GetFloat(attributes["sizeU"]) + if err != nil { + return &u.Error{ + Type: u.ErrBadFormat, + Message: err.Error(), + } + } + height := attributes["height"] + h := sizeU * RACKUNIT + switch heightUnit := attributes["heightUnit"]; heightUnit { + case "cm": + h *= 100 + case "mm": + h *= 1000 + } + if height == h { + return nil + } else { + return &u.Error{ + Type: u.ErrBadFormat, + Message: "sizeU and height are not consistent", + } + } +} diff --git a/API/models/update_object_test.go b/API/models/update_object_test.go index 34633556..0b06e759 100644 --- a/API/models/update_object_test.go +++ b/API/models/update_object_test.go @@ -9,6 +9,23 @@ import ( "github.com/stretchr/testify/assert" ) +var device map[string]any + +func init() { + siName := "site" + bdName := siName + ".building" + roName := bdName + ".room" + rkName := roName + ".rack" + + integration.RequireCreateSite(siName) + integration.RequireCreateBuilding(siName, "building") + integration.RequireCreateRoom(bdName, "room") + integration.RequireCreateRack(roName, "rack") + device = integration.RequireCreateDevice(rkName, "device") +} + +// region generic + func TestUpdateGenericWorks(t *testing.T) { generic := integration.RequireCreateGeneric("", "update-object-1") @@ -26,3 +43,63 @@ func TestUpdateGenericWorks(t *testing.T) { ) assert.Nil(t, err) } + +// endregion generic + +// region device's sizeU & height + +func TestUpdateDeviceSizeUAndHeightmm(t *testing.T) { + _, err := models.UpdateObject( + u.EntityToString(u.DEVICE), + device["id"].(string), + map[string]any{ + "attributes": map[string]any{ + "sizeU": 1, + "height": 44.45, + "heightUnit": "mm", + }, + }, + true, + integration.ManagerUserRoles, + false, + ) + assert.Nil(t, err) +} +func TestUpdateDeviceSizeUAndHeightcm(t *testing.T) { + _, err := models.UpdateObject( + u.EntityToString(u.DEVICE), + device["id"].(string), + map[string]any{ + "attributes": map[string]any{ + "sizeU": 1, + "height": 4.445, + "heightUnit": "cm", + }, + }, + true, + integration.ManagerUserRoles, + false, + ) + assert.Nil(t, err) +} + +func TestUpdateDeviceSizeUAndHeightmmError(t *testing.T) { + _, err := models.UpdateObject( + u.EntityToString(u.DEVICE), + device["id"].(string), + map[string]any{ + "attributes": map[string]any{ + "sizeU": 12, + "height": 44.45, + "heightUnit": "mm", + }, + }, + true, + integration.ManagerUserRoles, + false, + ) + assert.NotNil(t, err) + assert.ErrorContains(t, err, "sizeU and height are not consistent") +} + +// endregion device's sizeU & height diff --git a/API/utils/util.go b/API/utils/util.go index b0e5b1da..11c93308 100644 --- a/API/utils/util.go +++ b/API/utils/util.go @@ -8,6 +8,7 @@ import ( "context" "encoding/json" "flag" + "fmt" "log" "net/http" "net/url" @@ -507,3 +508,15 @@ func StrSliceContains(slice []string, elem string) bool { } return false } + +var floatType = reflect.TypeOf(float64(0)) + +func GetFloat(unk interface{}) (float64, error) { + v := reflect.ValueOf(unk) + v = reflect.Indirect(v) + if !v.Type().ConvertibleTo(floatType) { + return 0, fmt.Errorf("cannot convert %v to float64", v.Type()) + } + fv := v.Convert(floatType) + return fv.Float(), nil +} diff --git a/CLI/controllers/create_test.go b/CLI/controllers/create_test.go index f8c12449..d8913f2d 100644 --- a/CLI/controllers/create_test.go +++ b/CLI/controllers/create_test.go @@ -461,7 +461,7 @@ func TestCreateDeviceWithSizeU(t *testing.T) { mockGetResponse := test_utils.GetEntity("rack", "A01", "BASIC.A.R1", "test-domain") sizeU := float64(2) - height := sizeU * 44.5 + height := sizeU * models.RACKUNIT * 1000 mockCreateResponse := map[string]any{ "category": "device", "id": "BASIC.A.R1.A01.D1", diff --git a/CLI/controllers/link_test.go b/CLI/controllers/link_test.go index 6a661f97..c9dcbbf2 100644 --- a/CLI/controllers/link_test.go +++ b/CLI/controllers/link_test.go @@ -55,7 +55,7 @@ func TestLinkObjectWithInvalidSlots(t *testing.T) { } err := controller.LinkObject(models.StrayPath+"chT", models.PhysicalPath+"BASIC/A/R1/A01", attributes, values, slots) assert.NotNil(t, err) - assert.Equal(t, "Invalid device syntax: .. can only be used in a single element vector", err.Error()) + assert.Equal(t, "invalid device syntax: .. can only be used in a single element vector", err.Error()) } func TestLinkObjectWithValidSlots(t *testing.T) { diff --git a/CLI/controllers/template.go b/CLI/controllers/template.go index a4a68ad4..b51ce51d 100644 --- a/CLI/controllers/template.go +++ b/CLI/controllers/template.go @@ -94,9 +94,9 @@ func (controller Controller) ApplyTemplate(attr, data map[string]interface{}, en if t == "chassis" || t == "server" { res := 0 if val, ok := sizeInf[2].(float64); ok { - res = int((val / 1000) / RACKUNIT) + res = int((val / 1000) / models.RACKUNIT) } else if val, ok := sizeInf[2].(int); ok { - res = int((float64(val) / 1000) / RACKUNIT) + res = int((float64(val) / 1000) / models.RACKUNIT) } else { return errors.New("invalid size vector on given template") } diff --git a/CLI/controllers/template_test.go b/CLI/controllers/template_test.go index 48accbe8..61b38c1c 100644 --- a/CLI/controllers/template_test.go +++ b/CLI/controllers/template_test.go @@ -1,7 +1,6 @@ package controllers_test import ( - "cli/controllers" "cli/models" test_utils "cli/test" "maps" @@ -44,7 +43,7 @@ func TestApplyTemplateOfTypeDeviceWorks(t *testing.T) { test_utils.MockGetObjTemplate(mockAPI, template) - sizeU := int((float64(template["sizeWDHmm"].([]any)[2].(int)) / 1000) / controllers.RACKUNIT) + sizeU := int((float64(template["sizeWDHmm"].([]any)[2].(int)) / 1000) / models.RACKUNIT) err := controller.ApplyTemplate(attributes, device, models.DEVICE) assert.Nil(t, err) diff --git a/CLI/controllers/update.go b/CLI/controllers/update.go index 3260632e..1ada948e 100644 --- a/CLI/controllers/update.go +++ b/CLI/controllers/update.go @@ -63,6 +63,10 @@ func (controller Controller) PatchObj(pathStr string, data map[string]any, withR category = obj["category"].(string) } + if category == models.EntityToString(models.DEVICE) { + models.ComputeSizeUAndHeight(obj, data) + } + url, err := controller.ObjectUrl(pathStr, 0) if err != nil { return nil, err @@ -76,72 +80,13 @@ func (controller Controller) PatchObj(pathStr string, data map[string]any, withR return nil, err } - //Determine if Unity requires the message as - //Interact or Modify - entityType := models.EntityStrToInt(category) - if models.IsTag(pathStr) { - entityType = models.TAG - } else if models.IsLayer(pathStr) { + if models.IsLayer(pathStr) { // For layers, update the object to the hierarchy in order to be cached data := resp.Body["data"].(map[string]any) _, err = State.Hierarchy.AddObjectInPath(data, pathStr) if err != nil { return nil, err } - entityType = models.LAYER - } - - message := map[string]any{} - var key string - - if entityType == models.ROOM && (data["tilesName"] != nil || data["tilesColor"] != nil) { - println("Room modifier detected") - Disp(data) - - //Get the interactive key - key = determineStrKey(data, []string{"tilesName", "tilesColor"}) - - message["type"] = "interact" - message["data"] = map[string]any{ - "id": obj["id"], - "param": key, - "value": data[key], - } - } else if entityType == models.RACK && data["U"] != nil { - message["type"] = "interact" - message["data"] = map[string]any{ - "id": obj["id"], - "param": "U", - "value": data["U"], - } - } else if (entityType == models.DEVICE || entityType == models.RACK) && - (data["alpha"] != nil || data["slots"] != nil || data["localCS"] != nil) { - - //Get interactive key - key = determineStrKey(data, []string{"alpha", "U", "slots", "localCS"}) - - message["type"] = "interact" - message["data"] = map[string]any{ - "id": obj["id"], - "param": key, - "value": data[key], - } - } else if entityType == models.GROUP && data["content"] != nil { - message["type"] = "interact" - message["data"] = map[string]any{ - "id": obj["id"], - "param": "content", - "value": data["content"], - } - } else { - return resp.Body, nil - } - - if IsEntityTypeForOGrEE3D(entityType) { - err := controller.Ogree3D.InformOptional("UpdateObj", entityType, message) - if err != nil { - return nil, err - } } return resp.Body, nil diff --git a/CLI/controllers/update_test.go b/CLI/controllers/update_test.go index 484231c2..dec44025 100644 --- a/CLI/controllers/update_test.go +++ b/CLI/controllers/update_test.go @@ -11,6 +11,8 @@ import ( "github.com/stretchr/testify/assert" ) +// region tags + func TestUpdateTagColor(t *testing.T) { controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) @@ -33,9 +35,6 @@ func TestUpdateTagColor(t *testing.T) { } test_utils.MockUpdateObject(mockAPI, dataUpdate, dataUpdated) - - controllers.State.ObjsForUnity = controllers.SetObjsForUnity([]string{"all"}) - _, err := controller.PatchObj(path, dataUpdate, false) assert.Nil(t, err) } @@ -64,132 +63,134 @@ func TestUpdateTagSlug(t *testing.T) { } test_utils.MockUpdateObject(mockAPI, dataUpdate, dataUpdated) - - controllers.State.ObjsForUnity = controllers.SetObjsForUnity([]string{"all"}) - _, err := controller.PatchObj(path, dataUpdate, false) assert.Nil(t, err) } -func TestUpdateRoomTiles(t *testing.T) { - tests := []struct { - name string - attributeKey string - oldValue string - newValue string - }{ - {"Color", "tilesColor", "aaaaaa", "aaaaab"}, - {"Name", "tilesName", "t1", "t2"}, - } +//endregion tags +// region device's sizeU - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller, mockAPI, mockOgree3D, _ := test_utils.NewControllerWithMocks(t) +// Test an update of a device's sizeU with heightUnit == mm +func TestUpdateDeviceSizeUmm(t *testing.T) { + controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) - room := test_utils.CopyMap(roomWithoutChildren) - room["attributes"] = map[string]any{ - tt.attributeKey: tt.oldValue, - } - updatedRoom := test_utils.CopyMap(room) - updatedRoom["attributes"].(map[string]any)[tt.attributeKey] = tt.newValue - dataUpdate := updatedRoom["attributes"].(map[string]any) - entity := models.ROOM - - path := "/Physical/" + strings.Replace(room["id"].(string), ".", "/", -1) - message := map[string]any{ - "type": "interact", - "data": map[string]any{ - "id": room["id"], - "param": tt.attributeKey, - "value": tt.newValue, - }, - } + path := models.PhysicalIDToPath("BASIC.A.R1.A01.chU1") - mockOgree3D.On("InformOptional", "UpdateObj", entity, message).Return(nil) + device := test_utils.GetEntity("device", "chU1", "BASIC.A.R1.A01", "test") + test_utils.MockGetObject(mockAPI, device) - test_utils.MockGetObject(mockAPI, room) + dataUpdate := map[string]any{ + "attributes": map[string]any{ + "sizeU": 1, + }, + } + mockDataUpdate := map[string]any{ + "attributes": map[string]any{ + "sizeU": float64(1), + "height": 44.45, + }, + } + device["attributes"].(map[string]any)["sizeU"] = 1 + device["attributes"].(map[string]any)["height"] = 44.45 - dataUpdated := test_utils.CopyMap(room) - dataUpdated["attributes"].(map[string]any)[tt.attributeKey] = tt.newValue + test_utils.MockUpdateObject(mockAPI, mockDataUpdate, device) + _, err := controller.PatchObj(path, dataUpdate, false) + assert.Nil(t, err) +} - test_utils.MockUpdateObject(mockAPI, dataUpdate, dataUpdated) +// Test an update of a device's sizeU with heightUnit == cm +func TestUpdateDeviceSizeUcm(t *testing.T) { + controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) - controllers.State.ObjsForUnity = controllers.SetObjsForUnity([]string{"all"}) + path := models.PhysicalIDToPath("BASIC.A.R1.A01.chU1") - result, err := controller.PatchObj(path, dataUpdate, false) - assert.Nil(t, err) - assert.Equal(t, result["data"].(map[string]any)["attributes"].(map[string]any)[tt.attributeKey], tt.newValue) - mockOgree3D.AssertCalled(t, "InformOptional", "UpdateObj", entity, message) - }) - } -} + device := test_utils.GetEntity("device", "chU1", "BASIC.A.R1.A01", "test") + device["attributes"].(map[string]any)["heightUnit"] = "cm" -func TestUpdateRackU(t *testing.T) { - controller, mockAPI, mockOgree3D, _ := test_utils.NewControllerWithMocks(t) - rack := test_utils.CopyMap(rack2) - rack["attributes"] = map[string]any{ - "U": true, + test_utils.MockGetObject(mockAPI, device) + + dataUpdate := map[string]any{ + "attributes": map[string]any{ + "sizeU": 1, + }, } - updatedRack := test_utils.CopyMap(rack) - updatedRack["attributes"].(map[string]any)["U"] = false - dataUpdate := updatedRack["attributes"].(map[string]any) - entity := models.RACK - - path := "/Physical/" + strings.Replace(rack["id"].(string), ".", "/", -1) - message := map[string]any{ - "type": "interact", - "data": map[string]any{ - "id": rack["id"], - "param": "U", - "value": false, + mockDataUpdate := map[string]any{ + "attributes": map[string]any{ + "sizeU": float64(1), + "height": 4.445, }, } + device["attributes"].(map[string]any)["sizeU"] = 1 + device["attributes"].(map[string]any)["height"] = 4.445 - mockOgree3D.On("InformOptional", "UpdateObj", entity, message).Return(nil) + test_utils.MockUpdateObject(mockAPI, mockDataUpdate, device) + _, err := controller.PatchObj(path, dataUpdate, false) + assert.Nil(t, err) +} - test_utils.MockGetObject(mockAPI, rack) - test_utils.MockUpdateObject(mockAPI, dataUpdate, updatedRack) +// endregion sizeU +// region device's height - controllers.State.ObjsForUnity = controllers.SetObjsForUnity([]string{"all"}) +// Test an update of a device's height with heightUnit == mm +func TestUpdateDeviceheightmm(t *testing.T) { + controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) - result, err := controller.PatchObj(path, dataUpdate, false) - assert.Nil(t, err) - assert.False(t, result["data"].(map[string]any)["attributes"].(map[string]any)["U"].(bool)) - mockOgree3D.AssertCalled(t, "InformOptional", "UpdateObj", entity, message) -} + path := models.PhysicalIDToPath("BASIC.A.R1.A01.chU1") -func TestUpdateDeviceAlpha(t *testing.T) { - controller, mockAPI, mockOgree3D, _ := test_utils.NewControllerWithMocks(t) - device := test_utils.CopyMap(chassis) - device["attributes"].(map[string]any)["alpha"] = true - updatedDevice := test_utils.CopyMap(device) - updatedDevice["attributes"].(map[string]any)["alpha"] = false - dataUpdate := updatedDevice["attributes"].(map[string]any) - entity := models.DEVICE + device := test_utils.GetEntity("device", "chU1", "BASIC.A.R1.A01", "test") + test_utils.MockGetObject(mockAPI, device) - path := "/Physical/" + strings.Replace(device["id"].(string), ".", "/", -1) - message := map[string]any{ - "type": "interact", - "data": map[string]any{ - "id": device["id"], - "param": "alpha", - "value": false, + dataUpdate := map[string]any{ + "attributes": map[string]any{ + "height": 44.45, + }, + } + mockDataUpdate := map[string]any{ + "attributes": map[string]any{ + "sizeU": float64(1), + "height": 44.45, }, } + device["attributes"].(map[string]any)["sizeU"] = 1 + device["attributes"].(map[string]any)["height"] = 44.45 - mockOgree3D.On("InformOptional", "UpdateObj", entity, message).Return(nil) + test_utils.MockUpdateObject(mockAPI, mockDataUpdate, device) + _, err := controller.PatchObj(path, dataUpdate, false) + assert.Nil(t, err) +} +// Test an update of a device's height with heightUnit == cm +func TestUpdateDeviceheightcm(t *testing.T) { + controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) + + path := models.PhysicalIDToPath("BASIC.A.R1.A01.chU1") + + device := test_utils.GetEntity("device", "chU1", "BASIC.A.R1.A01", "test") + device["attributes"].(map[string]any)["heightUnit"] = "cm" test_utils.MockGetObject(mockAPI, device) - test_utils.MockUpdateObject(mockAPI, dataUpdate, updatedDevice) - controllers.State.ObjsForUnity = controllers.SetObjsForUnity([]string{"all"}) + dataUpdate := map[string]any{ + "attributes": map[string]any{ + "height": 4.445, + }, + } + mockDataUpdate := map[string]any{ + "attributes": map[string]any{ + "sizeU": float64(1), + "height": 4.445, + }, + } + device["attributes"].(map[string]any)["sizeU"] = 1 + device["attributes"].(map[string]any)["height"] = 4.445 - result, err := controller.PatchObj(path, dataUpdate, false) + test_utils.MockUpdateObject(mockAPI, mockDataUpdate, device) + _, err := controller.PatchObj(path, dataUpdate, false) assert.Nil(t, err) - assert.False(t, result["data"].(map[string]any)["attributes"].(map[string]any)["alpha"].(bool)) - mockOgree3D.AssertCalled(t, "InformOptional", "UpdateObj", entity, message) } +// endregion +// region update attribute + func TestUpdateDeviceDescription(t *testing.T) { controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) device := test_utils.CopyMap(chassis) @@ -225,6 +226,32 @@ func TestUpdateDeviceAttribute(t *testing.T) { assert.Equal(t, result["data"].(map[string]any)["attributes"], updatedDevice["attributes"]) } +func TestUpdateGroupContent(t *testing.T) { + controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) + group := test_utils.CopyMap(rackGroup) + group["attributes"] = map[string]any{ + "content": "A,B", + } + newValue := "A,B,C" + updatedGroup := test_utils.CopyMap(group) + updatedGroup["attributes"].(map[string]any)["content"] = newValue + dataUpdate := updatedGroup["attributes"].(map[string]any) + + path := "/Physical/" + strings.Replace(group["id"].(string), ".", "/", -1) + + test_utils.MockGetObject(mockAPI, group) + test_utils.MockUpdateObject(mockAPI, dataUpdate, updatedGroup) + + controllers.State.ObjsForUnity = controllers.SetObjsForUnity([]string{"all"}) + + result, err := controller.PatchObj(path, dataUpdate, false) + assert.Nil(t, err) + assert.Equal(t, result["data"].(map[string]any)["attributes"].(map[string]any)["content"].(string), newValue) +} + +// endregion +// region update virtual + func TestAddVirtualConfig(t *testing.T) { controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) device := test_utils.CopyMap(chassis) @@ -265,28 +292,6 @@ func TestUpdateVirtualConfigData(t *testing.T) { assert.Nil(t, err) } -func TestUpdateRackBreakerData(t *testing.T) { - controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) - // original device - rack := test_utils.GetEntity("rack", "rack", "site.building.room", "domain") - path := "/Physical/site/building/room/rack" - breakers := map[string]any{"break1": map[string]any{"powerpanel": "panel1"}} - rack["attributes"].(map[string]any)[controllers.BreakerAttr+"s"] = test_utils.CopyMap(breakers) - // updated device - updatedRack := test_utils.CopyMap(rack) - breakers["break1"].(map[string]any)["powerpanel"] = "panel2" - updatedRack["attributes"].(map[string]any)[controllers.BreakerAttr+"s"] = breakers - // update data - dataUpdate := map[string]any{"attributes": updatedRack["attributes"]} - - test_utils.MockGetObject(mockAPI, rack) - test_utils.MockGetObject(mockAPI, rack) - test_utils.MockUpdateObject(mockAPI, dataUpdate, updatedRack) - - err := controller.UpdateObject(path, controllers.BreakerAttr+"s.break1.powerpanel", []any{"panel2"}) - assert.Nil(t, err) -} - func TestUpdateVirtualLink(t *testing.T) { controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) vobj := test_utils.CopyMap(vobjCluster) @@ -316,61 +321,29 @@ func TestUpdateVirtualLink(t *testing.T) { assert.Equal(t, result["data"].(map[string]any)["attributes"], vobj["attributes"]) } -func TestUpdateGroupContent(t *testing.T) { - controller, mockAPI, mockOgree3D, _ := test_utils.NewControllerWithMocks(t) - group := test_utils.CopyMap(rackGroup) - group["attributes"] = map[string]any{ - "content": "A,B", - } - newValue := "A,B,C" - updatedGroup := test_utils.CopyMap(group) - updatedGroup["attributes"].(map[string]any)["content"] = newValue - dataUpdate := updatedGroup["attributes"].(map[string]any) - entity := models.GROUP - - path := "/Physical/" + strings.Replace(group["id"].(string), ".", "/", -1) - message := map[string]any{ - "type": "interact", - "data": map[string]any{ - "id": group["id"], - "param": "content", - "value": newValue, - }, - } - - mockOgree3D.On("InformOptional", "UpdateObj", entity, message).Return(nil) - - test_utils.MockGetObject(mockAPI, group) - test_utils.MockUpdateObject(mockAPI, dataUpdate, updatedGroup) - - controllers.State.ObjsForUnity = controllers.SetObjsForUnity([]string{"all"}) - - result, err := controller.PatchObj(path, dataUpdate, false) - assert.Nil(t, err) - assert.Equal(t, result["data"].(map[string]any)["attributes"].(map[string]any)["content"].(string), newValue) - mockOgree3D.AssertCalled(t, "InformOptional", "UpdateObj", entity, message) -} +// endregion +// region update inner attr object -func TestSetRoomAreas(t *testing.T) { +func TestUpdateRackBreakerData(t *testing.T) { controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) + // original device + rack := test_utils.GetEntity("rack", "rack", "site.building.room", "domain") + path := "/Physical/site/building/room/rack" + breakers := map[string]any{"break1": map[string]any{"powerpanel": "panel1"}} + rack["attributes"].(map[string]any)[controllers.BreakerAttr+"s"] = test_utils.CopyMap(breakers) + // updated device + updatedRack := test_utils.CopyMap(rack) + breakers["break1"].(map[string]any)["powerpanel"] = "panel2" + updatedRack["attributes"].(map[string]any)[controllers.BreakerAttr+"s"] = breakers + // update data + dataUpdate := map[string]any{"attributes": updatedRack["attributes"]} - room := test_utils.GetEntity("room", "room", "site.building", "domain") - - roomResponse := test_utils.GetEntity("room", "room", "site.building", "domain") - test_utils.MockGetObject(mockAPI, room) - - roomResponse["attributes"] = map[string]any{ - "reserved": []float64{1, 2, 3, 4}, - "technical": []float64{1, 2, 3, 4}, - } - test_utils.MockUpdateObject(mockAPI, map[string]interface{}{"attributes": map[string]interface{}{"reserved": []float64{1, 2, 3, 4}, "technical": []float64{1, 2, 3, 4}}}, roomResponse) - - reservedArea := []float64{1, 2, 3, 4} - technicalArea := []float64{1, 2, 3, 4} - value, err := controller.UpdateRoomAreas("/Physical/site/building/room", []any{reservedArea, technicalArea}) + test_utils.MockGetObject(mockAPI, rack) + test_utils.MockGetObject(mockAPI, rack) + test_utils.MockUpdateObject(mockAPI, dataUpdate, updatedRack) + err := controller.UpdateObject(path, controllers.BreakerAttr+"s.break1.powerpanel", []any{"panel2"}) assert.Nil(t, err) - assert.NotNil(t, value) } func TestAddInnerAtrObjWorks(t *testing.T) { @@ -551,6 +524,33 @@ func TestDeleteInnerAtrObjWorks(t *testing.T) { } } +// endregion +// region room areas + +func TestSetRoomAreas(t *testing.T) { + controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t) + + room := test_utils.GetEntity("room", "room", "site.building", "domain") + + roomResponse := test_utils.GetEntity("room", "room", "site.building", "domain") + test_utils.MockGetObject(mockAPI, room) + + roomResponse["attributes"] = map[string]any{ + "reserved": []float64{1, 2, 3, 4}, + "technical": []float64{1, 2, 3, 4}, + } + test_utils.MockUpdateObject(mockAPI, map[string]interface{}{"attributes": map[string]interface{}{"reserved": []float64{1, 2, 3, 4}, "technical": []float64{1, 2, 3, 4}}}, roomResponse) + + reservedArea := []float64{1, 2, 3, 4} + technicalArea := []float64{1, 2, 3, 4} + value, err := controller.UpdateRoomAreas("/Physical/site/building/room", []any{reservedArea, technicalArea}) + + assert.Nil(t, err) + assert.NotNil(t, value) +} + +// endregion + func TestAddToMap(t *testing.T) { newMap, replaced := controllers.AddToMap[int](map[string]any{"a": 3}, "b", 10) diff --git a/CLI/controllers/utils.go b/CLI/controllers/utils.go index b94b2e94..dd7d137d 100644 --- a/CLI/controllers/utils.go +++ b/CLI/controllers/utils.go @@ -16,8 +16,6 @@ const ( DEBUG ) -const RACKUNIT = .04445 //meter - // displays contents of maps func Disp(x map[string]interface{}) { diff --git a/CLI/models/attributes.go b/CLI/models/attributes.go index b99950b0..fd0dc2a7 100644 --- a/CLI/models/attributes.go +++ b/CLI/models/attributes.go @@ -10,6 +10,8 @@ import ( "strings" ) +const RACKUNIT = .04445 //meter + type EntityAttributes map[string]any var BldgBaseAttrs = EntityAttributes{ @@ -126,10 +128,10 @@ func SetDeviceSizeUIfExists(attr EntityAttributes) { //And Set height if sizeUInt, ok := sizeU.(int); ok { attr["sizeU"] = sizeUInt - attr["height"] = float64(sizeUInt) * 44.5 + attr["height"] = float64(sizeUInt) * RACKUNIT * 1000 } else if sizeUFloat, ok := sizeU.(float64); ok { attr["sizeU"] = sizeUFloat - attr["height"] = sizeUFloat * 44.5 + attr["height"] = sizeUFloat * RACKUNIT * 1000 } } } @@ -158,7 +160,7 @@ func CheckExpandStrVector(slotVector []string) ([]string, error) { for _, slot := range slotVector { if strings.Contains(slot, "..") { if len(slotVector) != 1 { - return nil, fmt.Errorf("Invalid device syntax: .. can only be used in a single element vector") + return nil, fmt.Errorf("invalid device syntax: .. can only be used in a single element vector") } return expandStrToVector(slot) } else { @@ -170,7 +172,7 @@ func CheckExpandStrVector(slotVector []string) ([]string, error) { func expandStrToVector(slot string) ([]string, error) { slots := []string{} - errMsg := "Invalid device syntax: incorrect use of .. for slot" + errMsg := "invalid device syntax: incorrect use of .. for slot" parts := strings.Split(slot, "..") if len(parts) != 2 || (parts[0][:len(parts[0])-1] != parts[1][:len(parts[1])-1]) { @@ -238,3 +240,11 @@ func ErrorResponder(attr, numElts string, multi bool) error { return fmt.Errorf(errorMsg + segment) } +func MapStringAny(value any) (map[string]any, error) { + m, ok := value.(map[string]any) + if ok { + return m, nil + } else { + return nil, fmt.Errorf("unable to convert given value to a map") + } +} diff --git a/CLI/models/attributes_device.go b/CLI/models/attributes_device.go new file mode 100644 index 00000000..7ccd9e72 --- /dev/null +++ b/CLI/models/attributes_device.go @@ -0,0 +1,55 @@ +package models + +import ( + "cli/utils" + "fmt" +) + +// Compute coherent sizeU or height according to given data +func ComputeSizeUAndHeight(obj, data map[string]any) error { + errMsg := "unknown heightUnit value" + if data["attributes"] == nil { + return nil + } + newAttrs, err := MapStringAny(data["attributes"]) + if err != nil { + return err + } + currentAttrs, err := MapStringAny(obj["attributes"]) + if err != nil { + return err + } + if newAttrs["sizeU"] != nil { + sizeU, err := utils.GetFloat(newAttrs["sizeU"]) + if err != nil { + return err + } + var height = sizeU * RACKUNIT + switch heightUnit := currentAttrs["heightUnit"]; heightUnit { + case "cm": + height *= 100 + case "mm": + height *= 1000 + default: + return fmt.Errorf(errMsg) + } + newAttrs["height"] = height + } + if newAttrs["height"] != nil { + height, err := utils.GetFloat(newAttrs["height"]) + if err != nil { + return err + } + var sizeU = height / RACKUNIT + switch heightUnit := currentAttrs["heightUnit"]; heightUnit { + case "cm": + sizeU /= 100 + case "mm": + sizeU /= 1000 + default: + return fmt.Errorf(errMsg) + } + newAttrs["sizeU"] = sizeU + } + return nil +} diff --git a/CLI/models/attributes_device_test.go b/CLI/models/attributes_device_test.go new file mode 100644 index 00000000..79016687 --- /dev/null +++ b/CLI/models/attributes_device_test.go @@ -0,0 +1,112 @@ +package models_test + +import ( + "cli/models" + "testing" + + "github.com/stretchr/testify/assert" +) + +// region sizeU + +func TestComputeFromSizeUmm(t *testing.T) { + device := map[string]any{ + "attributes": map[string]any{ + "heightUnit": "mm", + }, + } + input := map[string]any{ + "attributes": map[string]any{ + "sizeU": 1, + }, + } + err := models.ComputeSizeUAndHeight(device, input) + assert.Nil(t, err) + +} + +func TestComputeFromSizeUcm(t *testing.T) { + device := map[string]any{ + "attributes": map[string]any{ + "heightUnit": "cm", + }, + } + input := map[string]any{ + "attributes": map[string]any{ + "sizeU": 1, + }, + } + err := models.ComputeSizeUAndHeight(device, input) + assert.Nil(t, err) + +} + +func TestComputeFromSizeUFail(t *testing.T) { + device := map[string]any{ + "attributes": map[string]any{ + "heightUnit": "banana", + }, + } + input := map[string]any{ + "attributes": map[string]any{ + "sizeU": 1, + }, + } + err := models.ComputeSizeUAndHeight(device, input) + assert.NotNil(t, err) + assert.ErrorContains(t, err, "unknown heightUnit value") +} + +// endregion + +// region height + +func TestComputeFromHeightmm(t *testing.T) { + device := map[string]any{ + "attributes": map[string]any{ + "heightUnit": "mm", + }, + } + input := map[string]any{ + "attributes": map[string]any{ + "height": 44.45, + }, + } + err := models.ComputeSizeUAndHeight(device, input) + assert.Nil(t, err) + +} + +func TestComputeFromHeightcm(t *testing.T) { + device := map[string]any{ + "attributes": map[string]any{ + "heightUnit": "cm", + }, + } + input := map[string]any{ + "attributes": map[string]any{ + "height": 4.445, + }, + } + err := models.ComputeSizeUAndHeight(device, input) + assert.Nil(t, err) + +} + +func TestComputeFromHeightFail(t *testing.T) { + device := map[string]any{ + "attributes": map[string]any{ + "heightUnit": "banana", + }, + } + input := map[string]any{ + "attributes": map[string]any{ + "height": 1, + }, + } + err := models.ComputeSizeUAndHeight(device, input) + assert.NotNil(t, err) + assert.ErrorContains(t, err, "unknown heightUnit value") +} + +// endregion diff --git a/CLI/models/attributes_test.go b/CLI/models/attributes_test.go index ad861eb4..efd862f5 100644 --- a/CLI/models/attributes_test.go +++ b/CLI/models/attributes_test.go @@ -16,22 +16,22 @@ func TestExpandSlotVector(t *testing.T) { slots, err := models.CheckExpandStrVector([]string{"slot1..slot3", "slot4"}) assert.Nil(t, slots) assert.NotNil(t, err) - assert.ErrorContains(t, err, "Invalid device syntax: .. can only be used in a single element vector") + assert.ErrorContains(t, err, "invalid device syntax: .. can only be used in a single element vector") slots, err = models.CheckExpandStrVector([]string{"slot1..slot3..slot7"}) assert.Nil(t, slots) assert.NotNil(t, err) - assert.ErrorContains(t, err, "Invalid device syntax: incorrect use of .. for slot") + assert.ErrorContains(t, err, "invalid device syntax: incorrect use of .. for slot") slots, err = models.CheckExpandStrVector([]string{"slot1..slots3"}) assert.Nil(t, slots) assert.NotNil(t, err) - assert.ErrorContains(t, err, "Invalid device syntax: incorrect use of .. for slot") + assert.ErrorContains(t, err, "invalid device syntax: incorrect use of .. for slot") slots, err = models.CheckExpandStrVector([]string{"slot1..slotE"}) assert.Nil(t, slots) assert.NotNil(t, err) - assert.ErrorContains(t, err, "Invalid device syntax: incorrect use of .. for slot") + assert.ErrorContains(t, err, "invalid device syntax: incorrect use of .. for slot") slots, err = models.CheckExpandStrVector([]string{"slot1..slot3"}) assert.Nil(t, err) diff --git a/CLI/parser/ast_test.go b/CLI/parser/ast_test.go index 123dd6d7..7a106bb7 100644 --- a/CLI/parser/ast_test.go +++ b/CLI/parser/ast_test.go @@ -722,26 +722,20 @@ func TestCreateDeviceNodeExecution(t *testing.T) { _, mockAPI, _, _ := test_utils.SetMainEnvironmentMock(t) rack := test_utils.GetEntity("rack", "myRack", "mySite.myBuilding.myRoom", "") device := test_utils.GetEntity("device", "myDevice", "mySite.myBuilding.myRoom.myRack", "") - device["attributes"].(map[string]any)["posU/slot"] = []string{} - device["attributes"].(map[string]any)["sizeU"] = 10 - device["attributes"].(map[string]any)["sizeUnit"] = "mm" - device["attributes"].(map[string]any)["height"] = 445.0 - device["attributes"].(map[string]any)["heightUnit"] = "mm" - device["attributes"].(map[string]any)["orientation"] = "front" - device["attributes"].(map[string]any)["invertOffset"] = false - delete(device["attributes"].(map[string]any), "size") + delete(device["attributes"].(map[string]any), "size") delete(device, "id") delete(device, "children") + delete(device, "tags") test_utils.MockGetObject(mockAPI, rack) test_utils.MockCreateObject(mockAPI, "device", device) executionNode := createDeviceNode{ path: pathNode{path: &valueNode{"/Physical/mySite/myBuilding/myRoom/myRack/myDevice"}}, - posUOrSlot: []node{}, + posUOrSlot: []node{&valueNode{1}}, invertOffset: false, - sizeUOrTemplate: &valueNode{10}, + sizeUOrTemplate: &valueNode{2}, side: &valueNode{"front"}, } diff --git a/CLI/test/utils.go b/CLI/test/utils.go index 64a78d01..cf7d9be5 100644 --- a/CLI/test/utils.go +++ b/CLI/test/utils.go @@ -176,19 +176,23 @@ func GetEntity(entityName string, name string, parentId string, domain string) m } case "device": return map[string]any{ + "attributes": map[string]any{ + "height": 88.9, + "heightUnit": "mm", + "invertOffset": false, + "orientation": "front", + "posU": 1, + "size": []float64{60, 120}, + "sizeU": 2, + "sizeUnit": "mm", + }, "category": "device", + "description": "", + "domain": domain, "id": id, "name": name, "parentId": parentId, - "domain": domain, - "description": "", - "attributes": map[string]any{ - "height": 47, - "heightUnit": "U", - "orientation": "front", - "size": []float64{1, 1}, - "sizeUnit": "cm", - }, + "tags": []any{}, } case "generic": return map[string]any{