SMQ-2924 - Allow bulk deleting of invitations from repository (#2942)

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
This commit is contained in:
Felix Gateru
2025-09-05 20:26:45 +03:00
committed by GitHub
parent c50221a042
commit a70b838f1c
6 changed files with 89 additions and 65 deletions
+2 -2
View File
@@ -263,8 +263,8 @@ type Repository interface {
// UpdateRejection updates an invitation by setting the rejection time.
UpdateRejection(ctx context.Context, invitation Invitation) (err error)
// Delete deletes an invitation.
DeleteInvitation(ctx context.Context, userID, domainID string) (err error)
// DeleteUsersInvitations deletes invitation to a provided domain for users with provided user IDs.
DeleteUsersInvitations(ctx context.Context, domainID string, userID ...string) (err error)
roles.Repository
}
+28 -19
View File
@@ -167,37 +167,44 @@ func (_c *Repository_DeleteDomain_Call) RunAndReturn(run func(ctx context.Contex
return _c
}
// DeleteInvitation provides a mock function for the type Repository
func (_mock *Repository) DeleteInvitation(ctx context.Context, userID string, domainID string) error {
ret := _mock.Called(ctx, userID, domainID)
// DeleteUsersInvitations provides a mock function for the type Repository
func (_mock *Repository) DeleteUsersInvitations(ctx context.Context, domainID string, userID ...string) error {
var tmpRet mock.Arguments
if len(userID) > 0 {
tmpRet = _mock.Called(ctx, domainID, userID)
} else {
tmpRet = _mock.Called(ctx, domainID)
}
ret := tmpRet
if len(ret) == 0 {
panic("no return value specified for DeleteInvitation")
panic("no return value specified for DeleteUsersInvitations")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
r0 = returnFunc(ctx, userID, domainID)
if returnFunc, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok {
r0 = returnFunc(ctx, domainID, userID...)
} else {
r0 = ret.Error(0)
}
return r0
}
// Repository_DeleteInvitation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteInvitation'
type Repository_DeleteInvitation_Call struct {
// Repository_DeleteUsersInvitations_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteUsersInvitations'
type Repository_DeleteUsersInvitations_Call struct {
*mock.Call
}
// DeleteInvitation is a helper method to define mock.On call
// DeleteUsersInvitations is a helper method to define mock.On call
// - ctx context.Context
// - userID string
// - domainID string
func (_e *Repository_Expecter) DeleteInvitation(ctx interface{}, userID interface{}, domainID interface{}) *Repository_DeleteInvitation_Call {
return &Repository_DeleteInvitation_Call{Call: _e.mock.On("DeleteInvitation", ctx, userID, domainID)}
// - userID ...string
func (_e *Repository_Expecter) DeleteUsersInvitations(ctx interface{}, domainID interface{}, userID ...interface{}) *Repository_DeleteUsersInvitations_Call {
return &Repository_DeleteUsersInvitations_Call{Call: _e.mock.On("DeleteUsersInvitations",
append([]interface{}{ctx, domainID}, userID...)...)}
}
func (_c *Repository_DeleteInvitation_Call) Run(run func(ctx context.Context, userID string, domainID string)) *Repository_DeleteInvitation_Call {
func (_c *Repository_DeleteUsersInvitations_Call) Run(run func(ctx context.Context, domainID string, userID ...string)) *Repository_DeleteUsersInvitations_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
@@ -207,25 +214,27 @@ func (_c *Repository_DeleteInvitation_Call) Run(run func(ctx context.Context, us
if args[1] != nil {
arg1 = args[1].(string)
}
var arg2 string
if args[2] != nil {
arg2 = args[2].(string)
var arg2 []string
var variadicArgs []string
if len(args) > 2 {
variadicArgs = args[2].([]string)
}
arg2 = variadicArgs
run(
arg0,
arg1,
arg2,
arg2...,
)
})
return _c
}
func (_c *Repository_DeleteInvitation_Call) Return(err error) *Repository_DeleteInvitation_Call {
func (_c *Repository_DeleteUsersInvitations_Call) Return(err error) *Repository_DeleteUsersInvitations_Call {
_c.Call.Return(err)
return _c
}
func (_c *Repository_DeleteInvitation_Call) RunAndReturn(run func(ctx context.Context, userID string, domainID string) error) *Repository_DeleteInvitation_Call {
func (_c *Repository_DeleteUsersInvitations_Call) RunAndReturn(run func(ctx context.Context, domainID string, userID ...string) error) *Repository_DeleteUsersInvitations_Call {
_c.Call.Return(run)
return _c
}
+11 -3
View File
@@ -150,10 +150,18 @@ func (repo domainRepo) UpdateRejection(ctx context.Context, invitation domains.I
return nil
}
func (repo domainRepo) DeleteInvitation(ctx context.Context, inviteeUserID, domain string) (err error) {
q := `DELETE FROM invitations WHERE invitee_user_id = $1 AND domain_id = $2`
func (repo domainRepo) DeleteUsersInvitations(ctx context.Context, domain string, inviteeUserIDs ...string) (err error) {
if len(inviteeUserIDs) == 0 {
return repoerr.ErrNotFound
}
result, err := repo.db.ExecContext(ctx, q, inviteeUserID, domain)
q := `DELETE FROM invitations WHERE domain_id = :domain_id AND invitee_user_id = ANY(:invitee_user_ids);`
params := map[string]any{
"invitee_user_ids": inviteeUserIDs,
"domain_id": domain,
}
result, err := repo.db.NamedExecContext(ctx, q, params)
if err != nil {
return postgres.HandleError(repoerr.ErrRemoveEntity, err)
}
+40 -30
View File
@@ -761,7 +761,7 @@ func TestUpdateRejection(t *testing.T) {
}
}
func TestDeleteInvitation(t *testing.T) {
func TestDeleteUsersInvitations(t *testing.T) {
t.Cleanup(func() {
_, err := db.Exec("DELETE FROM invitations")
require.Nil(t, err, fmt.Sprintf("clean invitations unexpected error: %s", err))
@@ -772,48 +772,58 @@ func TestDeleteInvitation(t *testing.T) {
dom := saveDomain(t, repo)
invitation := domains.Invitation{
InvitedBy: testsutil.GenerateUUID(t),
InviteeUserID: testsutil.GenerateUUID(t),
DomainID: dom.ID,
DomainName: dom.Name,
RoleID: testsutil.GenerateUUID(t),
RoleName: roleName,
CreatedAt: time.Now(),
num := 10
items := make([]domains.Invitation, 0, num)
for i := 0; i < num; i++ {
invitation := domains.Invitation{
InvitedBy: testsutil.GenerateUUID(t),
InviteeUserID: testsutil.GenerateUUID(t),
DomainID: dom.ID,
DomainName: dom.Name,
RoleID: testsutil.GenerateUUID(t),
RoleName: roleName,
CreatedAt: time.Now(),
}
err := repo.SaveInvitation(context.Background(), invitation)
require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err))
items = append(items, invitation)
}
err := repo.SaveInvitation(context.Background(), invitation)
require.Nil(t, err, fmt.Sprintf("create invitation unexpected error: %s", err))
cases := []struct {
desc string
invitation domains.Invitation
err error
desc string
domainID string
userIDs []string
err error
}{
{
desc: "delete invitation successfully",
invitation: domains.Invitation{
InviteeUserID: invitation.InviteeUserID,
DomainID: invitation.DomainID,
},
err: nil,
desc: "delete one invitation successfully",
domainID: dom.ID,
userIDs: []string{items[0].InviteeUserID},
err: nil,
},
{
desc: "delete invitation with invalid invitation id",
invitation: domains.Invitation{
InviteeUserID: testsutil.GenerateUUID(t),
DomainID: testsutil.GenerateUUID(t),
},
err: repoerr.ErrNotFound,
desc: "delete multiple invitations successfully",
domainID: dom.ID,
userIDs: []string{items[1].InviteeUserID, items[2].InviteeUserID, items[3].InviteeUserID},
err: nil,
},
{
desc: "delete invitation with empty invitation id",
invitation: domains.Invitation{},
err: repoerr.ErrNotFound,
desc: "delete invitation with invalid invitation id",
domainID: dom.ID,
userIDs: []string{testsutil.GenerateUUID(t)},
err: repoerr.ErrNotFound,
},
{
desc: "delete invitation with empty user id",
domainID: dom.ID,
userIDs: []string{},
err: repoerr.ErrNotFound,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := repo.DeleteInvitation(context.Background(), tc.invitation.InviteeUserID, tc.invitation.DomainID)
err := repo.DeleteUsersInvitations(context.Background(), tc.domainID, tc.userIDs...)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
})
}
+7 -10
View File
@@ -306,7 +306,7 @@ func (svc *service) RejectInvitation(ctx context.Context, session authn.Session,
func (svc *service) DeleteInvitation(ctx context.Context, session authn.Session, inviteeUserID, domainID string) error {
if session.UserID == inviteeUserID {
if err := svc.repo.DeleteInvitation(ctx, inviteeUserID, domainID); err != nil {
if err := svc.repo.DeleteUsersInvitations(ctx, domainID, inviteeUserID); err != nil {
return errors.Wrap(svcerr.ErrRemoveEntity, err)
}
return nil
@@ -325,7 +325,7 @@ func (svc *service) DeleteInvitation(ctx context.Context, session authn.Session,
return errors.Wrap(svcerr.ErrRemoveEntity, svcerr.ErrInvitationAlreadyRejected)
}
if err := svc.repo.DeleteInvitation(ctx, inviteeUserID, domainID); err != nil {
if err := svc.repo.DeleteUsersInvitations(ctx, domainID, inviteeUserID); err != nil {
return errors.Wrap(svcerr.ErrRemoveEntity, err)
}
@@ -334,11 +334,10 @@ func (svc *service) DeleteInvitation(ctx context.Context, session authn.Session,
// Add addition removal of user from invitations.
func (svc *service) RemoveEntityMembers(ctx context.Context, session authn.Session, entityID string, members []string) error {
for _, member := range members {
if err := svc.repo.DeleteInvitation(ctx, member, entityID); err != nil && err != repoerr.ErrNotFound {
return err
}
if err := svc.repo.DeleteUsersInvitations(ctx, entityID, members...); err != nil && err != repoerr.ErrNotFound {
return err
}
return svc.ProvisionManageService.RemoveEntityMembers(ctx, session, entityID, members)
}
@@ -358,10 +357,8 @@ func (svc *service) RoleRemoveMembers(ctx context.Context, session authn.Session
}
}
for _, memberID := range members {
if err := svc.repo.DeleteInvitation(ctx, memberID, entityID); err != nil && err != repoerr.ErrNotFound {
return err
}
if err := svc.repo.DeleteUsersInvitations(ctx, entityID, members...); err != nil && err != repoerr.ErrNotFound {
return err
}
return svc.ProvisionManageService.RoleRemoveMembers(ctx, session, entityID, roleID, members)
+1 -1
View File
@@ -1114,7 +1114,7 @@ func TestDeleteInvitation(t *testing.T) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := drepo.On("RetrieveInvitation", context.Background(), mock.Anything, mock.Anything).Return(tc.resp, tc.retrieveInvitationErr)
repoCall1 := drepo.On("DeleteInvitation", context.Background(), mock.Anything, mock.Anything).Return(tc.deleteInvitationErr)
repoCall1 := drepo.On("DeleteUsersInvitations", context.Background(), mock.Anything, mock.Anything).Return(tc.deleteInvitationErr)
err := svc.DeleteInvitation(context.Background(), tc.session, tc.userID, tc.domainID)
assert.True(t, errors.Contains(err, tc.err))
repoCall.Unset()