Commit e2364a1a authored by ale's avatar ale

Allow creation of resources referencing pre-existing ParentIDs

Previously we were dropping these on the floor without any possibility
for the client to detect this case, because external parent ID
references (i.e. pointing at objects already in the database, rather
than temporary resource IDs in the same CreateResources request) were
dropped silently in the sortResourcesByDepth() function...
parent 3f8ea064
Pipeline #5126 passed with stages
in 4 minutes and 51 seconds
......@@ -602,6 +602,7 @@ func (tx *backendTX) createSingleResource(user *as.User, rsrc *as.Resource) erro
// Horrible algorithm: inefficient on large lists, never terminates on loops.
func sortResourcesByDepth(rsrcs []*as.Resource) []*as.Resource {
var out []*as.Resource
seen := make(map[string]struct{})
stack := []as.ResourceID{as.ResourceID("")}
for len(stack) > 0 {
cur := stack[0]
......@@ -610,6 +611,7 @@ func sortResourcesByDepth(rsrcs []*as.Resource) []*as.Resource {
for _, r := range rsrcs {
if r.ParentID.Equal(cur) {
out = append(out, r)
seen[string(r.ID)] = struct{}{}
stack = append(stack, r.ID)
} else {
left = append(left, r)
......@@ -617,6 +619,14 @@ func sortResourcesByDepth(rsrcs []*as.Resource) []*as.Resource {
}
rsrcs = left
}
// See if there are any resources left in rsrc that haven't
// been copied to the result slice. These will have external
// ParentIDs.
for _, r := range rsrcs {
if _, ok := seen[string(r.ID)]; !ok {
out = append(out, r)
}
}
return out
}
......@@ -633,11 +643,10 @@ func (tx *backendTX) CreateResources(ctx context.Context, user *as.User, rsrcs [
for _, rsrc := range sortResourcesByDepth(rsrcs) {
oldID := rsrc.ID
if !rsrc.ParentID.Empty() {
pid, ok := idMap[rsrc.ParentID]
if !ok {
return nil, fmt.Errorf("resource %s references unknown parent_id %s", rsrc.String(), rsrc.ParentID.String())
// Attempt to resolve "local" parentID reference.
if pid, ok := idMap[rsrc.ParentID]; ok {
rsrc.ParentID = pid
}
rsrc.ParentID = pid
}
if err := tx.createSingleResource(user, rsrc); err != nil {
return nil, err
......
......@@ -434,3 +434,21 @@ func TestSortResources(t *testing.T) {
t.Fatalf("bad ordering: got %v, expected %v", ids, expected)
}
}
func TestSortResources_ExternalParentID(t *testing.T) {
rsrcs := []*as.Resource{
&as.Resource{
ID: "id1",
ParentID: "pre-existing-id",
},
}
expected := []string{"id1"}
var ids []string
for _, r := range sortResourcesByDepth(rsrcs) {
ids = append(ids, r.ID.String())
}
if diff := deep.Equal(ids, expected); diff != nil {
t.Fatalf("bad ordering: got %v, expected %v", ids, expected)
}
}
......@@ -107,9 +107,20 @@ func TestIntegration_CreateResources(t *testing.T) {
},
false,
},
// Create a database resource (nested below a website).
{
&as.Resource{
Name: "unodb2",
Type: as.ResourceTypeDatabase,
ParentID: "alias=uno,uid=uno@investici.org,ou=People,dc=example,dc=com",
},
true,
},
}
for _, td := range testdata {
var resp as.CreateResourcesResponse
err := c.request("/api/resource/create", &as.CreateResourcesRequest{
AdminRequestBase: as.AdminRequestBase{
RequestBase: as.RequestBase{
......@@ -118,11 +129,22 @@ func TestIntegration_CreateResources(t *testing.T) {
},
Username: "uno@investici.org",
Resources: []*as.Resource{td.resource},
}, nil)
}, &resp)
if err == nil && !td.expectedOk {
t.Errorf("CreateResource(%s) should have failed but didn't", td.resource.ID)
t.Errorf("CreateResource(%s) should have failed but didn't", td.resource.Name)
} else if err != nil && td.expectedOk {
t.Errorf("CreateResource(%s) failed: %v", td.resource.ID, err)
t.Errorf("CreateResource(%s) failed: %v", td.resource.Name, err)
}
// Validate the response.
if err == nil {
if len(resp.Resources) < 1 {
t.Errorf("CreateResource(%s): no resources in response", td.resource.Name)
continue
}
if resp.Resources[0].ID == "" {
t.Errorf("CreateResources(%s): resource ID unset in response", td.resource.Name)
}
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment