Skip to content
This repository has been archived by the owner on Mar 11, 2021. It is now read-only.

Enable the delete workitem endpoint #2305

Merged
merged 32 commits into from
Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f93db24
[1] Enable the delete workitem endpoint
DhritiShikhar Sep 28, 2018
3fda771
rename function
DhritiShikhar Sep 28, 2018
aa39af2
Removes MethodNotAllowed HTTP response
DhritiShikhar Sep 28, 2018
ed9d652
Removes a test which is not longer valid
DhritiShikhar Sep 28, 2018
9f8c107
Adds tests for delete workitem
DhritiShikhar Oct 8, 2018
cfaed6b
Adds tests, returns proper error
DhritiShikhar Oct 10, 2018
f9be9b0
Merge remote-tracking branch 'upstream/master' into delete-workitem-1
DhritiShikhar Oct 10, 2018
1b07bad
removes unused identity
DhritiShikhar Oct 10, 2018
37e2d35
Adds test to check work item link deletion
DhritiShikhar Oct 10, 2018
1aaa631
adds comments in tests
DhritiShikhar Oct 10, 2018
115d334
Merge remote-tracking branch 'upstream/master' into delete-workitem-1
DhritiShikhar Oct 10, 2018
77b6e4e
removes unnecessary code
DhritiShikhar Oct 10, 2018
f7fd115
* Removes topology from test case
DhritiShikhar Oct 11, 2018
bb06677
Merge remote-tracking branch 'upstream/master' into delete-workitem-1
DhritiShikhar Oct 11, 2018
49d5479
Uses uuid for creator and editor in isWorkitemCreatorOrSpaceOwner
DhritiShikhar Oct 22, 2018
eb19194
Merge remote-tracking branch 'upstream/master' into delete-workitem-1
DhritiShikhar Oct 22, 2018
0c8fe6e
change isWorkitemCreatorOrSpaceOwner method to return only error
DhritiShikhar Oct 22, 2018
5990f13
Error handling
DhritiShikhar Oct 22, 2018
50c9e17
Adds repository level test for delete workitem
DhritiShikhar Oct 22, 2018
d37cced
Adds test for deleteLinks
DhritiShikhar Oct 22, 2018
e8a08a5
Corrects error returned
DhritiShikhar Oct 23, 2018
31ef3d4
Merge remote-tracking branch 'upstream/master' into delete-workitem-1
DhritiShikhar Oct 23, 2018
68517a6
Updates comment
DhritiShikhar Oct 23, 2018
b996cf6
update error response
DhritiShikhar Oct 23, 2018
6bc6b87
Uncomments an delete workitem test
DhritiShikhar Oct 23, 2018
3d6aabb
Adds assertion to test cases
DhritiShikhar Oct 23, 2018
15ac6ab
Merge remote-tracking branch 'upstream/master' into delete-workitem-1
DhritiShikhar Oct 23, 2018
f9d6c10
removes unnecessary comment
DhritiShikhar Oct 23, 2018
b2f76c6
Merge branch 'master' into del-wi-1
DhritiShikhar Oct 23, 2018
41bc004
checks for result of type conversion
DhritiShikhar Oct 23, 2018
16a708e
Changes name of function
DhritiShikhar Oct 23, 2018
e131257
Merge remote-tracking branch 'origin/del-wi-1' into delete-workitem-1
DhritiShikhar Oct 23, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 33 additions & 25 deletions controller/workitem.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,23 +71,21 @@ func NewNotifyingWorkitemController(service *goa.Service, db application.DB, not
config: config}
}

