Skip to content

Commit

Permalink
Add dry run and refactor controller (#486)
Browse files Browse the repository at this point in the history
* feat(api) update validate endpoint

* feat(cli) add dry run

* refactor(cli) adapt pattern

* refactor(cli) packages

* refactor(cli) simplify create obj

* refactor(cli) pattern

* fix(cli) tests

* fix(cli,api) tests

* fix(cli, api) minor fixes

* fix(cli) improve Cognitive Complexity

* refactor(cli) minor improvements

* fix(cli) test

* fix(api) test

* fix(api) improve cc

* fix(api) improve cc

* fix(api) improve cc

* feat(cli) add dry run flag to cmds
  • Loading branch information
helderbetiol authored Jul 3, 2024
1 parent 6bf309f commit 1661c88
Show file tree
Hide file tree
Showing 77 changed files with 3,577 additions and 3,692 deletions.
22 changes: 13 additions & 9 deletions API/controllers/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ func HandleGenericObjects(w http.ResponseWriter, r *http.Request) {
// Get objects
filters := getFiltersFromQueryParams(r)
req := u.FilteredReqFromQueryParams(r.URL)
entities := u.GetEntitiesByNamespace(filters.Namespace, filters.Id)
entities := u.GetEntitiesById(filters.Namespace, filters.Id)

for _, entStr := range entities {
// Get objects
Expand Down Expand Up @@ -669,7 +669,7 @@ func HandleComplexFilters(w http.ResponseWriter, r *http.Request) {
// Get objects
filters := getFiltersFromQueryParams(r)
req := u.FilteredReqFromQueryParams(r.URL)
entities := u.GetEntitiesByNamespace(filters.Namespace, filters.Id)
entities := u.GetEntitiesById(filters.Namespace, filters.Id)

for _, entStr := range entities {
// Get objects
Expand Down Expand Up @@ -906,7 +906,7 @@ func GetLayerObjects(w http.ResponseWriter, r *http.Request) {

// Get objects
matchingObjects := []map[string]interface{}{}
entities := u.GetEntitiesByNamespace(u.Any, searchId)
entities := u.GetEntitiesById(u.Any, searchId)
fmt.Println(req)
fmt.Println(entities)
for _, entStr := range entities {
Expand Down Expand Up @@ -2047,7 +2047,13 @@ func ValidateEntity(w http.ResponseWriter, r *http.Request) {
}

if u.IsEntityHierarchical(entInt) {
if permission := models.CheckUserPermissions(user.Roles, entInt, obj["domain"].(string)); permission < models.WRITE {
domain := ""
if entInt == u.DOMAIN {
domain = obj["parentId"].(string) + obj["name"].(string)
} else if domainStr, ok := obj["domain"].(string); ok {
domain = domainStr
}
if permission := models.CheckUserPermissions(user.Roles, entInt, domain); permission < models.WRITE {
w.WriteHeader(http.StatusUnauthorized)
u.Respond(w, u.Message("This user"+
" does not have sufficient permissions to create"+
Expand All @@ -2058,12 +2064,10 @@ func ValidateEntity(w http.ResponseWriter, r *http.Request) {
}
}

uErr := models.ValidateEntity(entInt, obj)
if uErr == nil {
u.Respond(w, u.Message("This object can be created"))
return
if ok, err := models.ValidateJsonSchema(entInt, obj); !ok {
u.RespondWithError(w, err)
} else {
u.RespondWithError(w, uErr)
u.Respond(w, u.Message("This object can be created"))
}
}

Expand Down
9 changes: 5 additions & 4 deletions API/controllers/entity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,20 +513,21 @@ func TestValidateEntityWithoutAttributes(t *testing.T) {
func TestValidateEntity(t *testing.T) {
integration.CreateTestDomain(t, "temporaryDomain", "", "")
integration.CreateTestPhysicalEntity(t, utils.BLDG, "tempBldg", "tempSite", true)
room := test_utils.GetEntityMap("room", "roomA", "tempSite.tempBldg", "")
room := test_utils.GetEntityMap("room", "roomA", "tempSite.tempBldg", integration.TestDBName)

endpoint := test_utils.GetEndpoint("validateEntity", "rooms")
tests := []struct {
name string
domain string
domain any
statusCode int
message string
}{
{"NonExistentDomain", "invalid", http.StatusNotFound, "Domain not found: invalid"},
{"InvalidDomain", "temporaryDomain", http.StatusBadRequest, "Object domain is not equal or child of parent's domain"},

{"ValidRoomEntity", integration.TestDBName, http.StatusOK, "This object can be created"},
{"NonExistentDomain", 222, http.StatusBadRequest, "JSON body doesn't validate with the expected JSON schema"},
}
for _, tt := range tests {

t.Run(tt.name, func(t *testing.T) {
room["domain"] = tt.domain
requestBody, _ := json.Marshal(room)
Expand Down
154 changes: 154 additions & 0 deletions API/models/attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package models

import (
"p3/repository"
u "p3/utils"
"strings"

"github.com/elliotchance/pie/v2"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)

func validateAttributes(entity int, data, parent map[string]any) *u.Error {
attributes := data["attributes"].(map[string]any)
switch entity {
case u.CORRIDOR:
setCorridorColor(attributes)
case u.GROUP:
if err := validateGroupContent(attributes["content"].([]any),
data["parentId"].(string), parent["parent"].(string)); err != nil {
return err
}
case u.DEVICE:
var deviceSlots []string
var err *u.Error
if deviceSlots, err = slotToValidSlice(attributes); err != nil {
return err
}
// check if all requested slots are free
if err = validateDeviceSlots(deviceSlots,
data["name"].(string), data["parentId"].(string)); err != nil {
return err
}
case u.VIRTUALOBJ:
if attributes["vlinks"] != nil {
// check if all vlinks point to valid objects
if err := validateVlinks(attributes["vlinks"].([]any)); err != nil {
return err
}
}
}
return nil
}

func validateDeviceSlots(deviceSlots []string, deviceName, deviceParentd string) *u.Error {
// check if all requested slots are free
var siblings []map[string]any
var err *u.Error

// find siblings
idPattern := primitive.Regex{Pattern: "^" + deviceParentd +
"(." + u.NAME_REGEX + "){1}$", Options: ""}
if siblings, err = GetManyObjects(u.EntityToString(u.DEVICE), bson.M{"id": idPattern},
u.RequestFilters{}, "", nil); err != nil {
return err
}

for _, obj := range siblings {
if obj["name"] == deviceName {
// do not check itself
continue
}
if siblingSlots, err := slotToValidSlice(obj["attributes"].(map[string]any)); err == nil {
for _, requestedSlot := range deviceSlots {
if pie.Contains(siblingSlots, requestedSlot) {
return &u.Error{Type: u.ErrBadFormat,
Message: "Invalid slot: one or more requested slots are already in use"}
}
}
} else {
// fmt.Println(err)
}
}
return nil
}

func validateVlinks(vlinks []any) *u.Error {
for _, vlinkId := range vlinks {
count, err := repository.CountObjectsManyEntities([]int{u.DEVICE, u.VIRTUALOBJ},
bson.M{"id": strings.Split(vlinkId.(string), "#")[0]})
if err != nil {
return err
}

if count != 1 {
return &u.Error{
Type: u.ErrBadFormat,
Message: "One or more vlink objects could not be found. Note that it must be device or virtual obj",
}
}
}
return nil
}

func validateGroupContent(content []any, parentId, parentCategory string) *u.Error {
if len(content) <= 1 && content[0] == "" {
return &u.Error{
Type: u.ErrBadFormat,
Message: "objects separated by a comma must be on the payload",
}
}

// Ensure objects are all unique
if !pie.AreUnique(content) {
return &u.Error{
Type: u.ErrBadFormat,
Message: "The group cannot have duplicate objects",
}
}

// Ensure objects all exist
if err := checkGroupContentExists(content, parentId, parentCategory); err != nil {
return err
}

return nil
}

func checkGroupContentExists(content []any, parentId, parentCategory string) *u.Error {
// Get filter
filter := repository.GroupContentToOrFilter(content, parentId)

// Get entities
var siblingsEnts []int
if parentCategory == "rack" {
// If parent is rack, retrieve devices
siblingsEnts = []int{u.DEVICE}
} else {
// If parent is room, retrieve room children
siblingsEnts = u.RoomChildren
}

// Try to get the whole content
count, err := repository.CountObjectsManyEntities(siblingsEnts, filter)
if err != nil {
return err
}
if count != len(content) {
return &u.Error{
Type: u.ErrBadFormat,
Message: "Some object(s) could not be found. Please check and try again",
}
}
return nil
}

func setCorridorColor(attributes map[string]any) {
// Set the color manually based on temp. as specified by client
if attributes["temperature"] == "warm" {
attributes["color"] = "990000"
} else if attributes["temperature"] == "cold" {
attributes["color"] = "000099"
}
}
6 changes: 3 additions & 3 deletions API/models/create_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,13 @@ func TestValidateEntityGroupParent(t *testing.T) {
}
err := models.ValidateEntity(u.GROUP, template)
assert.NotNil(t, err)
assert.Equal(t, "Group parent should correspond to existing rack or room", err.Message)
assert.Equal(t, "JSON body doesn't validate with the expected JSON schema", err.Message)

template["parentId"] = "temporarySite.temporaryBuilding.temporaryRoom"
template["name"] = "groupA"
err = models.ValidateEntity(u.GROUP, template)
assert.NotNil(t, err)
assert.Equal(t, "All group objects must be directly under the parent (no . allowed)", err.Message)
assert.Equal(t, "JSON body doesn't validate with the expected JSON schema", err.Message)

template["parentId"] = "temporarySite.temporaryBuilding.temporaryRoom"
template["name"] = "groupA"
Expand Down Expand Up @@ -225,7 +225,7 @@ func TestCreateCorridorOrGenericWithSameNameAsRackReturnsError(t *testing.T) {
_, err := tt.createFunction(roomId, childName)
assert.NotNil(t, err)
assert.Equal(t, u.ErrBadFormat, err.Type)
assert.Equal(t, "Object name must be unique among corridors, racks and generic objects", err.Message)
assert.Equal(t, "This object ID is not unique", err.Message)
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions API/models/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func prepareCreateEntity(entity int, t map[string]interface{}, userRoles map[str

func GetHierarchyObjectById(hierarchyName string, filters u.RequestFilters, userRoles map[string]Role) (map[string]interface{}, *u.Error) {
// Get possible collections for this name
rangeEntities := u.GetEntitiesByNamespace(u.PHierarchy, hierarchyName)
rangeEntities := u.GetEntitiesById(u.PHierarchy, hierarchyName)
req := bson.M{"id": hierarchyName}

// Search each collection
Expand Down Expand Up @@ -460,7 +460,7 @@ func getHierarchyWithNamespace(namespace u.Namespace, userRoles map[string]Role,
}

// Search collections according to namespace
entities := u.GetEntitiesByNamespace(namespace, "")
entities := u.GetEntitiesById(namespace, "")

for _, entityName := range entities {
// Get data
Expand Down
File renamed without changes.
3 changes: 2 additions & 1 deletion API/models/schemas/group_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"content": {
"type": "array",
"items": {
"type": "string"
"type": "string",
"$ref": "refs/types.json#/definitions/name"
},
"minItems": 1
},
Expand Down
Loading

0 comments on commit 1661c88

Please sign in to comment.