mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-26 22:46:28 +00:00
# Which Problems Are Solved 1. Adding users to user groups and removing users from user groups. 2. Searching for users in user groups by group IDs or user IDs # How the Problems Are Solved By adding: 1. The API definitions to manage users in users groups 3. The command-layer implementation of adding users/removing users to/from user groups. 4. The projection table group_users1 5. Query-side implementation to search for users in user groups # Additional Changes 1. Remove debug statements from unit tests. 2. Fix removal of groups when orgs are removed 3. Add unit tests for groups projection # Additional Context * Related to #9702 * Follow-up for PRs * https://github.com/zitadel/zitadel/pull/10455 * https://github.com/zitadel/zitadel/pull/10758 * https://github.com/zitadel/zitadel/pull/10853
542 lines
13 KiB
Go
542 lines
13 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/repository/group"
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
func TestCommands_AddUsersToGroup(t *testing.T) {
|
|
t.Parallel()
|
|
filterErr := errors.New("filter error")
|
|
pushErr := errors.New("push error")
|
|
|
|
type fields struct {
|
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
|
checkPermission domain.PermissionCheck
|
|
}
|
|
type args struct {
|
|
groupID string
|
|
userIDs []string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want *domain.ObjectDetails
|
|
wantErr func(error) bool
|
|
}{
|
|
{
|
|
name: "failed to get group write model, error",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilterError(filterErr),
|
|
),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
wantErr: func(err error) bool {
|
|
return errors.Is(err, filterErr)
|
|
},
|
|
},
|
|
{
|
|
name: "group not found, error",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
wantErr: zerrors.IsPreconditionFailed,
|
|
},
|
|
{
|
|
name: "missing permission, error",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
)),
|
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
wantErr: zerrors.IsPermissionDenied,
|
|
},
|
|
{
|
|
name: "user not found, error",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
),
|
|
expectFilter(), // to get the user write model for user1
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
wantErr: zerrors.IsPreconditionFailed,
|
|
},
|
|
{
|
|
name: "some users already exist in the group, ok",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
eventFromEventPusher(
|
|
addNewGroupUsersAddedEvent("group1", "org1", []string{"user1"}),
|
|
),
|
|
),
|
|
expectFilter( // to get the user write model to check if user1 exists
|
|
eventFromEventPusher(
|
|
addNewUserEvent("user2", "org1"),
|
|
),
|
|
),
|
|
expectPush(
|
|
group.NewGroupUsersAddedEvent(context.Background(),
|
|
&group.NewAggregate("group1", "org1").Aggregate,
|
|
[]string{"user2"},
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
want: &domain.ObjectDetails{
|
|
ID: "group1",
|
|
ResourceOwner: "org1",
|
|
},
|
|
},
|
|
{
|
|
name: "all users already exist in the group, no events pushed, ok",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
eventFromEventPusher(
|
|
addNewGroupUsersAddedEvent("group1", "org1", []string{"user1", "user2"}),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
want: &domain.ObjectDetails{
|
|
ID: "group1",
|
|
ResourceOwner: "org1",
|
|
},
|
|
},
|
|
{
|
|
name: "failed to push events, error",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
),
|
|
expectFilter( // to get the user write model for user1
|
|
eventFromEventPusher(
|
|
addNewUserEvent("user1", "org1"),
|
|
),
|
|
),
|
|
expectPushFailed(
|
|
pushErr,
|
|
group.NewGroupUsersAddedEvent(context.Background(),
|
|
&group.NewAggregate("group1", "org1").Aggregate,
|
|
[]string{"user1"},
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1"},
|
|
},
|
|
wantErr: func(err error) bool {
|
|
return errors.Is(err, pushErr)
|
|
},
|
|
},
|
|
{
|
|
name: "all users added, ok",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
),
|
|
expectFilter( // to get the user write model for user1
|
|
eventFromEventPusher(
|
|
addNewUserEvent("user1", "org1"),
|
|
),
|
|
),
|
|
expectFilter( // to get the user write model for user2
|
|
eventFromEventPusher(
|
|
addNewUserEvent("user2", "org1"),
|
|
),
|
|
),
|
|
expectPush(
|
|
group.NewGroupUsersAddedEvent(context.Background(),
|
|
&group.NewAggregate("group1", "org1").Aggregate,
|
|
[]string{"user1", "user2"},
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
want: &domain.ObjectDetails{
|
|
ID: "group1",
|
|
ResourceOwner: "org1",
|
|
},
|
|
},
|
|
{
|
|
name: "all users added (with duplicate users), ok",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
),
|
|
expectFilter( // to get the user write model for user1
|
|
eventFromEventPusher(
|
|
addNewUserEvent("user1", "org1"),
|
|
),
|
|
),
|
|
expectFilter( // to get the user write model for user2
|
|
eventFromEventPusher(
|
|
addNewUserEvent("user2", "org1"),
|
|
),
|
|
),
|
|
expectPush(
|
|
group.NewGroupUsersAddedEvent(context.Background(),
|
|
&group.NewAggregate("group1", "org1").Aggregate,
|
|
[]string{"user1", "user2"},
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2", "user2", "user1"},
|
|
},
|
|
want: &domain.ObjectDetails{
|
|
ID: "group1",
|
|
ResourceOwner: "org1",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := &Commands{
|
|
eventstore: tt.fields.eventstore(t),
|
|
checkPermission: tt.fields.checkPermission,
|
|
}
|
|
got, err := c.AddUsersToGroup(context.Background(), tt.args.groupID, tt.args.userIDs)
|
|
if tt.wantErr != nil {
|
|
require.True(t, tt.wantErr(err))
|
|
return
|
|
}
|
|
assertObjectDetails(t, tt.want, got)
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func addNewGroupEvent(groupID, orgID string) *group.GroupAddedEvent {
|
|
return group.NewGroupAddedEvent(context.Background(),
|
|
&group.NewAggregate(groupID, orgID).Aggregate,
|
|
groupID,
|
|
"group description",
|
|
)
|
|
}
|
|
|
|
func TestCommands_RemoveUsersFromGroup(t *testing.T) {
|
|
t.Parallel()
|
|
filterErr := errors.New("filter error")
|
|
pushErr := errors.New("push error")
|
|
|
|
type fields struct {
|
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
|
checkPermission domain.PermissionCheck
|
|
}
|
|
type args struct {
|
|
groupID string
|
|
userIDs []string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want *domain.ObjectDetails
|
|
wantErr func(error) bool
|
|
}{
|
|
{
|
|
name: "failed to get group write model, error",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilterError(filterErr),
|
|
),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
wantErr: func(err error) bool {
|
|
return errors.Is(err, filterErr)
|
|
},
|
|
},
|
|
{
|
|
name: "group not found, error",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
wantErr: zerrors.IsPreconditionFailed,
|
|
},
|
|
{
|
|
name: "missing permission, error",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
)),
|
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
wantErr: zerrors.IsPermissionDenied,
|
|
},
|
|
{
|
|
name: "remove users, ok",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
eventFromEventPusher(
|
|
addNewGroupUsersAddedEvent("group1", "org1", []string{"user1", "user2"}),
|
|
),
|
|
),
|
|
expectPush(
|
|
group.NewGroupUsersRemovedEvent(context.Background(),
|
|
&group.NewAggregate("group1", "org1").Aggregate,
|
|
[]string{"user1", "user2"},
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2"},
|
|
},
|
|
want: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "group1",
|
|
},
|
|
},
|
|
{
|
|
name: "remove users (with duplicates), ok",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
eventFromEventPusher(
|
|
addNewGroupUsersAddedEvent("group1", "org1", []string{"user1", "user2"}),
|
|
),
|
|
),
|
|
expectPush(
|
|
group.NewGroupUsersRemovedEvent(context.Background(),
|
|
&group.NewAggregate("group1", "org1").Aggregate,
|
|
[]string{"user1", "user2"},
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1", "user2", "user1", "user2", "user1", "user2"},
|
|
},
|
|
want: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "group1",
|
|
},
|
|
},
|
|
{
|
|
name: "some users not in group, ok",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
eventFromEventPusher(
|
|
addNewGroupUsersAddedEvent("group1", "org1", []string{"user1", "user2"}),
|
|
),
|
|
),
|
|
expectPush(
|
|
group.NewGroupUsersRemovedEvent(context.Background(),
|
|
&group.NewAggregate("group1", "org1").Aggregate,
|
|
[]string{"user2"},
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user2", "user3"},
|
|
},
|
|
want: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "group1",
|
|
},
|
|
},
|
|
{
|
|
name: "all users not in group, ok",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
eventFromEventPusher(
|
|
addNewGroupUsersAddedEvent("group1", "org1", []string{"user1", "user2"}),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user3", "user4"},
|
|
},
|
|
want: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "group1",
|
|
},
|
|
},
|
|
{
|
|
name: "failed to push events, error",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter( // to get the group write model
|
|
eventFromEventPusher(
|
|
addNewGroupEvent("group1", "org1"),
|
|
),
|
|
eventFromEventPusher(
|
|
addNewGroupUsersAddedEvent("group1", "org1", []string{"user1", "user2"}),
|
|
),
|
|
),
|
|
expectPushFailed(
|
|
pushErr,
|
|
group.NewGroupUsersRemovedEvent(context.Background(),
|
|
&group.NewAggregate("group1", "org1").Aggregate,
|
|
[]string{"user1"},
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args: args{
|
|
groupID: "group1",
|
|
userIDs: []string{"user1"},
|
|
},
|
|
wantErr: func(err error) bool {
|
|
return errors.Is(err, pushErr)
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
c := &Commands{
|
|
eventstore: tt.fields.eventstore(t),
|
|
checkPermission: tt.fields.checkPermission,
|
|
}
|
|
got, err := c.RemoveUsersFromGroup(context.Background(), tt.args.groupID, tt.args.userIDs)
|
|
if tt.wantErr != nil {
|
|
require.True(t, tt.wantErr(err))
|
|
return
|
|
}
|
|
assertObjectDetails(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func addNewGroupUsersAddedEvent(groupID, orgID string, userIds []string) *group.GroupUsersAddedEvent {
|
|
return group.NewGroupUsersAddedEvent(context.Background(),
|
|
&group.NewAggregate(groupID, orgID).Aggregate,
|
|
userIds,
|
|
)
|
|
}
|
|
|
|
func addNewUserEvent(userID, orgID string) *user.HumanAddedEvent {
|
|
return user.NewHumanAddedEvent(context.Background(),
|
|
&user.NewAggregate(userID, orgID).Aggregate,
|
|
"username",
|
|
"firstname",
|
|
"lastname",
|
|
"nickname",
|
|
"displayname",
|
|
AllowedLanguage,
|
|
domain.GenderUnspecified,
|
|
"email@test.ch",
|
|
true,
|
|
)
|
|
}
|