// authorizeWorkitemTypeEditor returns true if the modifier is allowed to change
// workitem type else it returns false.
// Only space owner and workitem creator are allowed to change workitem type
func (c *WorkitemController) authorizeWorkitemTypeEditor(ctx context.Context, spaceID uuid.UUID, creatorID string, editorID string) (bool, error) {
// isWorkitemCreatorOrSpaceOwner checks if the modifier is space owner or workitem creator
func (c *WorkitemController) isWorkitemCreatorOrSpaceOwner(ctx context.Context, spaceID uuid.UUID, creatorID uuid.UUID, editorID uuid.UUID) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A function that begins with is should return a boolean IMHO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed it here
16a708e

// check if workitem editor is same as workitem creator
if editorID == creatorID {
return true, nil
return nil
}
space, err := c.db.Spaces().Load(ctx, spaceID)
if err != nil {
return false, errors.NewNotFoundError("space", spaceID.String())
return errors.NewNotFoundError("space", spaceID.String())
}
// check if workitem editor is same as space owner
if space != nil && editorID == space.OwnerID.String() {
return true, nil
if space != nil && editorID == space.OwnerID {
return nil
}
return false, errors.NewUnauthorizedError("user is not allowed to change workitem type")
return errors.NewForbiddenError("user is not a workitem creator or space owner")
}

// Returns true if the user is the work item creator or space collaborator
Expand Down Expand Up @@ -123,6 +121,10 @@ func (c *WorkitemController) Update(ctx *app.UpdateWorkitemContext) error {
if creator == nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, errs.New("work item doesn't have creator")))
}
creatorID, err := uuid.FromString(creator.(string))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, never ever do a type conversion without checking for the result. Otherwise we have a panic.

Suggested change
creatorID, err := uuid.FromString(creator.(string))
creatorIDStr, ok := creator.(string)
if !ok {
return jsonapi.JSONErrorResponse(ctx, errs.Errorf("failed to convert user to string: %+v (%[1]T)", creator))
}
creatorID, err := uuid.FromString(creatorIDStr)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kwk thanks for pointing out. addressed it here
41bc004

if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}
authorized, err := authorizeWorkitemEditor(ctx, c.db, wi.SpaceID, creator.(string), currentUserIdentityID.String())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
Expand All @@ -134,13 +136,10 @@ func (c *WorkitemController) Update(ctx *app.UpdateWorkitemContext) error {
if ctx.Payload.Data.Relationships != nil && ctx.Payload.Data.Relationships.BaseType != nil &&
ctx.Payload.Data.Relationships.BaseType.Data != nil && ctx.Payload.Data.Relationships.BaseType.Data.ID != wi.Type {

authorized, err := c.authorizeWorkitemTypeEditor(ctx, wi.SpaceID, creator.(string), currentUserIdentityID.String())
err := c.isWorkitemCreatorOrSpaceOwner(ctx, wi.SpaceID, creatorID, *currentUserIdentityID)
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}
if !authorized {
return jsonapi.JSONErrorResponse(ctx, errors.NewForbiddenError("user is not authorized to change the workitemtype"))
}
// Store new values of type and version
newType := ctx.Payload.Data.Relationships.BaseType
newVersion := ctx.Payload.Data.Attributes[workitem.SystemVersion]
Expand Down Expand Up @@ -236,39 +235,48 @@ func (c *WorkitemController) Show(ctx *app.ShowWorkitemContext) error {

// Delete does DELETE workitem
func (c *WorkitemController) Delete(ctx *app.DeleteWorkitemContext) error {
// Temporarly disabled, See https://github.com/fabric8-services/fabric8-wit/issues/1036
if true {
return ctx.MethodNotAllowed()
}
currentUserIdentityID, err := login.ContextIdentity(ctx)
if err != nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError(err.Error()))
}

var wi *workitem.WorkItem
err = application.Transactional(c.db, func(appl application.Application) error {
wi, err = appl.WorkItems().LoadByID(ctx, ctx.WiID)
if err != nil {
return errs.Wrap(err, fmt.Sprintf("Failed to load work item with id %v", ctx.WiID))
return jsonapi.JSONErrorResponse(ctx, errs.Wrap("failed to delete workitem", err))
}
return nil
})
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
jarifibrahim marked this conversation as resolved.
Show resolved Hide resolved
}
authorized, err := authz.Authorize(ctx, wi.SpaceID.String())

// Check if user is space owner or workitem creator. Only space owner or workitem creator are allowed to delete the workitem.
creator := wi.Fields[workitem.SystemCreator]
if creator == nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, errs.New("work item doesn't have creator")))
}
creatorID, err := uuid.FromString(creator.(string))
if err != nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError(err.Error()))
return jsonapi.JSONErrorResponse(ctx, err)
}
if !authorized {
return jsonapi.JSONErrorResponse(ctx, errors.NewForbiddenError("user is not authorized to access the space"))
err = c.isWorkitemCreatorOrSpaceOwner(ctx, wi.SpaceID, creatorID, *currentUserIdentityID)
if err != nil {
forbidden, _ := errors.IsForbiddenError(err)
if forbidden {
return jsonapi.JSONErrorResponse(ctx, errors.NewForbiddenError("user is not authorized to delete the workitem"))

}
return jsonapi.JSONErrorResponse(ctx, err)
}
err = application.Transactional(c.db, func(appl application.Application) error {
if err := appl.WorkItems().Delete(ctx, ctx.WiID, *currentUserIdentityID); err != nil {
return errs.Wrapf(err, "error deleting work item %s", ctx.WiID)
}
if err := appl.WorkItemLinks().DeleteRelatedLinks(ctx, ctx.WiID, *currentUserIdentityID); err != nil {
return errs.Wrapf(err, "failed to delete work item links related to work item %s", ctx.WiID)
}
if err := appl.WorkItems().Delete(ctx, ctx.WiID, *currentUserIdentityID); err != nil {
return errs.Wrapf(err, "error deleting work item %s", ctx.WiID)
}
return nil
})
if err != nil {
Expand Down
51 changes: 40 additions & 11 deletions controller/workitem_blackbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2304,17 +2304,6 @@ func (s *WorkItem2Suite) TestWI2FailShowMissing() {
test.ShowWorkitemNotFound(s.T(), s.svc.Context, s.svc, s.workitemCtrl, uuid.NewV4(), nil, nil)
}

func (s *WorkItem2Suite) TestWI2FailOnDelete() {
c := minimumRequiredCreatePayload()
c.Data.Attributes[workitem.SystemTitle] = "Title"
c.Data.Attributes[workitem.SystemState] = workitem.SystemStateNew
c.Data.Relationships.BaseType = newRelationBaseType(workitem.SystemBug)

_, createdWI := test.CreateWorkitemsCreated(s.T(), s.svc.Context, s.svc, s.workitemsCtrl, *c.Data.Relationships.Space.Data.ID, &c)
test.ShowWorkitemOK(s.T(), s.svc.Context, s.svc, s.workitemCtrl, *createdWI.Data.ID, nil, nil)
test.DeleteWorkitemMethodNotAllowed(s.T(), s.svc.Context, s.svc, s.workitemCtrl, *createdWI.Data.ID)
}

func (s *WorkItem2Suite) TestWI2CreateWithArea() {
fxt := tf.NewTestFixture(s.T(), s.DB,
tf.CreateWorkItemEnvironment(),
Expand Down Expand Up @@ -3392,3 +3381,43 @@ func (s *WorkItem2Suite) TestCreateAndUpdateWorkItemForEveryWIT() {
})
}
}

// TestDeleteWorkitem tests the delete action
func (s *WorkItem2Suite) TestDeleteWorkitem() {
// Delete Workitem tests deletion of workitem
s.T().Run("Delete Workitem", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
fxt := tf.NewTestFixture(s.T(), s.DB, tf.WorkItems(1))
s.svc = testsupport.ServiceAsUser("TestUpdateWI2-Service", *fxt.Identities[0])
test.DeleteWorkitemOK(s.T(), s.svc.Context, s.svc, s.workitemCtrl, fxt.WorkItems[0].ID)
})
t.Run("unauthorized", func(t *testing.T) {
fxt := tf.NewTestFixture(s.T(), s.DB, tf.WorkItems(1))
svcNotAuthorized := goa.New("TestDeleteWI2-Service")
workitemCtrlNotAuthorized := NewWorkitemController(svcNotAuthorized, s.GormDB, s.Configuration)
test.DeleteWorkitemUnauthorized(s.T(), svcNotAuthorized.Context, svcNotAuthorized, workitemCtrlNotAuthorized, fxt.WorkItems[0].ID)
})
t.Run("forbidden", func(t *testing.T) {
fxt := tf.NewTestFixture(s.T(), s.DB, tf.WorkItems(1), tf.Identities(2))
s.svc = testsupport.ServiceAsUser("TestUpdateWI2-Service", *fxt.Identities[1])
test.DeleteWorkitemForbidden(s.T(), s.svc.Context, s.svc, s.workitemCtrl, fxt.WorkItems[0].ID)
})
t.Run("workitem not found", func(t *testing.T) {
test.DeleteWorkitemNotFound(s.T(), s.svc.Context, s.svc, s.workitemCtrl, uuid.NewV4())
})
})
// Delete Workitem Links tests deletion of corresponding workitem links when a workitem is deleted
s.T().Run("Delete Workitem Links", func(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this test pass? I don't see where you're deleting links.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kwk checkout this -> https://github.com/fabric8-services/fabric8-wit/pull/2305/files#diff-02845b093c442bf1ae790a6ebb6c8da8R273

DeleteRelatedLinks delete the links associated with the workitem.

t.Run("ok", func(t *testing.T) {
fxt := tf.NewTestFixture(t, s.DB,
tf.CreateWorkItemEnvironment(),
tf.WorkItems(2, tf.SetWorkItemTitles("A", "B")),
tf.WorkItemLinkTypes(1),
tf.WorkItemLinksCustom(1, tf.BuildLinks(tf.LinkChain("A", "B")...)),
)
s.svc = testsupport.ServiceAsUser("TestUpdateWI2-Service", *fxt.Identities[0])
test.DeleteWorkitemOK(s.T(), s.svc.Context, s.svc, s.workitemCtrl, fxt.WorkItems[0].ID)
test.ShowWorkItemLinkNotFound(t, s.svc.Context, s.svc, s.linkCtrl, fxt.WorkItemLinks[0].ID, nil, nil)
})
})
}
2 changes: 0 additions & 2 deletions design/workitems.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,7 @@ var _ = a.Resource("workitem", func() {
a.Params(func() {
a.Param("wiID", d.UUID, "ID of the work item to delete")
})
a.Response(d.MethodNotAllowed)
a.Response(d.OK)
a.Response(d.BadRequest, JSONAPIErrors)
a.Response(d.InternalServerError, JSONAPIErrors)
a.Response(d.NotFound, JSONAPIErrors)
a.Response(d.Unauthorized, JSONAPIErrors)
Expand Down
8 changes: 8 additions & 0 deletions workitem/link/link_repository_blackbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -892,3 +892,11 @@ func (s *linkRepoBlackBoxTest) TestDeleteLinkAndListChildren() {
require.Len(t, childrenList, 0)
})
}

func (s *linkRepoBlackBoxTest) TestDeleteLink() {
s.T().Run("ok", func(t *testing.T) {
fxt := tf.NewTestFixture(t, s.DB, tf.WorkItemLinks(1))
err := s.workitemLinkRepo.DeleteRelatedLinks(s.Ctx, fxt.WorkItems[0].ID, fxt.Identities[0].ID)
require.NoError(t, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to add more assertion here? Other than just checking error, if it is possible to assert that there is no link exists, that would be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed here 3d6aabb

})
}
10 changes: 10 additions & 0 deletions workitem/workitem_repository_blackbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -769,3 +769,13 @@ func (s *workItemRepoBlackBoxTest) TestList() {

})
}

func (s *workItemRepoBlackBoxTest) TestDeleteWorkitem() {
jarifibrahim marked this conversation as resolved.
Show resolved Hide resolved
s.T().Run("ok", func(t *testing.T) {
fxt := tf.NewTestFixture(t, s.DB,
tf.WorkItems(1),
)
err := s.repo.Delete(s.Ctx, fxt.WorkItems[0].ID, fxt.Identities[0].ID)
require.Nil(t, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to add more assertion here? Other than just checking error, if it is possible to assert that there is no work item exists, that would be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed here 3d6aabb

})
}