// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package storetest import ( "errors" "fmt" "math" "slices" "sort" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/request" "github.com/mattermost/mattermost/server/v8/channels/store" ) func TestGroupStore(t *testing.T, rctx request.CTX, ss store.Store) { t.Run("Create", func(t *testing.T) { testGroupStoreCreate(t, rctx, ss) }) t.Run("CreateWithUserIds", func(t *testing.T) { testGroupCreateWithUserIds(t, rctx, ss) }) t.Run("Get", func(t *testing.T) { testGroupStoreGet(t, rctx, ss) }) t.Run("GetByName", func(t *testing.T) { testGroupStoreGetByName(t, rctx, ss) }) t.Run("GetByIDs", func(t *testing.T) { testGroupStoreGetByIDs(t, rctx, ss) }) t.Run("GetByRemoteID", func(t *testing.T) { testGroupStoreGetByRemoteID(t, rctx, ss) }) t.Run("GetAllBySource", func(t *testing.T) { testGroupAllBySource(t, rctx, ss) }) t.Run("GetByUser", func(t *testing.T) { testGroupStoreGetByUser(t, rctx, ss) }) t.Run("Update", func(t *testing.T) { testGroupStoreUpdate(t, rctx, ss) }) t.Run("Delete", func(t *testing.T) { testGroupStoreDelete(t, rctx, ss) }) t.Run("Restore", func(t *testing.T) { testGroupStoreRestore(t, rctx, ss) }) t.Run("ToModelChannelAssociations", func(t *testing.T) { testGroupStoreToModelChannelAssociations(t, rctx, ss) }) t.Run("GetMemberUsers", func(t *testing.T) { testGroupGetMemberUsers(t, rctx, ss) }) t.Run("GetMemberUsersPage", func(t *testing.T) { testGroupGetMemberUsersPage(t, rctx, ss) }) t.Run("GetMemberUsersSortedPage", func(t *testing.T) { testGroupGetMemberUsersSortedPage(t, rctx, ss) }) t.Run("GetMemberUsersInTeam", func(t *testing.T) { testGroupGetMemberUsersInTeam(t, rctx, ss) }) t.Run("GetMemberUsersNotInChannel", func(t *testing.T) { testGroupGetMemberUsersNotInChannel(t, rctx, ss) }) t.Run("UpsertMember", func(t *testing.T) { testUpsertMember(t, rctx, ss) }) t.Run("UpsertMembers", func(t *testing.T) { testUpsertMembers(t, rctx, ss) }) t.Run("DeleteMember", func(t *testing.T) { testGroupDeleteMember(t, rctx, ss) }) t.Run("DeleteMembers", func(t *testing.T) { testGroupDeleteMembers(t, rctx, ss) }) t.Run("PermanentDeleteMembersByUser", func(t *testing.T) { testGroupPermanentDeleteMembersByUser(t, rctx, ss) }) t.Run("CreateGroupSyncable", func(t *testing.T) { testCreateGroupSyncable(t, rctx, ss) }) t.Run("GetGroupSyncable", func(t *testing.T) { testGetGroupSyncable(t, rctx, ss) }) t.Run("GetGroupSyncableErrors", func(t *testing.T) { testGetGroupSyncableErrors(t, rctx, ss) }) t.Run("GetAllGroupSyncablesByGroupId", func(t *testing.T) { testGetAllGroupSyncablesByGroup(t, rctx, ss) }) t.Run("UpdateGroupSyncable", func(t *testing.T) { testUpdateGroupSyncable(t, rctx, ss) }) t.Run("DeleteGroupSyncable", func(t *testing.T) { testDeleteGroupSyncable(t, rctx, ss) }) t.Run("TeamMembersToAdd", func(t *testing.T) { testTeamMembersToAdd(t, rctx, ss) }) t.Run("TeamMembersToAdd_SingleTeam", func(t *testing.T) { testTeamMembersToAddSingleTeam(t, rctx, ss) }) t.Run("ChannelMembersToAdd", func(t *testing.T) { testChannelMembersToAdd(t, rctx, ss) }) t.Run("ChannelMembersToAdd_SingleChannel", func(t *testing.T) { testChannelMembersToAddSingleChannel(t, rctx, ss) }) t.Run("TeamMembersToRemove", func(t *testing.T) { testTeamMembersToRemove(t, rctx, ss) }) t.Run("TeamMembersToRemove_SingleTeam", func(t *testing.T) { testTeamMembersToRemoveSingleTeam(t, rctx, ss) }) t.Run("ChannelMembersToRemove", func(t *testing.T) { testChannelMembersToRemove(t, rctx, ss) }) t.Run("ChannelMembersToRemove_SingleChannel", func(t *testing.T) { testChannelMembersToRemoveSingleChannel(t, rctx, ss) }) t.Run("GetGroupsByChannel", func(t *testing.T) { testGetGroupsByChannel(t, rctx, ss) }) t.Run("GetGroupsAssociatedToChannelsByTeam", func(t *testing.T) { testGetGroupsAssociatedToChannelsByTeam(t, rctx, ss) }) t.Run("GetGroupsByTeam", func(t *testing.T) { testGetGroupsByTeam(t, rctx, ss) }) t.Run("GetGroups", func(t *testing.T) { testGetGroups(t, rctx, ss) }) t.Run("TeamMembersMinusGroupMembers", func(t *testing.T) { testTeamMembersMinusGroupMembers(t, rctx, ss) }) t.Run("ChannelMembersMinusGroupMembers", func(t *testing.T) { testChannelMembersMinusGroupMembers(t, rctx, ss) }) t.Run("CountMembersMinusGroupMembers", func(t *testing.T) { testCountMembersMinusGroupMembers(t, rctx, ss) }) t.Run("GetMemberCount", func(t *testing.T) { groupTestGetMemberCount(t, rctx, ss) }) t.Run("AdminRoleGroupsForSyncableMember_Channel", func(t *testing.T) { groupTestAdminRoleGroupsForSyncableMemberChannel(t, rctx, ss) }) t.Run("AdminRoleGroupsForSyncableMember_Team", func(t *testing.T) { groupTestAdminRoleGroupsForSyncableMemberTeam(t, rctx, ss) }) t.Run("PermittedSyncableAdmins_Team", func(t *testing.T) { groupTestPermittedSyncableAdminsTeam(t, rctx, ss) }) t.Run("PermittedSyncableAdmins_Channel", func(t *testing.T) { groupTestPermittedSyncableAdminsChannel(t, rctx, ss) }) t.Run("UpdateMembersRole_Team", func(t *testing.T) { groupTestUpdateMembersRoleTeam(t, rctx, ss) }) t.Run("UpdateMembersRole_Channel", func(t *testing.T) { groupTestpUpdateMembersRoleChannel(t, rctx, ss) }) t.Run("GroupCount", func(t *testing.T) { groupTestGroupCount(t, rctx, ss) }) t.Run("GroupTeamCount", func(t *testing.T) { groupTestGroupTeamCount(t, rctx, ss) }) t.Run("GroupChannelCount", func(t *testing.T) { groupTestGroupChannelCount(t, rctx, ss) }) t.Run("GroupMemberCount", func(t *testing.T) { groupTestGroupMemberCount(t, rctx, ss) }) t.Run("DistinctGroupMemberCount", func(t *testing.T) { groupTestDistinctGroupMemberCount(t, rctx, ss) }) t.Run("GroupCountWithAllowReference", func(t *testing.T) { groupTestGroupCountWithAllowReference(t, rctx, ss) }) t.Run("GetMember", func(t *testing.T) { groupTestGetMember(t, rctx, ss) }) t.Run("GetNonMemberUsersPage", func(t *testing.T) { groupTestGetNonMemberUsersPage(t, rctx, ss) }) t.Run("DistinctGroupMemberCountForSource", func(t *testing.T) { groupTestDistinctGroupMemberCountForSource(t, rctx, ss) }) t.Run("GroupCountBySource", func(t *testing.T) { groupTestGroupCountBySource(t, rctx, ss) }) } func testGroupStoreCreate(t *testing.T, rctx request.CTX, ss store.Store) { // Save a new group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } // Happy path d1, err := ss.Group().Create(g1) require.NoError(t, err) require.Len(t, d1.Id, 26) require.Equal(t, *g1.Name, *d1.Name) require.Equal(t, g1.DisplayName, d1.DisplayName) require.Equal(t, g1.Description, d1.Description) require.Equal(t, g1.RemoteId, d1.RemoteId) require.NotZero(t, d1.CreateAt) require.NotZero(t, d1.UpdateAt) require.Zero(t, d1.DeleteAt) // Requires display name g2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "", Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } data, err := ss.Group().Create(g2) require.Nil(t, data) require.Error(t, err) var appErr *model.AppError require.True(t, errors.As(err, &appErr)) require.Equal(t, appErr.Id, "model.group.display_name.app_error") // Won't accept a duplicate name g4 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } _, err = ss.Group().Create(g4) require.NoError(t, err) g4b := &model.Group{ Name: g4.Name, DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } data, err = ss.Group().Create(g4b) require.Nil(t, data) require.Error(t, err) require.Contains(t, err.Error(), fmt.Sprintf("Group with name %s already exists", *g4b.Name)) // Fields cannot be greater than max values g5 := &model.Group{ Name: model.NewPointer(strings.Repeat("x", model.GroupNameMaxLength)), DisplayName: strings.Repeat("x", model.GroupDisplayNameMaxLength), Description: strings.Repeat("x", model.GroupDescriptionMaxLength), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } require.Nil(t, g5.IsValidForCreate()) g5.Name = model.NewPointer(*g5.Name + "x") require.Equal(t, g5.IsValidForCreate().Id, "model.group.name.invalid_length.app_error") g5.Name = model.NewPointer(model.NewId()) require.Nil(t, g5.IsValidForCreate()) g5.DisplayName = g5.DisplayName + "x" require.Equal(t, g5.IsValidForCreate().Id, "model.group.display_name.app_error") g5.DisplayName = model.NewId() require.Nil(t, g5.IsValidForCreate()) g5.Description = g5.Description + "x" require.Equal(t, g5.IsValidForCreate().Id, "model.group.description.app_error") g5.Description = model.NewId() require.Nil(t, g5.IsValidForCreate()) // Must use a valid type g6 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSource("fake"), RemoteId: model.NewPointer(model.NewId()), } require.Equal(t, g6.IsValidForCreate().Id, "model.group.source.app_error") // must use valid characters g7 := &model.Group{ Name: model.NewPointer("%^#@$$"), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } require.Equal(t, g7.IsValidForCreate().Id, "model.group.name.invalid_chars.app_error") } func testGroupCreateWithUserIds(t *testing.T, rctx request.CTX, ss store.Store) { // Create user 1 u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) // Create user 2 u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, nErr := ss.User().Save(rctx, u2) require.NoError(t, nErr) g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceCustom, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } // Save a new group guids1 := &model.GroupWithUserIds{ Group: *g1, UserIds: []string{user1.Id, user2.Id}, } // Happy path d1, err := ss.Group().CreateWithUserIds(guids1) require.NoError(t, err) require.Len(t, d1.Id, 26) require.Equal(t, *guids1.Name, *d1.Name) require.Equal(t, guids1.DisplayName, d1.DisplayName) require.Equal(t, guids1.Description, d1.Description) require.Equal(t, guids1.RemoteId, d1.RemoteId) require.NotZero(t, d1.CreateAt) require.NotZero(t, d1.UpdateAt) require.Zero(t, d1.DeleteAt) require.Equal(t, *model.NewPointer(2), *d1.MemberCount) // Requires display name g2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "", Source: model.GroupSourceCustom, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } guids2 := &model.GroupWithUserIds{ Group: *g2, UserIds: []string{user1.Id, user2.Id}, } data, err := ss.Group().CreateWithUserIds(guids2) require.Nil(t, data) require.Error(t, err) var appErr *model.AppError require.True(t, errors.As(err, &appErr)) require.Equal(t, appErr.Id, "model.group.display_name.app_error") // Won't accept a duplicate name g4 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), } guids4 := &model.GroupWithUserIds{ Group: *g4, UserIds: []string{user1.Id, user2.Id}, } _, err = ss.Group().CreateWithUserIds(guids4) require.NoError(t, err) g4b := &model.Group{ Name: g4.Name, DisplayName: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), } guids4b := &model.GroupWithUserIds{ Group: *g4b, UserIds: []string{user1.Id}, } data, err = ss.Group().CreateWithUserIds(guids4b) require.Nil(t, data) require.Error(t, err) require.Contains(t, err.Error(), "unique constraint: Name") // Fields cannot be greater than max values g5 := &model.Group{ Name: model.NewPointer(strings.Repeat("x", model.GroupNameMaxLength)), DisplayName: strings.Repeat("x", model.GroupDisplayNameMaxLength), Description: strings.Repeat("x", model.GroupDescriptionMaxLength), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), } guids5 := &model.GroupWithUserIds{ Group: *g5, } require.Nil(t, guids5.IsValidForCreate()) guids5.Name = model.NewPointer(*guids5.Name + "x") require.Equal(t, guids5.IsValidForCreate().Id, "model.group.name.invalid_length.app_error") guids5.Name = model.NewPointer(model.NewId()) require.Nil(t, guids5.IsValidForCreate()) guids5.DisplayName = guids5.DisplayName + "x" require.Equal(t, guids5.IsValidForCreate().Id, "model.group.display_name.app_error") guids5.DisplayName = model.NewId() require.Nil(t, guids5.IsValidForCreate()) guids5.Description = guids5.Description + "x" require.Equal(t, guids5.IsValidForCreate().Id, "model.group.description.app_error") guids5.Description = model.NewId() require.Nil(t, guids5.IsValidForCreate()) // Must use a valid type g6 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSource("fake"), RemoteId: model.NewPointer(model.NewId()), } guids6 := &model.GroupWithUserIds{ Group: *g6, } require.Equal(t, guids6.IsValidForCreate().Id, "model.group.source.app_error") // must use valid characters g7 := &model.Group{ Name: model.NewPointer("%^#@$$"), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), } guids7 := &model.GroupWithUserIds{ Group: *g7, } require.Equal(t, guids7.IsValidForCreate().Id, "model.group.name.invalid_chars.app_error") // Invalid user ids g8 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), } guids8 := &model.GroupWithUserIds{ Group: *g8, UserIds: []string{"1234uid"}, } data, err = ss.Group().CreateWithUserIds(guids8) require.Nil(t, data) require.Error(t, err) require.Equal(t, store.NewErrNotFound("User", "1234uid"), err) } func testGroupStoreGet(t *testing.T, rctx request.CTX, ss store.Store) { // Create a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } d1, err := ss.Group().Create(g1) require.NoError(t, err) require.Len(t, d1.Id, 26) // Get the group d2, err := ss.Group().Get(d1.Id) require.NoError(t, err) require.Equal(t, d1.Id, d2.Id) require.Equal(t, *d1.Name, *d2.Name) require.Equal(t, d1.DisplayName, d2.DisplayName) require.Equal(t, d1.Description, d2.Description) require.Equal(t, d1.RemoteId, d2.RemoteId) require.Equal(t, d1.CreateAt, d2.CreateAt) require.Equal(t, d1.UpdateAt, d2.UpdateAt) require.Equal(t, d1.DeleteAt, d2.DeleteAt) // Get an invalid group _, err = ss.Group().Get(model.NewId()) require.Error(t, err) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) } func testGroupStoreGetByName(t *testing.T, rctx request.CTX, ss store.Store) { // Create a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } g1Opts := model.GroupSearchOpts{ FilterAllowReference: false, } d1, err := ss.Group().Create(g1) require.NoError(t, err) require.Len(t, d1.Id, 26) // Get the group d2, err := ss.Group().GetByName(*d1.Name, g1Opts) require.NoError(t, err) require.Equal(t, d1.Id, d2.Id) require.Equal(t, *d1.Name, *d2.Name) require.Equal(t, d1.DisplayName, d2.DisplayName) require.Equal(t, d1.Description, d2.Description) require.Equal(t, d1.RemoteId, d2.RemoteId) require.Equal(t, d1.CreateAt, d2.CreateAt) require.Equal(t, d1.UpdateAt, d2.UpdateAt) require.Equal(t, d1.DeleteAt, d2.DeleteAt) // Get an invalid group _, err = ss.Group().GetByName(model.NewId(), g1Opts) require.Error(t, err) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) } func testGroupStoreGetByIDs(t *testing.T, rctx request.CTX, ss store.Store) { var group1 *model.Group var group2 *model.Group for i := range 2 { group := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(group) require.NoError(t, err) switch i { case 0: group1 = group case 1: group2 = group } } groups, err := ss.Group().GetByIDs([]string{group1.Id, group2.Id}) require.NoError(t, err) require.Len(t, groups, 2) for i := range 2 { require.True(t, (groups[i].Id == group1.Id || groups[i].Id == group2.Id)) } require.True(t, groups[0].Id != groups[1].Id) } func testGroupStoreGetByRemoteID(t *testing.T, rctx request.CTX, ss store.Store) { // Create a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } d1, err := ss.Group().Create(g1) require.NoError(t, err) require.Len(t, d1.Id, 26) // Get the group d2, err := ss.Group().GetByRemoteID(*d1.RemoteId, model.GroupSourceLdap) require.NoError(t, err) require.Equal(t, d1.Id, d2.Id) require.Equal(t, *d1.Name, *d2.Name) require.Equal(t, d1.DisplayName, d2.DisplayName) require.Equal(t, d1.Description, d2.Description) require.Equal(t, d1.RemoteId, d2.RemoteId) require.Equal(t, d1.CreateAt, d2.CreateAt) require.Equal(t, d1.UpdateAt, d2.UpdateAt) require.Equal(t, d1.DeleteAt, d2.DeleteAt) // Get an invalid group _, err = ss.Group().GetByRemoteID(model.NewId(), model.GroupSource("fake")) require.Error(t, err) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) } func testGroupAllBySource(t *testing.T, rctx request.CTX, ss store.Store) { // Create groups with different sources g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), } customGroup, err := ss.Group().Create(g1) require.NoError(t, err) defer ss.Group().Delete(customGroup.Id) g2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } ldapGroup, err := ss.Group().Create(g2) require.NoError(t, err) defer ss.Group().Delete(ldapGroup.Id) g3 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } ldapGroup2, err := ss.Group().Create(g3) require.NoError(t, err) defer ss.Group().Delete(ldapGroup2.Id) // Test filtering by LDAP source ldapGroups, err := ss.Group().GetAllBySource(model.GroupSourceLdap) require.NoError(t, err) // Verify we got at least the 2 LDAP groups we created found1, found2 := false, false for _, group := range ldapGroups { if group.Id == ldapGroup.Id { found1 = true } if group.Id == ldapGroup2.Id { found2 = true } // Make sure all returned groups are LDAP source require.Equal(t, model.GroupSourceLdap, group.Source) } require.True(t, found1, "Failed to find the first LDAP group") require.True(t, found2, "Failed to find the second LDAP group") // Test filtering by Custom source customGroups, err := ss.Group().GetAllBySource(model.GroupSourceCustom) require.NoError(t, err) // Verify we got at least the custom group we created foundCustom := false for _, group := range customGroups { if group.Id == customGroup.Id { foundCustom = true } // Make sure all returned groups are Custom source require.Equal(t, model.GroupSourceCustom, group.Source) } require.True(t, foundCustom, "Failed to find the custom group") // Test with deleted group to ensure it's not returned _, err = ss.Group().Delete(ldapGroup2.Id) require.NoError(t, err) ldapGroupsAfterDelete, err := ss.Group().GetAllBySource(model.GroupSourceLdap) require.NoError(t, err) // Verify the deleted group is not returned for _, group := range ldapGroupsAfterDelete { require.NotEqual(t, ldapGroup2.Id, group.Id, "Deleted group should not be returned") } } func testGroupStoreGetByUser(t *testing.T, rctx request.CTX, ss store.Store) { // Save a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } g1, err := ss.Group().Create(g1) require.NoError(t, err) g2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), AllowReference: true, } g2, err = ss.Group().Create(g2) require.NoError(t, err) u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } u1, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(g1.Id, u1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(g2.Id, u1.Id) require.NoError(t, err) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } u2, nErr = ss.User().Save(rctx, u2) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(g2.Id, u2.Id) require.NoError(t, err) groups, err := ss.Group().GetByUser(u1.Id, model.GroupSearchOpts{}) require.NoError(t, err) assert.Equal(t, 2, len(groups)) found1 := false found2 := false for _, g := range groups { if g.Id == g1.Id { found1 = true } if g.Id == g2.Id { found2 = true } } assert.True(t, found1) assert.True(t, found2) groups, err = ss.Group().GetByUser(u2.Id, model.GroupSearchOpts{}) require.NoError(t, err) require.Equal(t, 1, len(groups)) assert.Equal(t, g2.Id, groups[0].Id) groups, err = ss.Group().GetByUser(model.NewId(), model.GroupSearchOpts{}) require.NoError(t, err) assert.Equal(t, 0, len(groups)) groups, err = ss.Group().GetByUser(u1.Id, model.GroupSearchOpts{FilterAllowReference: true}) require.NoError(t, err) assert.Equal(t, 1, len(groups)) assert.Equal(t, g2.Id, groups[0].Id) } func testGroupStoreUpdate(t *testing.T, rctx request.CTX, ss store.Store) { // Save a new group g1 := &model.Group{ Name: model.NewPointer("g1-test"), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } // Create a group d1, err := ss.Group().Create(g1) require.NoError(t, err) // Update happy path g1Update := &model.Group{} *g1Update = *g1 g1Update.Name = model.NewPointer(model.NewId()) g1Update.DisplayName = model.NewId() g1Update.Description = model.NewId() g1Update.RemoteId = model.NewPointer(model.NewId()) ud1, err := ss.Group().Update(g1Update) require.NoError(t, err) // Not changed... require.Equal(t, d1.Id, ud1.Id) require.Equal(t, d1.CreateAt, ud1.CreateAt) require.Equal(t, d1.Source, ud1.Source) // Still zero... require.Zero(t, ud1.DeleteAt) // Updated... require.Equal(t, *g1Update.Name, *ud1.Name) require.Equal(t, g1Update.DisplayName, ud1.DisplayName) require.Equal(t, g1Update.Description, ud1.Description) require.Equal(t, g1Update.RemoteId, ud1.RemoteId) // Requires display name data, err := ss.Group().Update(&model.Group{ Id: d1.Id, Name: model.NewPointer(model.NewId()), DisplayName: "", Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.Nil(t, data) require.Error(t, err) var appErr *model.AppError require.True(t, errors.As(err, &appErr)) require.Equal(t, appErr.Id, "model.group.display_name.app_error") // Create another Group g2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } d2, err := ss.Group().Create(g2) require.NoError(t, err) // Can't update the name to be a duplicate of an existing group's name _, err = ss.Group().Update(&model.Group{ Id: d2.Id, Name: g1Update.Name, DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), }) require.Error(t, err) require.Contains(t, err.Error(), "unique constraint: Name") // Cannot update CreateAt someVal := model.GetMillis() d1.CreateAt = someVal d3, err := ss.Group().Update(d1) require.NoError(t, err) require.NotEqual(t, someVal, d3.CreateAt) // Cannot update DeleteAt to non-zero d1.DeleteAt = 1 _, err = ss.Group().Update(d1) require.Error(t, err) require.Contains(t, err.Error(), "DeleteAt should be 0 when updating") //...except for 0 for DeleteAt d1.DeleteAt = 0 d4, err := ss.Group().Update(d1) require.NoError(t, err) require.Zero(t, d4.DeleteAt) } func testGroupStoreDelete(t *testing.T, rctx request.CTX, ss store.Store) { // Save a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } d1, err := ss.Group().Create(g1) require.NoError(t, err) require.Len(t, d1.Id, 26) // Check the group is retrievable _, err = ss.Group().Get(d1.Id) require.NoError(t, err) // Get the before count d7, err := ss.Group().GetAllBySource(model.GroupSourceLdap) require.NoError(t, err) beforeCount := len(d7) // Delete the group _, err = ss.Group().Delete(d1.Id) require.NoError(t, err) // Check the group is deleted d4, err := ss.Group().Get(d1.Id) require.NoError(t, err) require.NotZero(t, d4.DeleteAt) // Check the after count d5, err := ss.Group().GetAllBySource(model.GroupSourceLdap) require.NoError(t, err) afterCount := len(d5) require.Condition(t, func() bool { return beforeCount == afterCount+1 }, beforeCount, "==", afterCount+1) // Try and delete a nonexistent group _, err = ss.Group().Delete(model.NewId()) require.Error(t, err) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) // Cannot delete again _, err = ss.Group().Delete(d1.Id) require.True(t, errors.As(err, &nfErr)) } func testGroupStoreRestore(t *testing.T, rctx request.CTX, ss store.Store) { // Save a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } d1, err := ss.Group().Create(g1) require.NoError(t, err) require.Len(t, d1.Id, 26) // Check the group is retrievable _, err = ss.Group().Get(d1.Id) require.NoError(t, err) // Delete the group _, err = ss.Group().Delete(d1.Id) require.NoError(t, err) // Get the before count d7, err := ss.Group().GetAllBySource(model.GroupSourceLdap) require.NoError(t, err) beforeCount := len(d7) // restore the group _, err = ss.Group().Restore(d1.Id) require.NoError(t, err) // Check the group is restored d4, err := ss.Group().Get(d1.Id) require.NoError(t, err) require.Zero(t, d4.DeleteAt) // Check the after count d5, err := ss.Group().GetAllBySource(model.GroupSourceLdap) require.NoError(t, err) afterCount := len(d5) require.Condition(t, func() bool { return beforeCount == afterCount-1 }) // Try and restore a nonexistent group _, err = ss.Group().Delete(model.NewId()) require.Error(t, err) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) // Cannot restore again _, err = ss.Group().Restore(d1.Id) require.True(t, errors.As(err, &nfErr)) } func testGroupGetMemberUsers(t *testing.T, rctx request.CTX, ss store.Store) { // Save a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user1.Id) require.NoError(t, err) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, nErr := ss.User().Save(rctx, u2) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user2.Id) require.NoError(t, err) // Check returns members groupMembers, err := ss.Group().GetMemberUsers(group.Id) require.NoError(t, err) require.Equal(t, 2, len(groupMembers)) // Check madeup id groupMembers, err = ss.Group().GetMemberUsers(model.NewId()) require.NoError(t, err) require.Equal(t, 0, len(groupMembers)) // Delete a member _, err = ss.Group().DeleteMember(group.Id, user1.Id) require.NoError(t, err) // Should not return deleted members groupMembers, err = ss.Group().GetMemberUsers(group.Id) require.NoError(t, err) require.Equal(t, 1, len(groupMembers)) } func testGroupGetMemberUsersPage(t *testing.T, rctx request.CTX, ss store.Store) { // Save a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) u1 := &model.User{ Email: MakeEmail(), Username: "user1" + model.NewId(), } user1, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user1.Id) require.NoError(t, err) u2 := &model.User{ Email: MakeEmail(), Username: "user2" + model.NewId(), } user2, nErr := ss.User().Save(rctx, u2) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user2.Id) require.NoError(t, err) u3 := &model.User{ Email: MakeEmail(), Username: "user3" + model.NewId(), } user3, nErr := ss.User().Save(rctx, u3) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user3.Id) require.NoError(t, err) // Check returns members groupMembers, err := ss.Group().GetMemberUsersPage(group.Id, 0, 100, nil) require.NoError(t, err) require.Equal(t, 3, len(groupMembers)) // Check page 1 groupMembers, err = ss.Group().GetMemberUsersPage(group.Id, 0, 2, nil) require.NoError(t, err) require.Equal(t, 2, len(groupMembers)) require.ElementsMatch(t, []*model.User{user1, user2}, groupMembers) // Check page 2 groupMembers, err = ss.Group().GetMemberUsersPage(group.Id, 1, 2, nil) require.NoError(t, err) require.Equal(t, 1, len(groupMembers)) require.ElementsMatch(t, []*model.User{user3}, groupMembers) // Check madeup id groupMembers, err = ss.Group().GetMemberUsersPage(model.NewId(), 0, 100, nil) require.NoError(t, err) require.Equal(t, 0, len(groupMembers)) // Delete a member _, err = ss.Group().DeleteMember(group.Id, user1.Id) require.NoError(t, err) // Should not return deleted members groupMembers, err = ss.Group().GetMemberUsersPage(group.Id, 0, 100, nil) require.NoError(t, err) require.Equal(t, 2, len(groupMembers)) } func testGroupGetMemberUsersSortedPage(t *testing.T, rctx request.CTX, ss store.Store) { // Save a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) // First by nickname, third by full name, second by username u1 := &model.User{ Email: MakeEmail(), Username: "y" + model.NewId(), Nickname: model.NewUsername(), FirstName: "z" + model.NewId(), LastName: "z" + model.NewId(), } user1, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user1.Id) require.NoError(t, err) // Second by nickname, first by full name, third by username u2 := &model.User{ Email: MakeEmail(), Username: "z" + model.NewId(), FirstName: "b" + model.NewId(), LastName: "b" + model.NewId(), } user2, nErr := ss.User().Save(rctx, u2) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user2.Id) require.NoError(t, err) // Third by nickname, second by full name, first by username u3 := &model.User{ Email: MakeEmail(), Username: "d" + model.NewId(), } user3, nErr := ss.User().Save(rctx, u3) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user3.Id) require.NoError(t, err) // Check nickname ordering, paged groupMembers, err := ss.Group().GetMemberUsersSortedPage(group.Id, 0, 2, nil, model.ShowNicknameFullName) require.NoError(t, err) require.Equal(t, 2, len(groupMembers)) require.ElementsMatch(t, []*model.User{user1, user2}, groupMembers) groupMembers, err = ss.Group().GetMemberUsersSortedPage(group.Id, 1, 2, nil, model.ShowNicknameFullName) require.NoError(t, err) require.Equal(t, 1, len(groupMembers)) require.ElementsMatch(t, []*model.User{user3}, groupMembers) // Check full name ordering, paged groupMembers, err = ss.Group().GetMemberUsersSortedPage(group.Id, 0, 2, nil, model.ShowFullName) require.NoError(t, err) require.Equal(t, 2, len(groupMembers)) require.ElementsMatch(t, []*model.User{user2, user3}, groupMembers) groupMembers, err = ss.Group().GetMemberUsersSortedPage(group.Id, 1, 2, nil, model.ShowFullName) require.NoError(t, err) require.Equal(t, 1, len(groupMembers)) require.ElementsMatch(t, []*model.User{user1}, groupMembers) // Check username ordering groupMembers, err = ss.Group().GetMemberUsersSortedPage(group.Id, 0, 2, nil, model.ShowUsername) require.NoError(t, err) require.Equal(t, 2, len(groupMembers)) require.ElementsMatch(t, []*model.User{user3, user1}, groupMembers) groupMembers, err = ss.Group().GetMemberUsersSortedPage(group.Id, 1, 2, nil, model.ShowUsername) require.NoError(t, err) require.Equal(t, 1, len(groupMembers)) require.ElementsMatch(t, []*model.User{user2}, groupMembers) } func testGroupGetMemberUsersInTeam(t *testing.T, rctx request.CTX, ss store.Store) { // Save a team team := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, err := ss.Team().Save(team) require.NoError(t, err) // Save a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, u1) require.NoError(t, err) _, err = ss.Group().UpsertMember(group.Id, user1.Id) require.NoError(t, err) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err := ss.User().Save(rctx, u2) require.NoError(t, err) _, err = ss.Group().UpsertMember(group.Id, user2.Id) require.NoError(t, err) u3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, err := ss.User().Save(rctx, u3) require.NoError(t, err) _, err = ss.Group().UpsertMember(group.Id, user3.Id) require.NoError(t, err) // returns no members when team does not exist groupMembers, err := ss.Group().GetMemberUsersInTeam(group.Id, "non-existent-channel-id") require.NoError(t, err) require.Equal(t, 0, len(groupMembers)) // returns no members when group has no members in the team groupMembers, err = ss.Group().GetMemberUsersInTeam(group.Id, team.Id) require.NoError(t, err) require.Equal(t, 0, len(groupMembers)) m1 := &model.TeamMember{TeamId: team.Id, UserId: user1.Id} _, nErr := ss.Team().SaveMember(rctx, m1, -1) require.NoError(t, nErr) // returns single member in team groupMembers, err = ss.Group().GetMemberUsersInTeam(group.Id, team.Id) require.NoError(t, err) require.Equal(t, 1, len(groupMembers)) m2 := &model.TeamMember{TeamId: team.Id, UserId: user2.Id} m3 := &model.TeamMember{TeamId: team.Id, UserId: user3.Id} _, nErr = ss.Team().SaveMember(rctx, m2, -1) require.NoError(t, nErr) _, nErr = ss.Team().SaveMember(rctx, m3, -1) require.NoError(t, nErr) // returns all members when all members are in team groupMembers, err = ss.Group().GetMemberUsersInTeam(group.Id, team.Id) require.NoError(t, err) require.Equal(t, 3, len(groupMembers)) } func testGroupGetMemberUsersNotInChannel(t *testing.T, rctx request.CTX, ss store.Store) { // Save a team team := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, err := ss.Team().Save(team) require.NoError(t, err) // Save a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, u1) require.NoError(t, err) _, err = ss.Group().UpsertMember(group.Id, user1.Id) require.NoError(t, err) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err := ss.User().Save(rctx, u2) require.NoError(t, err) _, err = ss.Group().UpsertMember(group.Id, user2.Id) require.NoError(t, err) u3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, err := ss.User().Save(rctx, u3) require.NoError(t, err) _, err = ss.Group().UpsertMember(group.Id, user3.Id) require.NoError(t, err) // Create Channel channel := &model.Channel{ TeamId: team.Id, DisplayName: "Channel", Name: model.NewId(), Type: model.ChannelTypeOpen, // Query does not look at type so this shouldn't matter. } channel, nErr := ss.Channel().Save(rctx, channel, 9999) require.NoError(t, nErr) // returns no members when channel does not exist groupMembers, err := ss.Group().GetMemberUsersNotInChannel(group.Id, "non-existent-channel-id") require.NoError(t, err) require.Equal(t, 0, len(groupMembers)) // returns no members when group has no members in the team that the channel belongs to groupMembers, err = ss.Group().GetMemberUsersNotInChannel(group.Id, channel.Id) require.NoError(t, err) require.Equal(t, 0, len(groupMembers)) m1 := &model.TeamMember{TeamId: team.Id, UserId: user1.Id} _, nErr = ss.Team().SaveMember(rctx, m1, -1) require.NoError(t, nErr) // returns single member in team and not in channel groupMembers, err = ss.Group().GetMemberUsersNotInChannel(group.Id, channel.Id) require.NoError(t, err) require.Equal(t, 1, len(groupMembers)) m2 := &model.TeamMember{TeamId: team.Id, UserId: user2.Id} m3 := &model.TeamMember{TeamId: team.Id, UserId: user3.Id} _, nErr = ss.Team().SaveMember(rctx, m2, -1) require.NoError(t, nErr) _, nErr = ss.Team().SaveMember(rctx, m3, -1) require.NoError(t, nErr) // returns all members when all members are in team and not in channel groupMembers, err = ss.Group().GetMemberUsersNotInChannel(group.Id, channel.Id) require.NoError(t, err) require.Equal(t, 3, len(groupMembers)) cm1 := &model.ChannelMember{ ChannelId: channel.Id, UserId: user1.Id, SchemeGuest: false, SchemeUser: true, SchemeAdmin: false, NotifyProps: model.GetDefaultChannelNotifyProps(), } _, err = ss.Channel().SaveMember(rctx, cm1) require.NoError(t, err) // returns both members not yet added to channel groupMembers, err = ss.Group().GetMemberUsersNotInChannel(group.Id, channel.Id) require.NoError(t, err) require.Equal(t, 2, len(groupMembers)) cm2 := &model.ChannelMember{ ChannelId: channel.Id, UserId: user2.Id, SchemeGuest: false, SchemeUser: true, SchemeAdmin: false, NotifyProps: model.GetDefaultChannelNotifyProps(), } cm3 := &model.ChannelMember{ ChannelId: channel.Id, UserId: user3.Id, SchemeGuest: false, SchemeUser: true, SchemeAdmin: false, NotifyProps: model.GetDefaultChannelNotifyProps(), } _, err = ss.Channel().SaveMember(rctx, cm2) require.NoError(t, err) _, err = ss.Channel().SaveMember(rctx, cm3) require.NoError(t, err) // returns none when all members have been added to team and channel groupMembers, err = ss.Group().GetMemberUsersNotInChannel(group.Id, channel.Id) require.NoError(t, err) require.Equal(t, 0, len(groupMembers)) } func testUpsertMember(t *testing.T, rctx request.CTX, ss store.Store) { // Create group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) // Create user u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) // Happy path d2, err := ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) require.Equal(t, d2.GroupId, group.Id) require.Equal(t, d2.UserId, user.Id) require.NotZero(t, d2.CreateAt) require.Zero(t, d2.DeleteAt) // Duplicate composite key (GroupId, UserId) // Ensure new CreateAt > previous CreateAt for the same (groupId, userId) time.Sleep(2 * time.Millisecond) _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) // Invalid GroupId _, err = ss.Group().UpsertMember(model.NewId(), user.Id) require.Error(t, err) require.Contains(t, err.Error(), "failed to get UserGroup with") // Restores a deleted member // Ensure new CreateAt > previous CreateAt for the same (groupId, userId) time.Sleep(2 * time.Millisecond) _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) _, err = ss.Group().DeleteMember(group.Id, user.Id) require.NoError(t, err) groupMembers, err := ss.Group().GetMemberUsers(group.Id) require.NoError(t, err) beforeRestoreCount := len(groupMembers) _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) groupMembers, err = ss.Group().GetMemberUsers(group.Id) require.NoError(t, err) afterRestoreCount := len(groupMembers) require.Equal(t, beforeRestoreCount+1, afterRestoreCount) } func testUpsertMembers(t *testing.T, rctx request.CTX, ss store.Store) { // Create group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) // Create user u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) // Create user u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, nErr := ss.User().Save(rctx, u2) require.NoError(t, nErr) // Happy path m, err := ss.Group().UpsertMembers(group.Id, []string{user.Id, user2.Id}) require.NoError(t, err) require.Equal(t, 2, len(m)) // Duplicate composite key (GroupId, UserId) // Ensure new CreateAt > previous CreateAt for the same (groupId, userId) // time.Sleep(2 * time.Millisecond) _, err = ss.Group().UpsertMembers(group.Id, []string{user.Id}) require.NoError(t, err) // Invalid GroupId _, err = ss.Group().UpsertMembers(model.NewId(), []string{user.Id}) require.Error(t, err) require.Contains(t, err.Error(), "failed to get UserGroup with") // Restores a deleted member // Ensure new CreateAt > previous CreateAt for the same (groupId, userId) time.Sleep(2 * time.Millisecond) _, err = ss.Group().UpsertMembers(group.Id, []string{user.Id, user2.Id}) require.NoError(t, err) _, err = ss.Group().DeleteMembers(group.Id, []string{user.Id}) require.NoError(t, err) groupMembers, err := ss.Group().GetMemberUsers(group.Id) require.NoError(t, err) beforeRestoreCount := len(groupMembers) _, err = ss.Group().UpsertMembers(group.Id, []string{user.Id, user2.Id}) require.NoError(t, err) groupMembers, err = ss.Group().GetMemberUsers(group.Id) require.NoError(t, err) afterRestoreCount := len(groupMembers) require.Equal(t, beforeRestoreCount+1, afterRestoreCount) } func testGroupDeleteMember(t *testing.T, rctx request.CTX, ss store.Store) { // Create group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) // Create user u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) // Create member d1, err := ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) // Happy path d2, err := ss.Group().DeleteMember(group.Id, user.Id) require.NoError(t, err) require.Equal(t, d2.GroupId, group.Id) require.Equal(t, d2.UserId, user.Id) require.Equal(t, d2.CreateAt, d1.CreateAt) require.NotZero(t, d2.DeleteAt) // Delete an already deleted member _, err = ss.Group().DeleteMember(group.Id, user.Id) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) // Delete with non-existent User _, err = ss.Group().DeleteMember(group.Id, model.NewId()) require.True(t, errors.As(err, &nfErr)) // Delete non-existent Group _, err = ss.Group().DeleteMember(model.NewId(), group.Id) require.True(t, errors.As(err, &nfErr)) } func testGroupDeleteMembers(t *testing.T, rctx request.CTX, ss store.Store) { // Create user u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) // Create group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } guids := &model.GroupWithUserIds{ Group: *g1, UserIds: []string{user.Id}, } group, err := ss.Group().CreateWithUserIds(guids) require.NoError(t, err) // Happy path d2, err := ss.Group().DeleteMembers(group.Id, []string{user.Id}) require.NoError(t, err) require.Equal(t, d2[0].GroupId, group.Id) require.Equal(t, d2[0].UserId, user.Id) require.NotZero(t, d2[0].DeleteAt) // Delete an already deleted member _, err = ss.Group().DeleteMembers(group.Id, []string{user.Id}) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) // Delete with non-existent User _, err = ss.Group().DeleteMembers(group.Id, []string{model.NewId()}) require.True(t, errors.As(err, &nfErr)) // Delete non-existent Group _, err = ss.Group().DeleteMembers(model.NewId(), []string{user.Id}) require.True(t, errors.As(err, &nfErr)) } func testGroupPermanentDeleteMembersByUser(t *testing.T, rctx request.CTX, ss store.Store) { var g *model.Group var groups []*model.Group numberOfGroups := 5 for range numberOfGroups { g = &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g) groups = append(groups, group) require.NoError(t, err) } // Create user u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user, err := ss.User().Save(rctx, u1) require.NoError(t, err) // Create members for _, group := range groups { _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) } // Happy path err = ss.Group().PermanentDeleteMembersByUser(user.Id) require.NoError(t, err) } func testCreateGroupSyncable(t *testing.T, rctx request.CTX, ss store.Store) { // Invalid GroupID _, err := ss.Group().CreateGroupSyncable(model.NewGroupTeam("x", model.NewId(), false)) var appErr *model.AppError require.True(t, errors.As(err, &appErr)) require.Equal(t, appErr.Id, "model.group_syncable.group_id.app_error") // Create Group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) // Create Team t1 := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, nErr := ss.Team().Save(t1) require.NoError(t, nErr) // New GroupSyncable, happy path gt1 := model.NewGroupTeam(group.Id, team.Id, false) d1, err := ss.Group().CreateGroupSyncable(gt1) require.NoError(t, err) require.Equal(t, gt1.SyncableId, d1.SyncableId) require.Equal(t, gt1.GroupId, d1.GroupId) require.Equal(t, gt1.AutoAdd, d1.AutoAdd) require.NotZero(t, d1.CreateAt) require.Zero(t, d1.DeleteAt) } func testGetGroupSyncable(t *testing.T, rctx request.CTX, ss store.Store) { // Create a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) // Create Team t1 := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, nErr := ss.Team().Save(t1) require.NoError(t, nErr) // Create GroupSyncable gt1 := model.NewGroupTeam(group.Id, team.Id, false) groupTeam, err := ss.Group().CreateGroupSyncable(gt1) require.NoError(t, err) // Get GroupSyncable dgt, err := ss.Group().GetGroupSyncable(groupTeam.GroupId, groupTeam.SyncableId, model.GroupSyncableTypeTeam) require.NoError(t, err) require.Equal(t, gt1.GroupId, dgt.GroupId) require.Equal(t, gt1.SyncableId, dgt.SyncableId) require.Equal(t, gt1.AutoAdd, dgt.AutoAdd) require.NotZero(t, gt1.CreateAt) require.NotZero(t, gt1.UpdateAt) require.Zero(t, gt1.DeleteAt) } func testGetGroupSyncableErrors(t *testing.T, rctx request.CTX, ss store.Store) { // Create a group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) // Test with invalid syncable type invalidSyncableType := model.GroupSyncableType("invalid") _, err = ss.Group().GetGroupSyncable(group.Id, model.NewId(), invalidSyncableType) require.Error(t, err) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr), "expected ErrNotFound, got %v", err) // Test with empty group ID _, err = ss.Group().GetGroupSyncable("", model.NewId(), model.GroupSyncableTypeTeam) require.True(t, errors.As(err, &nfErr), "expected ErrNotFound, got %v", err) // Test with empty syncable ID _, err = ss.Group().GetGroupSyncable(group.Id, "", model.GroupSyncableTypeTeam) require.True(t, errors.As(err, &nfErr), "expected ErrNotFound, got %v", err) // Test with completely non-existent IDs randomGroupId := model.NewId() randomTeamId := model.NewId() _, err = ss.Group().GetGroupSyncable(randomGroupId, randomTeamId, model.GroupSyncableTypeTeam) require.True(t, errors.As(err, &nfErr), "expected ErrNotFound, got %v", err) // Test with valid group ID but non-existent syncable _, err = ss.Group().GetGroupSyncable(group.Id, model.NewId(), model.GroupSyncableTypeTeam) require.True(t, errors.As(err, &nfErr)) } func testGetAllGroupSyncablesByGroup(t *testing.T, rctx request.CTX, ss store.Store) { t.Run("team", func(t *testing.T) { testGetAllGroupSyncablesByGroupTeam(t, rctx, ss) }) t.Run("channel", func(t *testing.T) { testGetAllGroupSyncablesByGroupChannel(t, rctx, ss) }) } func testGetAllGroupSyncablesByGroupTeam(t *testing.T, rctx request.CTX, ss store.Store) { numGroupSyncables := 10 // Create group g := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g) require.NoError(t, err) groupTeams := []*model.GroupSyncable{} // Create groupTeams for range numGroupSyncables { // Create Team t1 := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } var team *model.Team team, nErr := ss.Team().Save(t1) require.NoError(t, nErr) // create groupteam var groupTeam *model.GroupSyncable gt := model.NewGroupTeam(group.Id, team.Id, false) gt.SchemeAdmin = true groupTeam, err = ss.Group().CreateGroupSyncable(gt) require.NoError(t, err) groupTeams = append(groupTeams, groupTeam) } // Returns all the group teams d1, err := ss.Group().GetAllGroupSyncablesByGroupId(group.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) require.Len(t, d1, numGroupSyncables) for _, expectedGroupTeam := range groupTeams { present := false for _, dbGroupTeam := range d1 { if dbGroupTeam.GroupId == expectedGroupTeam.GroupId && dbGroupTeam.SyncableId == expectedGroupTeam.SyncableId { require.True(t, dbGroupTeam.SchemeAdmin) present = true break } } require.True(t, present) } } func testGetAllGroupSyncablesByGroupChannel(t *testing.T, rctx request.CTX, ss store.Store) { // Create Team team := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, nErr := ss.Team().Save(team) require.NoError(t, nErr) numGroupSyncables := 10 // Create group g := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g) require.NoError(t, err) groupChannels := []*model.GroupSyncable{} // Create groupChannels for range numGroupSyncables { // Create Channel channel := &model.Channel{ TeamId: team.Id, DisplayName: "A Name", Name: model.NewId(), Type: model.ChannelTypePrivate, } channel, nErr = ss.Channel().Save(rctx, channel, 9999) require.NoError(t, nErr) // Create groupChannel groupChannel := model.NewGroupChannel(group.Id, channel.Id, false) groupChannel.SchemeAdmin = true groupChannel, err = ss.Group().CreateGroupSyncable(groupChannel) require.NoError(t, err) groupChannels = append(groupChannels, groupChannel) } // Returns all the group channels groupSyncables, err := ss.Group().GetAllGroupSyncablesByGroupId(group.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) require.Len(t, groupSyncables, numGroupSyncables) for _, expectedGroupChannel := range groupChannels { present := false for _, dbGroupChannel := range groupSyncables { if dbGroupChannel.GroupId == expectedGroupChannel.GroupId && dbGroupChannel.SyncableId == expectedGroupChannel.SyncableId { require.True(t, dbGroupChannel.SchemeAdmin) present = true break } } require.True(t, present) } } func testUpdateGroupSyncable(t *testing.T, rctx request.CTX, ss store.Store) { // Create Group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) // Create Team t1 := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, nErr := ss.Team().Save(t1) require.NoError(t, nErr) // New GroupSyncable, happy path gt1 := model.NewGroupTeam(group.Id, team.Id, false) d1, err := ss.Group().CreateGroupSyncable(gt1) require.NoError(t, err) // Update existing group team gt1.AutoAdd = true d2, err := ss.Group().UpdateGroupSyncable(gt1) require.NoError(t, err) require.True(t, d2.AutoAdd) // Non-existent Group gt2 := model.NewGroupTeam(model.NewId(), team.Id, false) _, err = ss.Group().UpdateGroupSyncable(gt2) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) // Non-existent Team gt3 := model.NewGroupTeam(group.Id, model.NewId(), false) _, err = ss.Group().UpdateGroupSyncable(gt3) require.True(t, errors.As(err, &nfErr)) // Cannot update CreateAt or DeleteAt origCreateAt := d1.CreateAt d1.CreateAt = model.GetMillis() d1.AutoAdd = true d3, err := ss.Group().UpdateGroupSyncable(d1) require.NoError(t, err) require.Equal(t, origCreateAt, d3.CreateAt) // Cannot update DeleteAt to arbitrary value d1.DeleteAt = 1 _, err = ss.Group().UpdateGroupSyncable(d1) require.Error(t, err) require.Contains(t, err.Error(), "DeleteAt should be 0 when updating") // Can update DeleteAt to 0 d1.DeleteAt = 0 d4, err := ss.Group().UpdateGroupSyncable(d1) require.NoError(t, err) require.Zero(t, d4.DeleteAt) } func testDeleteGroupSyncable(t *testing.T, rctx request.CTX, ss store.Store) { // Create Group g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) // Create Team t1 := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, nErr := ss.Team().Save(t1) require.NoError(t, nErr) // Create GroupSyncable gt1 := model.NewGroupTeam(group.Id, team.Id, false) groupTeam, err := ss.Group().CreateGroupSyncable(gt1) require.NoError(t, err) // Non-existent Group _, err = ss.Group().DeleteGroupSyncable(model.NewId(), groupTeam.SyncableId, model.GroupSyncableTypeTeam) var nfErr *store.ErrNotFound require.True(t, errors.As(err, &nfErr)) // Non-existent Team _, err = ss.Group().DeleteGroupSyncable(groupTeam.GroupId, model.NewId(), model.GroupSyncableTypeTeam) require.True(t, errors.As(err, &nfErr)) // Happy path... d1, err := ss.Group().DeleteGroupSyncable(groupTeam.GroupId, groupTeam.SyncableId, model.GroupSyncableTypeTeam) require.NoError(t, err) require.NotZero(t, d1.DeleteAt) require.Equal(t, d1.GroupId, groupTeam.GroupId) require.Equal(t, d1.SyncableId, groupTeam.SyncableId) require.Equal(t, d1.AutoAdd, groupTeam.AutoAdd) require.Equal(t, d1.CreateAt, groupTeam.CreateAt) require.Condition(t, func() bool { return d1.UpdateAt >= groupTeam.UpdateAt }, d1.UpdateAt, ">=", groupTeam.UpdateAt) // Record already deleted _, err = ss.Group().DeleteGroupSyncable(d1.GroupId, d1.SyncableId, d1.Type) require.Error(t, err) var invErr *store.ErrInvalidInput require.True(t, errors.As(err, &invErr)) } func testTeamMembersToAdd(t *testing.T, rctx request.CTX, ss store.Store) { // Create Group group, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "TeamMembersToAdd Test Group", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) require.NoError(t, err) // Create User user := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user, nErr := ss.User().Save(rctx, user) require.NoError(t, nErr) // Create GroupMember _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) // Create Team team := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, nErr = ss.Team().Save(team) require.NoError(t, nErr) // Create GroupTeam syncable, err := ss.Group().CreateGroupSyncable(model.NewGroupTeam(group.Id, team.Id, true)) require.NoError(t, err) // Time before syncable was created teamMembers, err := ss.Group().TeamMembersToAdd(syncable.CreateAt-1, nil, false) require.NoError(t, err) require.Len(t, teamMembers, 1) require.Equal(t, user.Id, teamMembers[0].UserID) require.Equal(t, team.Id, teamMembers[0].TeamID) // Time after syncable was created teamMembers, err = ss.Group().TeamMembersToAdd(syncable.CreateAt+1, nil, false) require.NoError(t, err) require.Empty(t, teamMembers) // Delete and restore GroupMember should return result _, err = ss.Group().DeleteMember(group.Id, user.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToAdd(syncable.CreateAt+1, nil, false) require.NoError(t, err) require.Len(t, teamMembers, 1) pristineSyncable := *syncable _, err = ss.Group().UpdateGroupSyncable(syncable) require.NoError(t, err) // Time before syncable was updated teamMembers, err = ss.Group().TeamMembersToAdd(syncable.UpdateAt-1, nil, false) require.NoError(t, err) require.Len(t, teamMembers, 1) require.Equal(t, user.Id, teamMembers[0].UserID) require.Equal(t, team.Id, teamMembers[0].TeamID) // Time after syncable was updated teamMembers, err = ss.Group().TeamMembersToAdd(syncable.UpdateAt+1, nil, false) require.NoError(t, err) require.Empty(t, teamMembers) // Only includes if auto-add syncable.AutoAdd = false _, err = ss.Group().UpdateGroupSyncable(syncable) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, teamMembers) // reset state of syncable and verify _, err = ss.Group().UpdateGroupSyncable(&pristineSyncable) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, teamMembers, 1) // No result if Group deleted _, err = ss.Group().Delete(group.Id) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, teamMembers) // reset state of group and verify group.DeleteAt = 0 _, err = ss.Group().Update(group) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, teamMembers, 1) // No result if Team deleted team.DeleteAt = model.GetMillis() team, nErr = ss.Team().Update(team) require.NoError(t, nErr) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, teamMembers) // reset state of team and verify team.DeleteAt = 0 team, nErr = ss.Team().Update(team) require.NoError(t, nErr) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, teamMembers, 1) // No result if GroupTeam deleted _, err = ss.Group().DeleteGroupSyncable(group.Id, team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, teamMembers) // reset GroupTeam and verify _, err = ss.Group().UpdateGroupSyncable(&pristineSyncable) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, teamMembers, 1) // No result if GroupMember deleted _, err = ss.Group().DeleteMember(group.Id, user.Id) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, teamMembers) // restore group member and verify _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, teamMembers, 1) // adding team membership stops returning result _, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{ TeamId: team.Id, UserId: user.Id, }, 999) require.NoError(t, nErr) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, teamMembers) // Leaving Team should still not return result _, nErr = ss.Team().UpdateMember(rctx, &model.TeamMember{ TeamId: team.Id, UserId: user.Id, DeleteAt: model.GetMillis(), }) require.NoError(t, nErr) teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, teamMembers) // If reAddRemovedMembers is set to true, removed members should be added back in teamMembers, err = ss.Group().TeamMembersToAdd(0, nil, true) require.NoError(t, err) require.Len(t, teamMembers, 1) } func testTeamMembersToAddSingleTeam(t *testing.T, rctx request.CTX, ss store.Store) { group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "TeamMembersToAdd Test Group", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) require.NoError(t, err) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "TeamMembersToAdd Test Group", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) require.NoError(t, err) user1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, nErr := ss.User().Save(rctx, user1) require.NoError(t, nErr) user2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, nErr = ss.User().Save(rctx, user2) require.NoError(t, nErr) user3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, nErr = ss.User().Save(rctx, user3) require.NoError(t, nErr) for _, user := range []*model.User{user1, user2} { _, err = ss.Group().UpsertMember(group1.Id, user.Id) require.NoError(t, err) } _, err = ss.Group().UpsertMember(group2.Id, user3.Id) require.NoError(t, err) team1 := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team1, nErr = ss.Team().Save(team1) require.NoError(t, nErr) team2 := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team2, nErr = ss.Team().Save(team2) require.NoError(t, nErr) _, err = ss.Group().CreateGroupSyncable(model.NewGroupTeam(group1.Id, team1.Id, true)) require.NoError(t, err) _, err = ss.Group().CreateGroupSyncable(model.NewGroupTeam(group2.Id, team2.Id, true)) require.NoError(t, err) teamMembers, err := ss.Group().TeamMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, teamMembers, 3) teamMembers, err = ss.Group().TeamMembersToAdd(0, &team1.Id, false) require.NoError(t, err) require.Len(t, teamMembers, 2) teamMembers, err = ss.Group().TeamMembersToAdd(0, &team2.Id, false) require.NoError(t, err) require.Len(t, teamMembers, 1) } func testChannelMembersToAdd(t *testing.T, rctx request.CTX, ss store.Store) { // Create Group group, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "ChannelMembersToAdd Test Group", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) require.NoError(t, err) // Create User user := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user, nErr := ss.User().Save(rctx, user) require.NoError(t, nErr) // Create GroupMember _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) // Create Channel channel := &model.Channel{ TeamId: model.NewId(), DisplayName: "A Name", Name: model.NewId(), Type: model.ChannelTypeOpen, // Query does not look at type so this shouldn't matter. } channel, nErr = ss.Channel().Save(rctx, channel, 9999) require.NoError(t, nErr) // Create GroupChannel syncable, err := ss.Group().CreateGroupSyncable(model.NewGroupChannel(group.Id, channel.Id, true)) require.NoError(t, err) // Time before syncable was created channelMembers, err := ss.Group().ChannelMembersToAdd(syncable.CreateAt-1, nil, false) require.NoError(t, err) require.Len(t, channelMembers, 1) require.Equal(t, user.Id, channelMembers[0].UserID) require.Equal(t, channel.Id, channelMembers[0].ChannelID) // Time after syncable was created channelMembers, err = ss.Group().ChannelMembersToAdd(syncable.CreateAt+1, nil, false) require.NoError(t, err) require.Empty(t, channelMembers) // Delete and restore GroupMember should return result _, err = ss.Group().DeleteMember(group.Id, user.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToAdd(syncable.CreateAt+1, nil, false) require.NoError(t, err) require.Len(t, channelMembers, 1) pristineSyncable := *syncable _, err = ss.Group().UpdateGroupSyncable(syncable) require.NoError(t, err) // Time before syncable was updated channelMembers, err = ss.Group().ChannelMembersToAdd(syncable.UpdateAt-1, nil, false) require.NoError(t, err) require.Len(t, channelMembers, 1) require.Equal(t, user.Id, channelMembers[0].UserID) require.Equal(t, channel.Id, channelMembers[0].ChannelID) // Time after syncable was updated channelMembers, err = ss.Group().ChannelMembersToAdd(syncable.UpdateAt+1, nil, false) require.NoError(t, err) require.Empty(t, channelMembers) // Only includes if auto-add syncable.AutoAdd = false _, err = ss.Group().UpdateGroupSyncable(syncable) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, channelMembers) // reset state of syncable and verify _, err = ss.Group().UpdateGroupSyncable(&pristineSyncable) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, channelMembers, 1) // No result if Group deleted _, err = ss.Group().Delete(group.Id) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, channelMembers) // reset state of group and verify group.DeleteAt = 0 _, err = ss.Group().Update(group) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, channelMembers, 1) // No result if Channel deleted nErr = ss.Channel().Delete(channel.Id, model.GetMillis()) require.NoError(t, nErr) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, channelMembers) // reset state of channel and verify channel.DeleteAt = 0 _, nErr = ss.Channel().Update(rctx, channel) require.NoError(t, nErr) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, channelMembers, 1) // No result if GroupChannel deleted _, err = ss.Group().DeleteGroupSyncable(group.Id, channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, channelMembers) // reset GroupChannel and verify _, err = ss.Group().UpdateGroupSyncable(&pristineSyncable) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, channelMembers, 1) // No result if GroupMember deleted _, err = ss.Group().DeleteMember(group.Id, user.Id) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, channelMembers) // restore group member and verify _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, channelMembers, 1) // Adding Channel (ChannelMemberHistory) should stop returning result nErr = ss.ChannelMemberHistory().LogJoinEvent(user.Id, channel.Id, model.GetMillis()) require.NoError(t, nErr) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, channelMembers) // Leaving Channel (ChannelMemberHistory) should still not return result nErr = ss.ChannelMemberHistory().LogLeaveEvent(user.Id, channel.Id, model.GetMillis()) require.NoError(t, nErr) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Empty(t, channelMembers) // Purging ChannelMemberHistory re-returns the result _, _, nErr = ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{ Now: 0, GlobalPolicyEndTime: model.GetMillis() + 1, Limit: 100, }, model.RetentionPolicyCursor{}) require.NoError(t, nErr) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.Len(t, channelMembers, 1) // If reAddRemovedMembers is set to true, removed members should be added back in nErr = ss.ChannelMemberHistory().LogLeaveEvent(user.Id, channel.Id, model.GetMillis()) require.NoError(t, nErr) channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, true) require.NoError(t, err) require.Len(t, channelMembers, 1) } func testChannelMembersToAddSingleChannel(t *testing.T, rctx request.CTX, ss store.Store) { group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "TeamMembersToAdd Test Group", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) require.NoError(t, err) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "TeamMembersToAdd Test Group", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) require.NoError(t, err) user1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, nErr := ss.User().Save(rctx, user1) require.NoError(t, nErr) user2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, nErr = ss.User().Save(rctx, user2) require.NoError(t, nErr) user3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, nErr = ss.User().Save(rctx, user3) require.NoError(t, nErr) for _, user := range []*model.User{user1, user2} { _, err = ss.Group().UpsertMember(group1.Id, user.Id) require.NoError(t, err) } _, err = ss.Group().UpsertMember(group2.Id, user3.Id) require.NoError(t, err) channel1 := &model.Channel{ DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Type: model.ChannelTypeOpen, } channel1, nErr = ss.Channel().Save(rctx, channel1, 999) require.NoError(t, nErr) channel2 := &model.Channel{ DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Type: model.ChannelTypeOpen, } channel2, nErr = ss.Channel().Save(rctx, channel2, 999) require.NoError(t, nErr) _, err = ss.Group().CreateGroupSyncable(model.NewGroupChannel(group1.Id, channel1.Id, true)) require.NoError(t, err) _, err = ss.Group().CreateGroupSyncable(model.NewGroupChannel(group2.Id, channel2.Id, true)) require.NoError(t, err) channelMembers, err := ss.Group().ChannelMembersToAdd(0, nil, false) require.NoError(t, err) require.GreaterOrEqual(t, len(channelMembers), 3) channelMembers, err = ss.Group().ChannelMembersToAdd(0, &channel1.Id, false) require.NoError(t, err) require.Len(t, channelMembers, 2) channelMembers, err = ss.Group().ChannelMembersToAdd(0, &channel2.Id, false) require.NoError(t, err) require.Len(t, channelMembers, 1) } func testTeamMembersToRemove(t *testing.T, rctx request.CTX, ss store.Store) { data := pendingMemberRemovalsDataSetup(t, rctx, ss) // one result when both users are in the group (for user C) teamMembers, err := ss.Group().TeamMembersToRemove(nil) require.NoError(t, err) require.Len(t, teamMembers, 1) require.Equal(t, data.UserC.Id, teamMembers[0].UserId) _, err = ss.Group().DeleteMember(data.Group.Id, data.UserB.Id) require.NoError(t, err) // user b and c should now be returned teamMembers, err = ss.Group().TeamMembersToRemove(nil) require.NoError(t, err) require.Len(t, teamMembers, 2) var userIDs []string for _, item := range teamMembers { userIDs = append(userIDs, item.UserId) } require.Contains(t, userIDs, data.UserB.Id) require.Contains(t, userIDs, data.UserC.Id) require.Equal(t, data.ConstrainedTeam.Id, teamMembers[0].TeamId) require.Equal(t, data.ConstrainedTeam.Id, teamMembers[1].TeamId) _, err = ss.Group().DeleteMember(data.Group.Id, data.UserA.Id) require.NoError(t, err) teamMembers, err = ss.Group().TeamMembersToRemove(nil) require.NoError(t, err) require.Len(t, teamMembers, 3) // Make one of them a bot teamMembers, err = ss.Group().TeamMembersToRemove(nil) require.NoError(t, err) teamMember := teamMembers[0] bot := &model.Bot{ UserId: teamMember.UserId, Username: "un_" + model.NewId(), DisplayName: "dn_" + model.NewId(), OwnerId: teamMember.UserId, } bot, nErr := ss.Bot().Save(bot) require.NoError(t, nErr) // verify that bot is not returned in results teamMembers, err = ss.Group().TeamMembersToRemove(nil) require.NoError(t, err) require.Len(t, teamMembers, 2) // delete the bot nErr = ss.Bot().PermanentDelete(bot.UserId) require.NoError(t, nErr) // Should be back to 3 users teamMembers, err = ss.Group().TeamMembersToRemove(nil) require.NoError(t, err) require.Len(t, teamMembers, 3) // add users back to groups res := ss.Team().RemoveMember(rctx, data.ConstrainedTeam.Id, data.UserA.Id) require.NoError(t, res) res = ss.Team().RemoveMember(rctx, data.ConstrainedTeam.Id, data.UserB.Id) require.NoError(t, res) res = ss.Team().RemoveMember(rctx, data.ConstrainedTeam.Id, data.UserC.Id) require.NoError(t, res) nErr = ss.Channel().RemoveMember(rctx, data.ConstrainedChannel.Id, data.UserA.Id) require.NoError(t, nErr) nErr = ss.Channel().RemoveMember(rctx, data.ConstrainedChannel.Id, data.UserB.Id) require.NoError(t, nErr) nErr = ss.Channel().RemoveMember(rctx, data.ConstrainedChannel.Id, data.UserC.Id) require.NoError(t, nErr) } func testTeamMembersToRemoveSingleTeam(t *testing.T, rctx request.CTX, ss store.Store) { user1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, user1) require.NoError(t, err) user2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err = ss.User().Save(rctx, user2) require.NoError(t, err) user3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, err = ss.User().Save(rctx, user3) require.NoError(t, err) team1 := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, GroupConstrained: model.NewPointer(true), } team1, nErr := ss.Team().Save(team1) require.NoError(t, nErr) team2 := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, GroupConstrained: model.NewPointer(true), } team2, nErr = ss.Team().Save(team2) require.NoError(t, nErr) for _, user := range []*model.User{user1, user2} { _, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{ TeamId: team1.Id, UserId: user.Id, }, 999) require.NoError(t, nErr) } _, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{ TeamId: team2.Id, UserId: user3.Id, }, 999) require.NoError(t, nErr) teamMembers, err := ss.Group().TeamMembersToRemove(nil) require.NoError(t, err) require.Len(t, teamMembers, 3) teamMembers, err = ss.Group().TeamMembersToRemove(&team1.Id) require.NoError(t, err) require.Len(t, teamMembers, 2) teamMembers, err = ss.Group().TeamMembersToRemove(&team2.Id) require.NoError(t, err) require.Len(t, teamMembers, 1) } func testChannelMembersToRemove(t *testing.T, rctx request.CTX, ss store.Store) { data := pendingMemberRemovalsDataSetup(t, rctx, ss) // one result when both users are in the group (for user C) channelMembers, err := ss.Group().ChannelMembersToRemove(nil) require.NoError(t, err) require.Len(t, channelMembers, 1) require.Equal(t, data.UserC.Id, channelMembers[0].UserId) _, err = ss.Group().DeleteMember(data.Group.Id, data.UserB.Id) require.NoError(t, err) // user b and c should now be returned channelMembers, err = ss.Group().ChannelMembersToRemove(nil) require.NoError(t, err) require.Len(t, channelMembers, 2) var userIDs []string for _, item := range channelMembers { userIDs = append(userIDs, item.UserId) } require.Contains(t, userIDs, data.UserB.Id) require.Contains(t, userIDs, data.UserC.Id) require.Equal(t, data.ConstrainedChannel.Id, channelMembers[0].ChannelId) require.Equal(t, data.ConstrainedChannel.Id, channelMembers[1].ChannelId) _, err = ss.Group().DeleteMember(data.Group.Id, data.UserA.Id) require.NoError(t, err) channelMembers, err = ss.Group().ChannelMembersToRemove(nil) require.NoError(t, err) require.Len(t, channelMembers, 3) // Make one of them a bot channelMembers, err = ss.Group().ChannelMembersToRemove(nil) require.NoError(t, err) channelMember := channelMembers[0] bot := &model.Bot{ UserId: channelMember.UserId, Username: "un_" + model.NewId(), DisplayName: "dn_" + model.NewId(), OwnerId: channelMember.UserId, } bot, nErr := ss.Bot().Save(bot) require.NoError(t, nErr) // verify that bot is not returned in results channelMembers, err = ss.Group().ChannelMembersToRemove(nil) require.NoError(t, err) require.Len(t, channelMembers, 2) // delete the bot nErr = ss.Bot().PermanentDelete(bot.UserId) require.NoError(t, nErr) // Should be back to 3 users channelMembers, err = ss.Group().ChannelMembersToRemove(nil) require.NoError(t, err) require.Len(t, channelMembers, 3) // add users back to groups res := ss.Team().RemoveMember(rctx, data.ConstrainedTeam.Id, data.UserA.Id) require.NoError(t, res) res = ss.Team().RemoveMember(rctx, data.ConstrainedTeam.Id, data.UserB.Id) require.NoError(t, res) res = ss.Team().RemoveMember(rctx, data.ConstrainedTeam.Id, data.UserC.Id) require.NoError(t, res) nErr = ss.Channel().RemoveMember(rctx, data.ConstrainedChannel.Id, data.UserA.Id) require.NoError(t, nErr) nErr = ss.Channel().RemoveMember(rctx, data.ConstrainedChannel.Id, data.UserB.Id) require.NoError(t, nErr) nErr = ss.Channel().RemoveMember(rctx, data.ConstrainedChannel.Id, data.UserC.Id) require.NoError(t, nErr) } func testChannelMembersToRemoveSingleChannel(t *testing.T, rctx request.CTX, ss store.Store) { user1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, user1) require.NoError(t, err) user2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err = ss.User().Save(rctx, user2) require.NoError(t, err) user3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, err = ss.User().Save(rctx, user3) require.NoError(t, err) channel1 := &model.Channel{ DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Type: model.ChannelTypeOpen, GroupConstrained: model.NewPointer(true), } channel1, nErr := ss.Channel().Save(rctx, channel1, 999) require.NoError(t, nErr) channel2 := &model.Channel{ DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Type: model.ChannelTypeOpen, GroupConstrained: model.NewPointer(true), } channel2, nErr = ss.Channel().Save(rctx, channel2, 999) require.NoError(t, nErr) for _, user := range []*model.User{user1, user2} { _, nErr = ss.Channel().SaveMember(rctx, &model.ChannelMember{ ChannelId: channel1.Id, UserId: user.Id, NotifyProps: model.GetDefaultChannelNotifyProps(), }) require.NoError(t, nErr) } _, nErr = ss.Channel().SaveMember(rctx, &model.ChannelMember{ ChannelId: channel2.Id, UserId: user3.Id, NotifyProps: model.GetDefaultChannelNotifyProps(), }) require.NoError(t, nErr) channelMembers, err := ss.Group().ChannelMembersToRemove(nil) require.NoError(t, err) require.Len(t, channelMembers, 3) channelMembers, err = ss.Group().ChannelMembersToRemove(&channel1.Id) require.NoError(t, err) require.Len(t, channelMembers, 2) channelMembers, err = ss.Group().ChannelMembersToRemove(&channel2.Id) require.NoError(t, err) require.Len(t, channelMembers, 1) } type removalsData struct { UserA *model.User UserB *model.User UserC *model.User ConstrainedChannel *model.Channel UnconstrainedChannel *model.Channel ConstrainedTeam *model.Team UnconstrainedTeam *model.Team Group *model.Group } func pendingMemberRemovalsDataSetup(t *testing.T, rctx request.CTX, ss store.Store) *removalsData { // create group group, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "Pending[Channel|Team]MemberRemovals Test Group", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, }) require.NoError(t, err) // create users // userA will get removed from the group userA := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } userA, nErr := ss.User().Save(rctx, userA) require.NoError(t, nErr) // userB will not get removed from the group userB := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } userB, nErr = ss.User().Save(rctx, userB) require.NoError(t, nErr) // userC was never in the group userC := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } userC, nErr = ss.User().Save(rctx, userC) require.NoError(t, nErr) // add users to group (but not userC) _, err = ss.Group().UpsertMember(group.Id, userA.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group.Id, userB.Id) require.NoError(t, err) // create channels channelConstrained := &model.Channel{ TeamId: model.NewId(), DisplayName: "A Name", Name: model.NewId(), Type: model.ChannelTypePrivate, GroupConstrained: model.NewPointer(true), } channelConstrained, nErr = ss.Channel().Save(rctx, channelConstrained, 9999) require.NoError(t, nErr) channelUnconstrained := &model.Channel{ TeamId: model.NewId(), DisplayName: "A Name", Name: model.NewId(), Type: model.ChannelTypePrivate, } channelUnconstrained, nErr = ss.Channel().Save(rctx, channelUnconstrained, 9999) require.NoError(t, nErr) // create teams teamConstrained := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamInvite, GroupConstrained: model.NewPointer(true), } teamConstrained, nErr = ss.Team().Save(teamConstrained) require.NoError(t, nErr) teamUnconstrained := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid1", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamInvite, } teamUnconstrained, nErr = ss.Team().Save(teamUnconstrained) require.NoError(t, nErr) // create groupteams _, err = ss.Group().CreateGroupSyncable(model.NewGroupTeam(group.Id, teamConstrained.Id, true)) require.NoError(t, err) _, err = ss.Group().CreateGroupSyncable(model.NewGroupTeam(group.Id, teamUnconstrained.Id, true)) require.NoError(t, err) // create groupchannels _, err = ss.Group().CreateGroupSyncable(model.NewGroupChannel(group.Id, channelConstrained.Id, true)) require.NoError(t, err) _, err = ss.Group().CreateGroupSyncable(model.NewGroupChannel(group.Id, channelUnconstrained.Id, true)) require.NoError(t, err) // add users to teams userIDTeamIDs := [][]string{ {userA.Id, teamConstrained.Id}, {userB.Id, teamConstrained.Id}, {userC.Id, teamConstrained.Id}, {userA.Id, teamUnconstrained.Id}, {userB.Id, teamUnconstrained.Id}, {userC.Id, teamUnconstrained.Id}, } for _, item := range userIDTeamIDs { _, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{ UserId: item[0], TeamId: item[1], }, 99) require.NoError(t, nErr) } // add users to channels userIDChannelIDs := [][]string{ {userA.Id, channelConstrained.Id}, {userB.Id, channelConstrained.Id}, {userC.Id, channelConstrained.Id}, {userA.Id, channelUnconstrained.Id}, {userB.Id, channelUnconstrained.Id}, {userC.Id, channelUnconstrained.Id}, } for _, item := range userIDChannelIDs { _, err := ss.Channel().SaveMember(rctx, &model.ChannelMember{ UserId: item[0], ChannelId: item[1], NotifyProps: model.GetDefaultChannelNotifyProps(), }) require.NoError(t, err) } return &removalsData{ UserA: userA, UserB: userB, UserC: userC, ConstrainedChannel: channelConstrained, UnconstrainedChannel: channelUnconstrained, ConstrainedTeam: teamConstrained, UnconstrainedTeam: teamUnconstrained, Group: group, } } func testGetGroupsByChannel(t *testing.T, rctx request.CTX, ss store.Store) { // Create Channel1 channel1 := &model.Channel{ TeamId: model.NewId(), DisplayName: "Channel1", Name: model.NewId(), Type: model.ChannelTypeOpen, } channel1, err := ss.Channel().Save(rctx, channel1, 9999) require.NoError(t, err) // Create Groups 1, 2 and a deleted group group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-1", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, }) require.NoError(t, err) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-2", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: false, }) require.NoError(t, err) deletedGroup, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-deleted", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, DeleteAt: 1, }) require.NoError(t, err) // And associate them with Channel1 for _, g := range []*model.Group{group1, group2, deletedGroup} { _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel1.Id, Type: model.GroupSyncableTypeChannel, GroupId: g.Id, }) require.NoError(t, err) } // Create Channel2 channel2 := &model.Channel{ TeamId: model.NewId(), DisplayName: "Channel2", Name: model.NewId(), Type: model.ChannelTypeOpen, } channel2, nErr := ss.Channel().Save(rctx, channel2, 9999) require.NoError(t, nErr) // Create Group3 group3, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-3", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, }) require.NoError(t, err) // And associate it to Channel2 _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel2.Id, Type: model.GroupSyncableTypeChannel, GroupId: group3.Id, }) require.NoError(t, err) // add members u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, u1) require.NoError(t, err) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err := ss.User().Save(rctx, u2) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user2.Id) require.NoError(t, err) user2.DeleteAt = 1 _, err = ss.User().Update(rctx, user2, true) require.NoError(t, err) group1WithMemberCount := *group1 group1WithMemberCount.MemberCount = model.NewPointer(1) group2WithMemberCount := *group2 group2WithMemberCount.MemberCount = model.NewPointer(0) group1WSA := &model.GroupWithSchemeAdmin{Group: *group1, SchemeAdmin: model.NewPointer(false)} group2WSA := &model.GroupWithSchemeAdmin{Group: *group2, SchemeAdmin: model.NewPointer(false)} group3WSA := &model.GroupWithSchemeAdmin{Group: *group3, SchemeAdmin: model.NewPointer(false)} testCases := []struct { Name string ChannelId string Page int PerPage int Result []*model.GroupWithSchemeAdmin Opts model.GroupSearchOpts TotalCount *int64 }{ { Name: "Get the two Groups for Channel1", ChannelId: channel1.Id, Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, Result: []*model.GroupWithSchemeAdmin{group1WSA, group2WSA}, TotalCount: model.NewPointer(int64(2)), }, { Name: "Get first Group for Channel1 with page 0 with 1 element", ChannelId: channel1.Id, Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 1, Result: []*model.GroupWithSchemeAdmin{group1WSA}, }, { Name: "Get second Group for Channel1 with page 1 with 1 element", ChannelId: channel1.Id, Opts: model.GroupSearchOpts{}, Page: 1, PerPage: 1, Result: []*model.GroupWithSchemeAdmin{group2WSA}, }, { Name: "Get third Group for Channel2", ChannelId: channel2.Id, Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, Result: []*model.GroupWithSchemeAdmin{group3WSA}, }, { Name: "Get empty Groups for a fake id", ChannelId: model.NewId(), Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, Result: []*model.GroupWithSchemeAdmin{}, TotalCount: model.NewPointer(int64(0)), }, { Name: "Get group matching name", ChannelId: channel1.Id, Opts: model.GroupSearchOpts{Q: string([]rune(*group1.Name)[2:10])}, // very low change of a name collision Page: 0, PerPage: 100, Result: []*model.GroupWithSchemeAdmin{group1WSA}, TotalCount: model.NewPointer(int64(1)), }, { Name: "Get group matching display name", ChannelId: channel1.Id, Opts: model.GroupSearchOpts{Q: "rouP-1"}, Page: 0, PerPage: 100, Result: []*model.GroupWithSchemeAdmin{group1WSA}, TotalCount: model.NewPointer(int64(1)), }, { Name: "Get group matching multiple display names", ChannelId: channel1.Id, Opts: model.GroupSearchOpts{Q: "roUp-"}, Page: 0, PerPage: 100, Result: []*model.GroupWithSchemeAdmin{group1WSA, group2WSA}, TotalCount: model.NewPointer(int64(2)), }, { Name: "Include member counts", ChannelId: channel1.Id, Opts: model.GroupSearchOpts{IncludeMemberCount: true}, Page: 0, PerPage: 2, Result: []*model.GroupWithSchemeAdmin{ {Group: group1WithMemberCount, SchemeAdmin: model.NewPointer(false)}, {Group: group2WithMemberCount, SchemeAdmin: model.NewPointer(false)}, }, }, { Name: "Include allow reference", ChannelId: channel1.Id, Opts: model.GroupSearchOpts{FilterAllowReference: true}, Page: 0, PerPage: 100, Result: []*model.GroupWithSchemeAdmin{group1WSA}, }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { if tc.Opts.PageOpts == nil { tc.Opts.PageOpts = &model.PageOpts{} } tc.Opts.PageOpts.Page = tc.Page tc.Opts.PageOpts.PerPage = tc.PerPage groups, err := ss.Group().GetGroupsByChannel(tc.ChannelId, tc.Opts) require.NoError(t, err) require.ElementsMatch(t, tc.Result, groups) if tc.TotalCount != nil { var count int64 count, err = ss.Group().CountGroupsByChannel(tc.ChannelId, tc.Opts) require.NoError(t, err) require.Equal(t, *tc.TotalCount, count) } }) } } func testGetGroupsAssociatedToChannelsByTeam(t *testing.T, rctx request.CTX, ss store.Store) { // Create Team1 team1 := &model.Team{ DisplayName: "Team1", Description: model.NewId(), CompanyName: model.NewId(), AllowOpenInvite: false, InviteId: model.NewId(), Name: NewTestID(), Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team1, errt := ss.Team().Save(team1) require.NoError(t, errt) // Create Channel1 channel1 := &model.Channel{ TeamId: team1.Id, DisplayName: "Channel1", Name: model.NewId(), Type: model.ChannelTypeOpen, } channel1, err := ss.Channel().Save(rctx, channel1, 9999) require.NoError(t, err) // Create Groups 1, 2 and a deleted group group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-1", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: false, }) require.NoError(t, err) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-2", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, }) require.NoError(t, err) deletedGroup, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-deleted", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, DeleteAt: 1, }) require.NoError(t, err) // And associate them with Channel1 for _, g := range []*model.Group{group1, group2, deletedGroup} { _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel1.Id, Type: model.GroupSyncableTypeChannel, GroupId: g.Id, }) require.NoError(t, err) } // Create Channel2 channel2 := &model.Channel{ TeamId: team1.Id, DisplayName: "Channel2", Name: model.NewId(), Type: model.ChannelTypeOpen, } channel2, err = ss.Channel().Save(rctx, channel2, 9999) require.NoError(t, err) // Create Group3 group3, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-3", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, }) require.NoError(t, err) // And associate it to Channel2 _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel2.Id, Type: model.GroupSyncableTypeChannel, GroupId: group3.Id, }) require.NoError(t, err) // add members u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, u1) require.NoError(t, err) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err := ss.User().Save(rctx, u2) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user2.Id) require.NoError(t, err) user2.DeleteAt = 1 _, err = ss.User().Update(rctx, user2, true) require.NoError(t, err) group1WithMemberCount := *group1 group1WithMemberCount.MemberCount = model.NewPointer(1) group2WithMemberCount := *group2 group2WithMemberCount.MemberCount = model.NewPointer(0) group3WithMemberCount := *group3 group3WithMemberCount.MemberCount = model.NewPointer(0) group1WSA := &model.GroupWithSchemeAdmin{Group: *group1, SchemeAdmin: model.NewPointer(false)} group2WSA := &model.GroupWithSchemeAdmin{Group: *group2, SchemeAdmin: model.NewPointer(false)} group3WSA := &model.GroupWithSchemeAdmin{Group: *group3, SchemeAdmin: model.NewPointer(false)} testCases := []struct { Name string TeamId string Page int PerPage int Result map[string][]*model.GroupWithSchemeAdmin Opts model.GroupSearchOpts }{ { Name: "Get the groups for Channel1 and Channel2", TeamId: team1.Id, Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, Result: map[string][]*model.GroupWithSchemeAdmin{channel1.Id: {group1WSA, group2WSA}, channel2.Id: {group3WSA}}, }, { Name: "Get first Group for Channel1 with page 0 with 1 element", TeamId: team1.Id, Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 1, Result: map[string][]*model.GroupWithSchemeAdmin{channel1.Id: {group1WSA}}, }, { Name: "Get second Group for Channel1 with page 1 with 1 element", TeamId: team1.Id, Opts: model.GroupSearchOpts{}, Page: 1, PerPage: 1, Result: map[string][]*model.GroupWithSchemeAdmin{channel1.Id: {group2WSA}}, }, { Name: "Get empty Groups for a fake id", TeamId: model.NewId(), Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, Result: map[string][]*model.GroupWithSchemeAdmin{}, }, { Name: "Get group matching name", TeamId: team1.Id, Opts: model.GroupSearchOpts{Q: string([]rune(*group1.Name)[2:10])}, // very low chance of a name collision Page: 0, PerPage: 100, Result: map[string][]*model.GroupWithSchemeAdmin{channel1.Id: {group1WSA}}, }, { Name: "Get group matching display name", TeamId: team1.Id, Opts: model.GroupSearchOpts{Q: "rouP-1"}, Page: 0, PerPage: 100, Result: map[string][]*model.GroupWithSchemeAdmin{channel1.Id: {group1WSA}}, }, { Name: "Get group matching multiple display names", TeamId: team1.Id, Opts: model.GroupSearchOpts{Q: "roUp-"}, Page: 0, PerPage: 100, Result: map[string][]*model.GroupWithSchemeAdmin{channel1.Id: {group1WSA, group2WSA}, channel2.Id: {group3WSA}}, }, { Name: "Include member counts", TeamId: team1.Id, Opts: model.GroupSearchOpts{IncludeMemberCount: true}, Page: 0, PerPage: 10, Result: map[string][]*model.GroupWithSchemeAdmin{ channel1.Id: { {Group: group1WithMemberCount, SchemeAdmin: model.NewPointer(false)}, {Group: group2WithMemberCount, SchemeAdmin: model.NewPointer(false)}, }, channel2.Id: { {Group: group3WithMemberCount, SchemeAdmin: model.NewPointer(false)}, }, }, }, { Name: "Include allow reference", TeamId: team1.Id, Opts: model.GroupSearchOpts{FilterAllowReference: true}, Page: 0, PerPage: 2, Result: map[string][]*model.GroupWithSchemeAdmin{ channel1.Id: { group2WSA, }, channel2.Id: { group3WSA, }, }, }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { if tc.Opts.PageOpts == nil { tc.Opts.PageOpts = &model.PageOpts{} } tc.Opts.PageOpts.Page = tc.Page tc.Opts.PageOpts.PerPage = tc.PerPage groups, err := ss.Group().GetGroupsAssociatedToChannelsByTeam(tc.TeamId, tc.Opts) require.NoError(t, err) assert.Equal(t, tc.Result, groups) }) } } func testGetGroupsByTeam(t *testing.T, rctx request.CTX, ss store.Store) { // Create Team1 team1 := &model.Team{ DisplayName: "Team1", Description: model.NewId(), CompanyName: model.NewId(), AllowOpenInvite: false, InviteId: model.NewId(), Name: NewTestID(), Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team1, err := ss.Team().Save(team1) require.NoError(t, err) // Create Groups 1, 2 and a deleted group group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-1", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: false, }) require.NoError(t, err) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-2", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, }) require.NoError(t, err) deletedGroup, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-deleted", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, DeleteAt: 1, }) require.NoError(t, err) // And associate them with Team1 for _, g := range []*model.Group{group1, group2, deletedGroup} { _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: team1.Id, Type: model.GroupSyncableTypeTeam, GroupId: g.Id, }) require.NoError(t, err) } // Create Team2 team2 := &model.Team{ DisplayName: "Team2", Description: model.NewId(), CompanyName: model.NewId(), AllowOpenInvite: false, InviteId: model.NewId(), Name: NewTestID(), Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamInvite, } team2, err = ss.Team().Save(team2) require.NoError(t, err) // Create Group3 group3, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-3", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, }) require.NoError(t, err) // And associate it to Team2 _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: team2.Id, Type: model.GroupSyncableTypeTeam, GroupId: group3.Id, }) require.NoError(t, err) // add members u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, u1) require.NoError(t, err) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err := ss.User().Save(rctx, u2) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user2.Id) require.NoError(t, err) user2.DeleteAt = 1 _, err = ss.User().Update(rctx, user2, true) require.NoError(t, err) _, err = ss.Group().UpsertMember(deletedGroup.Id, user1.Id) require.NoError(t, err) group1WithMemberCount := *group1 group1WithMemberCount.MemberCount = model.NewPointer(1) group2WithMemberCount := *group2 group2WithMemberCount.MemberCount = model.NewPointer(0) group1WSA := &model.GroupWithSchemeAdmin{Group: *group1, SchemeAdmin: model.NewPointer(false)} group2WSA := &model.GroupWithSchemeAdmin{Group: *group2, SchemeAdmin: model.NewPointer(false)} group3WSA := &model.GroupWithSchemeAdmin{Group: *group3, SchemeAdmin: model.NewPointer(false)} testCases := []struct { Name string TeamId string Page int PerPage int Opts model.GroupSearchOpts Result []*model.GroupWithSchemeAdmin TotalCount *int64 }{ { Name: "Get the two Groups for Team1", TeamId: team1.Id, Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, Result: []*model.GroupWithSchemeAdmin{group1WSA, group2WSA}, TotalCount: model.NewPointer(int64(2)), }, { Name: "Get first Group for Team1 with page 0 with 1 element", TeamId: team1.Id, Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 1, Result: []*model.GroupWithSchemeAdmin{group1WSA}, }, { Name: "Get second Group for Team1 with page 1 with 1 element", TeamId: team1.Id, Opts: model.GroupSearchOpts{}, Page: 1, PerPage: 1, Result: []*model.GroupWithSchemeAdmin{group2WSA}, }, { Name: "Get third Group for Team2", TeamId: team2.Id, Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, Result: []*model.GroupWithSchemeAdmin{group3WSA}, TotalCount: model.NewPointer(int64(1)), }, { Name: "Get empty Groups for a fake id", TeamId: model.NewId(), Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, Result: []*model.GroupWithSchemeAdmin{}, TotalCount: model.NewPointer(int64(0)), }, { Name: "Get group matching name", TeamId: team1.Id, Opts: model.GroupSearchOpts{Q: string([]rune(*group1.Name)[2:10])}, // very low change of a name collision Page: 0, PerPage: 100, Result: []*model.GroupWithSchemeAdmin{group1WSA}, TotalCount: model.NewPointer(int64(1)), }, { Name: "Get group matching display name", TeamId: team1.Id, Opts: model.GroupSearchOpts{Q: "rouP-1"}, Page: 0, PerPage: 100, Result: []*model.GroupWithSchemeAdmin{group1WSA}, TotalCount: model.NewPointer(int64(1)), }, { Name: "Get group matching multiple display names", TeamId: team1.Id, Opts: model.GroupSearchOpts{Q: "roUp-"}, Page: 0, PerPage: 100, Result: []*model.GroupWithSchemeAdmin{group1WSA, group2WSA}, TotalCount: model.NewPointer(int64(2)), }, { Name: "Include member counts", TeamId: team1.Id, Opts: model.GroupSearchOpts{IncludeMemberCount: true}, Page: 0, PerPage: 2, Result: []*model.GroupWithSchemeAdmin{ {Group: group1WithMemberCount, SchemeAdmin: model.NewPointer(false)}, {Group: group2WithMemberCount, SchemeAdmin: model.NewPointer(false)}, }, }, { Name: "Include allow reference", TeamId: team1.Id, Opts: model.GroupSearchOpts{FilterAllowReference: true}, Page: 0, PerPage: 100, Result: []*model.GroupWithSchemeAdmin{group2WSA}, }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { if tc.Opts.PageOpts == nil { tc.Opts.PageOpts = &model.PageOpts{} } tc.Opts.PageOpts.Page = tc.Page tc.Opts.PageOpts.PerPage = tc.PerPage groups, err := ss.Group().GetGroupsByTeam(tc.TeamId, tc.Opts) require.NoError(t, err) require.ElementsMatch(t, tc.Result, groups) if tc.TotalCount != nil { var count int64 count, err = ss.Group().CountGroupsByTeam(tc.TeamId, tc.Opts) require.NoError(t, err) require.Equal(t, *tc.TotalCount, count) } }) } } func testGetGroups(t *testing.T, rctx request.CTX, ss store.Store) { // Create Team1 team1 := &model.Team{ DisplayName: "Team1", Description: model.NewId(), CompanyName: model.NewId(), AllowOpenInvite: false, InviteId: model.NewId(), Name: NewTestID(), Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, GroupConstrained: model.NewPointer(true), } team1, err := ss.Team().Save(team1) require.NoError(t, err) startCreateTime := team1.UpdateAt - 1 // Create Channel1 channel1 := &model.Channel{ TeamId: model.NewId(), DisplayName: "Channel1", Name: model.NewId(), Type: model.ChannelTypePrivate, } channel1, nErr := ss.Channel().Save(rctx, channel1, 9999) require.NoError(t, nErr) // Create Groups 1 and 2 group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: "group-1", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, }) require.NoError(t, err) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId() + "-group-2"), DisplayName: "group-2", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: false, }) require.NoError(t, err) deletedGroup, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId() + "-group-deleted"), DisplayName: "group-deleted", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: false, DeleteAt: 1, }) require.NoError(t, err) // And associate them with Team1 for _, g := range []*model.Group{group1, group2, deletedGroup} { _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: team1.Id, Type: model.GroupSyncableTypeTeam, GroupId: g.Id, }) require.NoError(t, err) } // Create Team2 team2 := &model.Team{ DisplayName: "Team2", Description: model.NewId(), CompanyName: model.NewId(), AllowOpenInvite: false, InviteId: model.NewId(), Name: NewTestID(), Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamInvite, } team2, err = ss.Team().Save(team2) require.NoError(t, err) // Create Channel2 channel2 := &model.Channel{ TeamId: model.NewId(), DisplayName: "Channel2", Name: model.NewId(), Type: model.ChannelTypePrivate, } channel2, nErr = ss.Channel().Save(rctx, channel2, 9999) require.NoError(t, nErr) // Create Channel3 channel3 := &model.Channel{ TeamId: team1.Id, DisplayName: "Channel3", Name: model.NewId(), Type: model.ChannelTypePrivate, } channel3, nErr = ss.Channel().Save(rctx, channel3, 9999) require.NoError(t, nErr) // Create Group3 group3, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId() + "-group-3"), DisplayName: "group-3", RemoteId: model.NewPointer(model.NewId()), Source: model.GroupSourceLdap, AllowReference: true, }) require.NoError(t, err) // And associate it to Team2 _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: team2.Id, Type: model.GroupSyncableTypeTeam, GroupId: group3.Id, }) require.NoError(t, err) // And associate Group1 to Channel2 _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel2.Id, Type: model.GroupSyncableTypeChannel, GroupId: group1.Id, }) require.NoError(t, err) // And associate Group2 and Group3 to Channel1 for _, g := range []*model.Group{group2, group3} { _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel1.Id, Type: model.GroupSyncableTypeChannel, GroupId: g.Id, }) require.NoError(t, err) } // add members u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, u1) require.NoError(t, err) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err := ss.User().Save(rctx, u2) require.NoError(t, err) u3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), Timezone: model.StringMap{ "useAutomaticTimezone": "false", "manualTimezone": "UTC", }, } user3, err := ss.User().Save(rctx, u3) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user2.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group2.Id, user2.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group2.Id, user3.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(deletedGroup.Id, user1.Id) require.NoError(t, err) m1 := model.ChannelMember{ ChannelId: channel1.Id, UserId: user1.Id, NotifyProps: model.GetDefaultChannelNotifyProps(), } _, err = ss.Channel().SaveMember(rctx, &m1) require.NoError(t, err) m2 := model.ChannelMember{ ChannelId: channel1.Id, UserId: user2.Id, NotifyProps: model.GetDefaultChannelNotifyProps(), } _, err = ss.Channel().SaveMember(rctx, &m2) require.NoError(t, err) m3 := model.ChannelMember{ ChannelId: channel2.Id, UserId: user2.Id, NotifyProps: model.GetDefaultChannelNotifyProps(), } _, err = ss.Channel().SaveMember(rctx, &m3) require.NoError(t, err) m4 := model.ChannelMember{ ChannelId: channel2.Id, UserId: user3.Id, NotifyProps: model.GetDefaultChannelNotifyProps(), } _, err = ss.Channel().SaveMember(rctx, &m4) require.NoError(t, err) user2.DeleteAt = 1 u2Update, _ := ss.User().Update(rctx, user2, true) group2NameSubstring := "group-2" endCreateTime := u2Update.New.UpdateAt + 1 // Create Team3 team3 := &model.Team{ DisplayName: "Team3", Description: model.NewId(), CompanyName: model.NewId(), AllowOpenInvite: false, InviteId: model.NewId(), Name: NewTestID(), Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamInvite, } team3, err = ss.Team().Save(team3) require.NoError(t, err) channel4 := &model.Channel{ TeamId: team3.Id, DisplayName: "Channel4", Name: model.NewId(), Type: model.ChannelTypePrivate, } channel4, nErr = ss.Channel().Save(rctx, channel4, 9999) require.NoError(t, nErr) testCases := []struct { Name string Page int PerPage int Opts model.GroupSearchOpts Resultf func([]*model.Group) bool Restrictions *model.ViewUsersRestrictions }{ { Name: "Get all the Groups", Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 3, Resultf: func(groups []*model.Group) bool { return len(groups) == 3 }, Restrictions: nil, }, { Name: "Get first Group with page 0 with 1 element", Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 1, Resultf: func(groups []*model.Group) bool { return len(groups) == 1 }, Restrictions: nil, }, { Name: "Get single result from page 1", Opts: model.GroupSearchOpts{}, Page: 1, PerPage: 1, Resultf: func(groups []*model.Group) bool { return len(groups) == 1 }, Restrictions: nil, }, { Name: "Get multiple results from page 1", Opts: model.GroupSearchOpts{}, Page: 1, PerPage: 2, Resultf: func(groups []*model.Group) bool { return len(groups) == 2 }, Restrictions: nil, }, { Name: "Get group matching name", Opts: model.GroupSearchOpts{Q: group2NameSubstring}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, g := range groups { if !strings.Contains(*g.Name, group2NameSubstring) && !strings.Contains(g.DisplayName, group2NameSubstring) { return false } } return true }, Restrictions: nil, }, { Name: "Get group matching display name", Opts: model.GroupSearchOpts{Q: "rouP-3"}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, g := range groups { if !strings.Contains(strings.ToLower(g.DisplayName), "roup-3") { return false } } return true }, Restrictions: nil, }, { Name: "Get group matching multiple display names", Opts: model.GroupSearchOpts{Q: "groUp"}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, g := range groups { if !strings.Contains(strings.ToLower(g.DisplayName), "group") { return false } } return true }, Restrictions: nil, }, { Name: "Include member counts", Opts: model.GroupSearchOpts{IncludeMemberCount: true}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, g := range groups { if g.MemberCount == nil { return false } if (g.Id == group1.Id || g.Id == group2.Id) && *g.MemberCount != 1 { return false } if g.DeleteAt != 0 { return false } } return true }, Restrictions: nil, }, { Name: "Include member counts with restrictions", Opts: model.GroupSearchOpts{IncludeMemberCount: true}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, g := range groups { if g.MemberCount == nil { return false } if g.Id == group1.Id && *g.MemberCount != 1 { return false } if g.Id == group2.Id && *g.MemberCount != 0 { return false } if g.DeleteAt != 0 { return false } } return true }, Restrictions: &model.ViewUsersRestrictions{Channels: []string{channel1.Id}}, }, { Name: "Not associated to team", Opts: model.GroupSearchOpts{NotAssociatedToTeam: team2.Id}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { if len(groups) == 0 { return false } for _, g := range groups { if g.Id == group3.Id { return false } if g.DeleteAt != 0 { return false } } return true }, Restrictions: nil, }, { Name: "Not associated to other team", Opts: model.GroupSearchOpts{NotAssociatedToTeam: team1.Id}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { if len(groups) == 0 { return false } for _, g := range groups { if g.Id == group1.Id || g.Id == group2.Id { return false } if g.DeleteAt != 0 { return false } } return true }, Restrictions: nil, }, { Name: "Include allow reference", Opts: model.GroupSearchOpts{FilterAllowReference: true}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { if len(groups) == 0 { return false } for _, g := range groups { if !g.AllowReference { return false } if g.DeleteAt != 0 { return false } } return true }, Restrictions: nil, }, { Name: "Use Since return all", Opts: model.GroupSearchOpts{FilterAllowReference: true, Since: startCreateTime}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { if len(groups) == 0 { return false } for _, g := range groups { if g.DeleteAt != 0 { return false } } return true }, Restrictions: nil, }, { Name: "Use Since return none", Opts: model.GroupSearchOpts{FilterAllowReference: true, Since: endCreateTime}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { return len(groups) == 0 }, Restrictions: nil, }, { Name: "Filter groups from group-constrained teams", Opts: model.GroupSearchOpts{NotAssociatedToChannel: channel3.Id, FilterParentTeamPermitted: true}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { return len(groups) == 2 && groups[0].Id == group1.Id && groups[1].Id == group2.Id }, Restrictions: nil, }, { Name: "Filter groups from group-constrained page 0", Opts: model.GroupSearchOpts{NotAssociatedToChannel: channel3.Id, FilterParentTeamPermitted: true}, Page: 0, PerPage: 1, Resultf: func(groups []*model.Group) bool { return groups[0].Id == group1.Id }, Restrictions: nil, }, { Name: "Filter groups from group-constrained page 1", Opts: model.GroupSearchOpts{NotAssociatedToChannel: channel3.Id, FilterParentTeamPermitted: true}, Page: 1, PerPage: 1, Resultf: func(groups []*model.Group) bool { return groups[0].Id == group2.Id }, Restrictions: nil, }, { Name: "Non-group constrained team with no associated groups still returns groups for the child channel", Opts: model.GroupSearchOpts{NotAssociatedToChannel: channel4.Id, FilterParentTeamPermitted: true}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { return len(groups) > 0 }, Restrictions: nil, }, { Name: "Filter by group member", Opts: model.GroupSearchOpts{FilterHasMember: user1.Id}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { return len(groups) == 1 && groups[0].Id == group1.Id }, Restrictions: nil, }, { Name: "Filter by non-existent group member", Opts: model.GroupSearchOpts{FilterHasMember: model.NewId()}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { return len(groups) == 0 }, Restrictions: nil, }, { Name: "Filter by non-member member", Opts: model.GroupSearchOpts{FilterHasMember: user2.Id}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { return len(groups) == 2 }, Restrictions: nil, }, { Name: "Include syncable sources only", Opts: model.GroupSearchOpts{OnlySyncableSources: true}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, g := range groups { if g.Source != model.GroupSourceLdap && !strings.HasPrefix(string(g.Source), "plugin_") { return false } } return true }, Restrictions: nil, }, { Name: "Include syncable sources with specific source", Opts: model.GroupSearchOpts{OnlySyncableSources: true, Source: model.GroupSourceLdap}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, g := range groups { if g.Source != model.GroupSourceLdap { return false } } return true }, Restrictions: nil, }, { Name: "Include archived groups", Opts: model.GroupSearchOpts{IncludeArchived: true, Q: "group-deleted"}, Page: 0, PerPage: 1, Resultf: func(groups []*model.Group) bool { return len(groups) == 1 }, Restrictions: nil, }, { Name: "Only return archived groups", Opts: model.GroupSearchOpts{FilterArchived: true, Q: "group-1"}, Page: 0, PerPage: 1, Resultf: func(groups []*model.Group) bool { return len(groups) == 0 }, Restrictions: nil, }, { Name: "Include channel1 member count", Opts: model.GroupSearchOpts{IncludeChannelMemberCount: channel1.Id}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, group := range groups { var channelMemberCount int if group.ChannelMemberCount != nil { channelMemberCount = *group.ChannelMemberCount } if group.Id == group1.Id && channelMemberCount != 2 { return false } if group.Id == group2.Id && channelMemberCount != 1 { return false } } return true }, Restrictions: nil, }, { Name: "Include channel2 member count", Opts: model.GroupSearchOpts{IncludeChannelMemberCount: channel2.Id}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, group := range groups { var channelMemberCount int if group.ChannelMemberCount != nil { channelMemberCount = *group.ChannelMemberCount } if group.Id == group1.Id && channelMemberCount != 1 { return false } if group.Id == group2.Id && channelMemberCount != 2 { return false } } return true }, Restrictions: nil, }, { Name: "Include channel member count for non-existent channel", Page: 0, PerPage: 100, Opts: model.GroupSearchOpts{IncludeChannelMemberCount: model.NewId()}, Resultf: func(groups []*model.Group) bool { for _, group := range groups { var channelMemberCount int if group.ChannelMemberCount != nil { channelMemberCount = *group.ChannelMemberCount } if channelMemberCount != 0 { return false } } return true }, Restrictions: nil, }, { Name: "Include channel1 member count, with timezones", Opts: model.GroupSearchOpts{IncludeChannelMemberCount: channel1.Id, IncludeTimezones: true}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, group := range groups { var channelMemberTimezonesCount int if group.ChannelMemberTimezonesCount != nil { channelMemberTimezonesCount = *group.ChannelMemberTimezonesCount } if group.Id == group1.Id && channelMemberTimezonesCount != 0 { return false } if group.Id == group2.Id && channelMemberTimezonesCount != 0 { return false } } return true }, Restrictions: nil, }, { Name: "Include channel2 member count, with timezones", Opts: model.GroupSearchOpts{IncludeChannelMemberCount: channel2.Id, IncludeTimezones: true}, Page: 0, PerPage: 100, Resultf: func(groups []*model.Group) bool { for _, group := range groups { var channelMemberTimezonesCount int if group.ChannelMemberTimezonesCount != nil { channelMemberTimezonesCount = *group.ChannelMemberTimezonesCount } if group.Id == group1.Id && channelMemberTimezonesCount != 0 { return false } if group.Id == group2.Id && channelMemberTimezonesCount != 1 { return false } } return true }, Restrictions: nil, }, { Name: "Include channel member count for non-existent channel, with timezones", Page: 0, PerPage: 100, Opts: model.GroupSearchOpts{IncludeChannelMemberCount: model.NewId(), IncludeTimezones: true}, Resultf: func(groups []*model.Group) bool { for _, group := range groups { var channelMemberTimezonesCount int if group.ChannelMemberTimezonesCount != nil { channelMemberTimezonesCount = *group.ChannelMemberCount } if channelMemberTimezonesCount != 0 { return false } } return true }, Restrictions: nil, }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { groups, err := ss.Group().GetGroups(tc.Page, tc.PerPage, tc.Opts, tc.Restrictions) require.NoError(t, err) require.True(t, tc.Resultf(groups)) }) } } func testTeamMembersMinusGroupMembers(t *testing.T, rctx request.CTX, ss store.Store) { const numberOfGroups = 3 const numberOfUsers = 4 groups := []*model.Group{} users := []*model.User{} team := &model.Team{ DisplayName: model.NewId(), Description: model.NewId(), CompanyName: model.NewId(), AllowOpenInvite: false, InviteId: model.NewId(), Name: NewTestID(), Email: model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, GroupConstrained: model.NewPointer(true), } team, err := ss.Team().Save(team) require.NoError(t, err) for i := range numberOfUsers { user := &model.User{ Email: MakeEmail(), Username: fmt.Sprintf("a%d_%s", i, model.NewId()), } user, err = ss.User().Save(rctx, user) require.NoError(t, err) users = append(users, user) trueOrFalse := int(math.Mod(float64(i), 2)) == 0 _, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: team.Id, UserId: user.Id, SchemeUser: trueOrFalse, SchemeAdmin: !trueOrFalse}, 999) require.NoError(t, nErr) } // Extra user outside of the group member users. user := &model.User{ Email: MakeEmail(), Username: "aa_" + model.NewId(), } user, err = ss.User().Save(rctx, user) require.NoError(t, err) users = append(users, user) _, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: team.Id, UserId: user.Id, SchemeUser: true, SchemeAdmin: false}, 999) require.NoError(t, nErr) for i := range numberOfGroups { group := &model.Group{ Name: model.NewPointer(fmt.Sprintf("n_%d_%s", i, model.NewId())), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(group) require.NoError(t, err) groups = append(groups, group) } sort.Slice(users, func(i, j int) bool { return users[i].Username < users[j].Username }) // Add even users to even group, and the inverse for i := range numberOfUsers { groupIndex := int(math.Mod(float64(i), 2)) _, err := ss.Group().UpsertMember(groups[groupIndex].Id, users[i].Id) require.NoError(t, err) // Add everyone to group 2 _, err = ss.Group().UpsertMember(groups[numberOfGroups-1].Id, users[i].Id) require.NoError(t, err) } testCases := map[string]struct { expectedUserIDs []string expectedTotalCount int64 groupIDs []string page int perPage int setup func() teardown func() }{ "No group IDs, all members": { expectedUserIDs: []string{users[0].Id, users[1].Id, users[2].Id, users[3].Id, user.Id}, expectedTotalCount: numberOfUsers + 1, groupIDs: []string{}, page: 0, perPage: 100, }, "All members, page 1": { expectedUserIDs: []string{users[0].Id, users[1].Id, users[2].Id}, expectedTotalCount: numberOfUsers + 1, groupIDs: []string{}, page: 0, perPage: 3, }, "All members, page 2": { expectedUserIDs: []string{users[3].Id, users[4].Id}, expectedTotalCount: numberOfUsers + 1, groupIDs: []string{}, page: 1, perPage: 3, }, "Group 1, even users would be removed": { expectedUserIDs: []string{users[0].Id, users[2].Id, users[4].Id}, expectedTotalCount: 3, groupIDs: []string{groups[1].Id}, page: 0, perPage: 100, }, "Group 0, odd users would be removed": { expectedUserIDs: []string{users[1].Id, users[3].Id, users[4].Id}, expectedTotalCount: 3, groupIDs: []string{groups[0].Id}, page: 0, perPage: 100, }, "All groups, no users would be removed": { expectedUserIDs: []string{users[4].Id}, expectedTotalCount: 1, groupIDs: []string{groups[0].Id, groups[1].Id}, page: 0, perPage: 100, }, } mapUserIDs := func(users []*model.UserWithGroups) []string { ids := []string{} for _, user := range users { ids = append(ids, user.Id) } return ids } for tcName, tc := range testCases { t.Run(tcName, func(t *testing.T) { if tc.setup != nil { tc.setup() } if tc.teardown != nil { defer tc.teardown() } actual, err := ss.Group().TeamMembersMinusGroupMembers(team.Id, tc.groupIDs, tc.page, tc.perPage) require.NoError(t, err) require.ElementsMatch(t, tc.expectedUserIDs, mapUserIDs(actual)) actualCount, err := ss.Group().CountTeamMembersMinusGroupMembers(team.Id, tc.groupIDs) require.NoError(t, err) require.Equal(t, tc.expectedTotalCount, actualCount) }) } } func testChannelMembersMinusGroupMembers(t *testing.T, rctx request.CTX, ss store.Store) { const numberOfGroups = 3 const numberOfUsers = 4 groups := []*model.Group{} users := []*model.User{} channel := &model.Channel{ TeamId: model.NewId(), DisplayName: "A Name", Name: model.NewId(), Type: model.ChannelTypePrivate, GroupConstrained: model.NewPointer(true), } channel, err := ss.Channel().Save(rctx, channel, 9999) require.NoError(t, err) for i := range numberOfUsers { user := &model.User{ Email: MakeEmail(), Username: fmt.Sprintf("a%d_%s", i, model.NewId()), } user, err = ss.User().Save(rctx, user) require.NoError(t, err) users = append(users, user) trueOrFalse := int(math.Mod(float64(i), 2)) == 0 _, err = ss.Channel().SaveMember(rctx, &model.ChannelMember{ ChannelId: channel.Id, UserId: user.Id, SchemeUser: trueOrFalse, SchemeAdmin: !trueOrFalse, NotifyProps: model.GetDefaultChannelNotifyProps(), }) require.NoError(t, err) } // Extra user outside of the group member users. user, err := ss.User().Save(rctx, &model.User{ Email: MakeEmail(), Username: "a99_" + model.NewId(), }) require.NoError(t, err) users = append(users, user) _, err = ss.Channel().SaveMember(rctx, &model.ChannelMember{ ChannelId: channel.Id, UserId: user.Id, SchemeUser: true, SchemeAdmin: false, NotifyProps: model.GetDefaultChannelNotifyProps(), }) require.NoError(t, err) for i := range numberOfGroups { group := &model.Group{ Name: model.NewPointer(fmt.Sprintf("n_%d_%s", i, model.NewId())), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(group) require.NoError(t, err) groups = append(groups, group) } sort.Slice(users, func(i, j int) bool { return users[i].Username < users[j].Username }) // Add even users to even group, and the inverse for i := range numberOfUsers { groupIndex := int(math.Mod(float64(i), 2)) _, err := ss.Group().UpsertMember(groups[groupIndex].Id, users[i].Id) require.NoError(t, err) // Add everyone to group 2 _, err = ss.Group().UpsertMember(groups[numberOfGroups-1].Id, users[i].Id) require.NoError(t, err) } testCases := map[string]struct { expectedUserIDs []string expectedTotalCount int64 groupIDs []string page int perPage int setup func() teardown func() }{ "No group IDs, all members": { expectedUserIDs: []string{users[0].Id, users[1].Id, users[2].Id, users[3].Id, users[4].Id}, expectedTotalCount: numberOfUsers + 1, groupIDs: []string{}, page: 0, perPage: 100, }, "All members, page 1": { expectedUserIDs: []string{users[0].Id, users[1].Id, users[2].Id}, expectedTotalCount: numberOfUsers + 1, groupIDs: []string{}, page: 0, perPage: 3, }, "All members, page 2": { expectedUserIDs: []string{users[3].Id, users[4].Id}, expectedTotalCount: numberOfUsers + 1, groupIDs: []string{}, page: 1, perPage: 3, }, "Group 1, even users would be removed": { expectedUserIDs: []string{users[0].Id, users[2].Id, users[4].Id}, expectedTotalCount: 3, groupIDs: []string{groups[1].Id}, page: 0, perPage: 100, }, "Group 0, odd users would be removed": { expectedUserIDs: []string{users[1].Id, users[3].Id, users[4].Id}, expectedTotalCount: 3, groupIDs: []string{groups[0].Id}, page: 0, perPage: 100, }, "All groups, no users would be removed": { expectedUserIDs: []string{users[4].Id}, expectedTotalCount: 1, groupIDs: []string{groups[0].Id, groups[1].Id}, page: 0, perPage: 100, }, } mapUserIDs := func(users []*model.UserWithGroups) []string { ids := []string{} for _, user := range users { ids = append(ids, user.Id) } return ids } for tcName, tc := range testCases { t.Run(tcName, func(t *testing.T) { if tc.setup != nil { tc.setup() } if tc.teardown != nil { defer tc.teardown() } actual, err := ss.Group().ChannelMembersMinusGroupMembers(channel.Id, tc.groupIDs, tc.page, tc.perPage) require.NoError(t, err) require.ElementsMatch(t, tc.expectedUserIDs, mapUserIDs(actual)) actualCount, err := ss.Group().CountChannelMembersMinusGroupMembers(channel.Id, tc.groupIDs) require.NoError(t, err) require.Equal(t, tc.expectedTotalCount, actualCount) }) } } func groupTestGetMemberCount(t *testing.T, rctx request.CTX, ss store.Store) { group := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(group) require.NoError(t, err) var user *model.User var nErr error for i := range 2 { user = &model.User{ Email: MakeEmail(), Username: fmt.Sprintf("a%d_%s", i, model.NewId()), } user, nErr = ss.User().Save(rctx, user) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) } count, err := ss.Group().GetMemberCount(group.Id) require.NoError(t, err) require.Equal(t, int64(2), count) user.DeleteAt = 1 _, nErr = ss.User().Update(rctx, user, true) require.NoError(t, nErr) count, err = ss.Group().GetMemberCount(group.Id) require.NoError(t, err) require.Equal(t, int64(1), count) } func groupTestAdminRoleGroupsForSyncableMemberChannel(t *testing.T, rctx request.CTX, ss store.Store) { user := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user, err := ss.User().Save(rctx, user) require.NoError(t, err) group1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group1, err = ss.Group().Create(group1) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user.Id) require.NoError(t, err) group2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group2, err = ss.Group().Create(group2) require.NoError(t, err) _, err = ss.Group().UpsertMember(group2.Id, user.Id) require.NoError(t, err) channel := &model.Channel{ TeamId: model.NewId(), DisplayName: "A Name", Name: model.NewId(), Type: model.ChannelTypeOpen, } channel, nErr := ss.Channel().Save(rctx, channel, 9999) require.NoError(t, nErr) _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel.Id, Type: model.GroupSyncableTypeChannel, GroupId: group1.Id, SchemeAdmin: true, }) require.NoError(t, err) groupSyncable2, err := ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel.Id, Type: model.GroupSyncableTypeChannel, GroupId: group2.Id, }) require.NoError(t, err) // User is a member of both groups but only one is SchemeAdmin: true actualGroupIDs, err := ss.Group().AdminRoleGroupsForSyncableMember(user.Id, channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) require.ElementsMatch(t, []string{group1.Id}, actualGroupIDs) // Update the second group syncable to be SchemeAdmin: true and both groups should be returned groupSyncable2.SchemeAdmin = true _, err = ss.Group().UpdateGroupSyncable(groupSyncable2) require.NoError(t, err) actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) require.ElementsMatch(t, []string{group1.Id, group2.Id}, actualGroupIDs) // Deleting membership from group should stop the group from being returned _, err = ss.Group().DeleteMember(group1.Id, user.Id) require.NoError(t, err) actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) require.ElementsMatch(t, []string{group2.Id}, actualGroupIDs) // Deleting group syncable should stop it being returned _, err = ss.Group().DeleteGroupSyncable(group2.Id, channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) require.ElementsMatch(t, []string{}, actualGroupIDs) } func groupTestAdminRoleGroupsForSyncableMemberTeam(t *testing.T, rctx request.CTX, ss store.Store) { user := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user, err := ss.User().Save(rctx, user) require.NoError(t, err) group1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group1, err = ss.Group().Create(group1) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user.Id) require.NoError(t, err) group2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group2, err = ss.Group().Create(group2) require.NoError(t, err) _, err = ss.Group().UpsertMember(group2.Id, user.Id) require.NoError(t, err) team := &model.Team{ DisplayName: "A Name", Name: NewTestID(), Type: model.TeamOpen, } team, nErr := ss.Team().Save(team) require.NoError(t, nErr) _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: team.Id, Type: model.GroupSyncableTypeTeam, GroupId: group1.Id, SchemeAdmin: true, }) require.NoError(t, err) groupSyncable2, err := ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: team.Id, Type: model.GroupSyncableTypeTeam, GroupId: group2.Id, }) require.NoError(t, err) // User is a member of both groups but only one is SchemeAdmin: true actualGroupIDs, err := ss.Group().AdminRoleGroupsForSyncableMember(user.Id, team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) require.ElementsMatch(t, []string{group1.Id}, actualGroupIDs) // Update the second group syncable to be SchemeAdmin: true and both groups should be returned groupSyncable2.SchemeAdmin = true _, err = ss.Group().UpdateGroupSyncable(groupSyncable2) require.NoError(t, err) actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) require.ElementsMatch(t, []string{group1.Id, group2.Id}, actualGroupIDs) // Deleting membership from group should stop the group from being returned _, err = ss.Group().DeleteMember(group1.Id, user.Id) require.NoError(t, err) actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) require.ElementsMatch(t, []string{group2.Id}, actualGroupIDs) // Deleting group syncable should stop it being returned _, err = ss.Group().DeleteGroupSyncable(group2.Id, team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) require.ElementsMatch(t, []string{}, actualGroupIDs) } func groupTestPermittedSyncableAdminsTeam(t *testing.T, rctx request.CTX, ss store.Store) { user1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, user1) require.NoError(t, err) user2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err = ss.User().Save(rctx, user2) require.NoError(t, err) user3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, err = ss.User().Save(rctx, user3) require.NoError(t, err) group1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group1, err = ss.Group().Create(group1) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user2.Id) require.NoError(t, err) group2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group2, err = ss.Group().Create(group2) require.NoError(t, err) _, err = ss.Group().UpsertMember(group2.Id, user3.Id) require.NoError(t, err) team := &model.Team{ DisplayName: "A Name", Name: NewTestID(), Type: model.TeamOpen, } team, nErr := ss.Team().Save(team) require.NoError(t, nErr) _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: team.Id, Type: model.GroupSyncableTypeTeam, GroupId: group1.Id, SchemeAdmin: true, }) require.NoError(t, err) groupSyncable2, err := ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: team.Id, Type: model.GroupSyncableTypeTeam, GroupId: group2.Id, SchemeAdmin: false, }) require.NoError(t, err) // group 1's users are returned because groupsyncable 2 has SchemeAdmin false. actualUserIDs, err := ss.Group().PermittedSyncableAdmins(team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) require.ElementsMatch(t, []string{user1.Id, user2.Id}, actualUserIDs) // update groupsyncable 2 to be SchemeAdmin true groupSyncable2.SchemeAdmin = true _, err = ss.Group().UpdateGroupSyncable(groupSyncable2) require.NoError(t, err) // group 2's users are now included in return value actualUserIDs, err = ss.Group().PermittedSyncableAdmins(team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) require.ElementsMatch(t, []string{user1.Id, user2.Id, user3.Id}, actualUserIDs) // deleted group member should not be included ss.Group().DeleteMember(group1.Id, user2.Id) require.NoError(t, err) actualUserIDs, err = ss.Group().PermittedSyncableAdmins(team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) require.ElementsMatch(t, []string{user1.Id, user3.Id}, actualUserIDs) // deleted group syncable no longer includes group members _, err = ss.Group().DeleteGroupSyncable(group1.Id, team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) actualUserIDs, err = ss.Group().PermittedSyncableAdmins(team.Id, model.GroupSyncableTypeTeam) require.NoError(t, err) require.ElementsMatch(t, []string{user3.Id}, actualUserIDs) } func groupTestPermittedSyncableAdminsChannel(t *testing.T, rctx request.CTX, ss store.Store) { user1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err := ss.User().Save(rctx, user1) require.NoError(t, err) user2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err = ss.User().Save(rctx, user2) require.NoError(t, err) user3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, err = ss.User().Save(rctx, user3) require.NoError(t, err) group1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group1, err = ss.Group().Create(group1) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group1.Id, user2.Id) require.NoError(t, err) group2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, Description: model.NewId(), RemoteId: model.NewPointer(model.NewId()), } group2, err = ss.Group().Create(group2) require.NoError(t, err) _, err = ss.Group().UpsertMember(group2.Id, user3.Id) require.NoError(t, err) channel := &model.Channel{ TeamId: model.NewId(), DisplayName: "A Name", Name: model.NewId(), Type: model.ChannelTypeOpen, } channel, nErr := ss.Channel().Save(rctx, channel, 9999) require.NoError(t, nErr) _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel.Id, Type: model.GroupSyncableTypeChannel, GroupId: group1.Id, SchemeAdmin: true, }) require.NoError(t, err) groupSyncable2, err := ss.Group().CreateGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: channel.Id, Type: model.GroupSyncableTypeChannel, GroupId: group2.Id, SchemeAdmin: false, }) require.NoError(t, err) // group 1's users are returned because groupsyncable 2 has SchemeAdmin false. actualUserIDs, err := ss.Group().PermittedSyncableAdmins(channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) require.ElementsMatch(t, []string{user1.Id, user2.Id}, actualUserIDs) // update groupsyncable 2 to be SchemeAdmin true groupSyncable2.SchemeAdmin = true _, err = ss.Group().UpdateGroupSyncable(groupSyncable2) require.NoError(t, err) // group 2's users are now included in return value actualUserIDs, err = ss.Group().PermittedSyncableAdmins(channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) require.ElementsMatch(t, []string{user1.Id, user2.Id, user3.Id}, actualUserIDs) // deleted group member should not be included _, err = ss.Group().DeleteMember(group1.Id, user2.Id) require.NoError(t, err) actualUserIDs, err = ss.Group().PermittedSyncableAdmins(channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) require.ElementsMatch(t, []string{user1.Id, user3.Id}, actualUserIDs) // deleted group syncable no longer includes group members _, err = ss.Group().DeleteGroupSyncable(group1.Id, channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) actualUserIDs, err = ss.Group().PermittedSyncableAdmins(channel.Id, model.GroupSyncableTypeChannel) require.NoError(t, err) require.ElementsMatch(t, []string{user3.Id}, actualUserIDs) } func groupTestUpdateMembersRoleTeam(t *testing.T, rctx request.CTX, ss store.Store) { team := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, err := ss.Team().Save(team) require.NoError(t, err) user1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err = ss.User().Save(rctx, user1) require.NoError(t, err) t.Log("Created user1", user1.Id) user2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err = ss.User().Save(rctx, user2) require.NoError(t, err) t.Log("Created user2", user2.Id) user3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, err = ss.User().Save(rctx, user3) require.NoError(t, err) t.Log("Created user3", user3.Id) user4 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user4, err = ss.User().Save(rctx, user4) require.NoError(t, err) t.Log("Created user4", user4.Id) for _, user := range []*model.User{user1, user2, user3} { _, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: team.Id, UserId: user.Id}, 9999) require.NoError(t, nErr) } _, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: team.Id, UserId: user4.Id, SchemeGuest: true}, 9999) require.NoError(t, nErr) tests := []struct { testName string newAdmins []string expectedUpdatedUsers []string }{ { "Two new admins", []string{user1.Id, user2.Id}, []string{user1.Id, user2.Id}, }, { "Demote one admin", []string{user1.Id}, []string{user2.Id}, }, { "Operation is idempotent", []string{user1.Id}, nil, }, { "Promote a team member", []string{user1.Id, user3.Id}, []string{user3.Id}, }, { "Guests never get promoted", []string{user1.Id, user3.Id, user4.Id}, nil, }, } for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { var updatedMembers []*model.TeamMember updatedMembers, err = ss.Team().UpdateMembersRole(team.Id, tt.newAdmins) require.NoError(t, err) var updatedUserIDs []string for _, member := range updatedMembers { assert.False(t, member.SchemeGuest, fmt.Sprintf("userID: %s", member.UserId)) if slices.Contains(tt.newAdmins, member.UserId) { assert.True(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) } else { assert.False(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) } updatedUserIDs = append(updatedUserIDs, member.UserId) } assert.ElementsMatch(t, tt.expectedUpdatedUsers, updatedUserIDs) members, err := ss.Team().GetMembers(team.Id, 0, 100, nil) require.NoError(t, err) assert.GreaterOrEqual(t, len(members), 4) // sanity check for team membership for _, member := range members { // Ensure guest account never changes. if member.UserId == user4.Id { assert.False(t, member.SchemeUser, fmt.Sprintf("userID: %s", member.UserId)) assert.False(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) assert.True(t, member.SchemeGuest, fmt.Sprintf("userID: %s", member.UserId)) } else { if slices.Contains(tt.newAdmins, member.UserId) { assert.True(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) } else { assert.False(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) } } } }) } } func groupTestpUpdateMembersRoleChannel(t *testing.T, rctx request.CTX, ss store.Store) { channel := &model.Channel{ TeamId: model.NewId(), DisplayName: "A Name", Name: model.NewId(), Type: model.ChannelTypeOpen, // Query does not look at type so this shouldn't matter. } channel, err := ss.Channel().Save(rctx, channel, 9999) require.NoError(t, err) user1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err = ss.User().Save(rctx, user1) require.NoError(t, err) t.Log("Created user1", user1.Id) user2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err = ss.User().Save(rctx, user2) require.NoError(t, err) t.Log("Created user2", user2.Id) user3 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user3, err = ss.User().Save(rctx, user3) require.NoError(t, err) t.Log("Created user3", user3.Id) user4 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user4, err = ss.User().Save(rctx, user4) require.NoError(t, err) t.Log("Created user4", user4.Id) for _, user := range []*model.User{user1, user2, user3} { _, err = ss.Channel().SaveMember(rctx, &model.ChannelMember{ ChannelId: channel.Id, UserId: user.Id, NotifyProps: model.GetDefaultChannelNotifyProps(), }) require.NoError(t, err) } _, err = ss.Channel().SaveMember(rctx, &model.ChannelMember{ ChannelId: channel.Id, UserId: user4.Id, NotifyProps: model.GetDefaultChannelNotifyProps(), SchemeGuest: true, }) require.NoError(t, err) tests := []struct { testName string newAdmins []string expectedUpdatedUsers []string }{ { "Two new admins", []string{user1.Id, user2.Id}, []string{user1.Id, user2.Id}, }, { "Demote one admin", []string{user1.Id}, []string{user2.Id}, }, { "Operation is idempotent", []string{user1.Id}, nil, }, { "Promote a team member", []string{user1.Id, user3.Id}, []string{user3.Id}, }, { "Guests never get promoted", []string{user1.Id, user3.Id, user4.Id}, nil, }, } for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { var updatedMemmbers []*model.ChannelMember updatedMemmbers, err = ss.Channel().UpdateMembersRole(channel.Id, tt.newAdmins) require.NoError(t, err) var updatedUserIDs []string for _, member := range updatedMemmbers { assert.False(t, member.SchemeGuest, fmt.Sprintf("userID: %s", member.UserId)) if slices.Contains(tt.newAdmins, member.UserId) { assert.True(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) } else { assert.False(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) } updatedUserIDs = append(updatedUserIDs, member.UserId) } assert.ElementsMatch(t, tt.expectedUpdatedUsers, updatedUserIDs) members, err := ss.Channel().GetMembers(model.ChannelMembersGetOptions{ChannelID: channel.Id, Offset: 0, Limit: 100}) require.NoError(t, err) assert.GreaterOrEqual(t, len(members), 4) // sanity check for channel membership for _, member := range members { // Ensure guest account never changes. if member.UserId == user4.Id { assert.False(t, member.SchemeUser, fmt.Sprintf("userID: %s", member.UserId)) assert.False(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) assert.True(t, member.SchemeGuest, fmt.Sprintf("userID: %s", member.UserId)) } else { if slices.Contains(tt.newAdmins, member.UserId) { assert.True(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) } else { assert.False(t, member.SchemeAdmin, fmt.Sprintf("userID: %s", member.UserId)) } } } }) } } func groupTestGroupCount(t *testing.T, rctx request.CTX, ss store.Store) { group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group1.Id) count, err := ss.Group().GroupCount() require.NoError(t, err) require.GreaterOrEqual(t, count, int64(1)) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group2.Id) countAfter, err := ss.Group().GroupCount() require.NoError(t, err) require.GreaterOrEqual(t, countAfter, count+1) } func groupTestGroupTeamCount(t *testing.T, rctx request.CTX, ss store.Store) { team, err := ss.Team().Save(&model.Team{ DisplayName: model.NewId(), Description: model.NewId(), AllowOpenInvite: false, InviteId: model.NewId(), Name: NewTestID(), Email: model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, }) require.NoError(t, err) defer ss.Team().PermanentDelete(team.Id) group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group1.Id) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group2.Id) groupSyncable1, err := ss.Group().CreateGroupSyncable(model.NewGroupTeam(group1.Id, team.Id, false)) require.NoError(t, err) defer ss.Group().DeleteGroupSyncable(groupSyncable1.GroupId, groupSyncable1.SyncableId, groupSyncable1.Type) count, err := ss.Group().GroupTeamCount() require.NoError(t, err) require.GreaterOrEqual(t, count, int64(1)) groupSyncable2, err := ss.Group().CreateGroupSyncable(model.NewGroupTeam(group2.Id, team.Id, false)) require.NoError(t, err) defer ss.Group().DeleteGroupSyncable(groupSyncable2.GroupId, groupSyncable2.SyncableId, groupSyncable2.Type) countAfter, err := ss.Group().GroupTeamCount() require.NoError(t, err) require.GreaterOrEqual(t, countAfter, count+1) } func groupTestGroupChannelCount(t *testing.T, rctx request.CTX, ss store.Store) { channel, err := ss.Channel().Save(rctx, &model.Channel{ TeamId: model.NewId(), DisplayName: model.NewId(), Name: model.NewId(), Type: model.ChannelTypeOpen, }, 9999) require.NoError(t, err) defer ss.Channel().Delete(channel.Id, 0) group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group1.Id) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group2.Id) groupSyncable1, err := ss.Group().CreateGroupSyncable(model.NewGroupChannel(group1.Id, channel.Id, false)) require.NoError(t, err) defer ss.Group().DeleteGroupSyncable(groupSyncable1.GroupId, groupSyncable1.SyncableId, groupSyncable1.Type) count, err := ss.Group().GroupChannelCount() require.NoError(t, err) require.GreaterOrEqual(t, count, int64(1)) groupSyncable2, err := ss.Group().CreateGroupSyncable(model.NewGroupChannel(group2.Id, channel.Id, false)) require.NoError(t, err) defer ss.Group().DeleteGroupSyncable(groupSyncable2.GroupId, groupSyncable2.SyncableId, groupSyncable2.Type) countAfter, err := ss.Group().GroupChannelCount() require.NoError(t, err) require.GreaterOrEqual(t, countAfter, count+1) } func groupTestGroupMemberCount(t *testing.T, rctx request.CTX, ss store.Store) { user := &model.User{ Email: fmt.Sprintf("test.%s@localhost", model.NewId()), Username: model.NewUsername(), } user, err := ss.User().Save(rctx, user) require.NoError(t, err) user2 := &model.User{ Email: fmt.Sprintf("test.%s@localhost", model.NewId()), Username: model.NewUsername(), } user2, err = ss.User().Save(rctx, user2) require.NoError(t, err) group, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group.Id) member1, err := ss.Group().UpsertMember(group.Id, user.Id) require.NoError(t, err) defer ss.Group().DeleteMember(group.Id, member1.UserId) count, err := ss.Group().GroupMemberCount() require.NoError(t, err) require.GreaterOrEqual(t, count, int64(1)) member2, err := ss.Group().UpsertMember(group.Id, user2.Id) require.NoError(t, err) defer ss.Group().DeleteMember(group.Id, member2.UserId) countAfter, err := ss.Group().GroupMemberCount() require.NoError(t, err) require.GreaterOrEqual(t, countAfter, count+1) } func groupTestDistinctGroupMemberCount(t *testing.T, rctx request.CTX, ss store.Store) { ss.DropAllTables() // Create two groups group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group1.Id) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group2.Id) // Create two users user1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, err = ss.User().Save(rctx, user1) require.NoError(t, err) user2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, err = ss.User().Save(rctx, user2) require.NoError(t, err) // Add user1 to group1 member1, err := ss.Group().UpsertMember(group1.Id, user1.Id) require.NoError(t, err) defer ss.Group().DeleteMember(group1.Id, member1.UserId) // Verify count is now 1 count, err := ss.Group().DistinctGroupMemberCount() require.NoError(t, err) require.Equal(t, int64(1), count) // Add user2 to group1 _, err = ss.Group().UpsertMember(group1.Id, user2.Id) require.NoError(t, err) // Verify count is now 2 countAfter1, err := ss.Group().DistinctGroupMemberCount() require.NoError(t, err) require.Equal(t, int64(2), countAfter1) // Add user1 to group2 as well _, err = ss.Group().UpsertMember(group2.Id, user1.Id) require.NoError(t, err) // Verify count stays at 2 (user1 is already counted) countAfter2, err := ss.Group().DistinctGroupMemberCount() require.NoError(t, err) require.Equal(t, countAfter1, countAfter2) } func groupTestGroupCountWithAllowReference(t *testing.T, rctx request.CTX, ss store.Store) { initialCount, err := ss.Group().GroupCountWithAllowReference() require.NoError(t, err) group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) defer ss.Group().Delete(group1.Id) count, err := ss.Group().GroupCountWithAllowReference() require.NoError(t, err) require.Equal(t, count, initialCount) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), AllowReference: true, }) require.NoError(t, err) defer ss.Group().Delete(group2.Id) countAfter, err := ss.Group().GroupCountWithAllowReference() require.NoError(t, err) require.Greater(t, countAfter, count) } func groupTestGetMember(t *testing.T, rctx request.CTX, ss store.Store) { g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, nErr := ss.User().Save(rctx, u2) require.NoError(t, nErr) _, err = ss.Group().UpsertMember(group.Id, user1.Id) require.NoError(t, err) member, err := ss.Group().GetMember(g1.Id, u1.Id) require.NoError(t, err) require.NotNil(t, member) member, err = ss.Group().GetMember(g1.Id, user2.Id) require.Error(t, err) require.Nil(t, member) } func groupTestGetNonMemberUsersPage(t *testing.T, rctx request.CTX, ss store.Store) { g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } group, err := ss.Group().Create(g1) require.NoError(t, err) u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } _, nErr = ss.User().Save(rctx, u2) require.NoError(t, nErr) users, err := ss.Group().GetNonMemberUsersPage(group.Id, 0, 1000, nil) require.NoError(t, err) originalLen := len(users) _, err = ss.Group().UpsertMember(group.Id, user1.Id) require.NoError(t, err) users, err = ss.Group().GetNonMemberUsersPage(group.Id, 0, 1000, nil) require.NoError(t, err) require.Len(t, users, originalLen-1) users, err = ss.Group().GetNonMemberUsersPage(model.NewId(), 0, 1000, nil) require.Error(t, err) require.Nil(t, users) } func groupTestDistinctGroupMemberCountForSource(t *testing.T, rctx request.CTX, ss store.Store) { // get the before counts customGroupCountBefore, err := ss.Group().DistinctGroupMemberCountForSource(model.GroupSourceCustom) require.NoError(t, err) ldapGroupCountBefore, err := ss.Group().DistinctGroupMemberCountForSource(model.GroupSourceLdap) require.NoError(t, err) // create 2 groups, 1 custom and 1 ldap g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), } customGroup, err := ss.Group().Create(g1) require.NoError(t, err) g2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } ldapGroup, err := ss.Group().Create(g2) require.NoError(t, err) // create a couple of users u1 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user1, nErr := ss.User().Save(rctx, u1) require.NoError(t, nErr) u2 := &model.User{ Email: MakeEmail(), Username: model.NewUsername(), } user2, nErr := ss.User().Save(rctx, u2) require.NoError(t, nErr) // add both new users to both new groups _, err = ss.Group().UpsertMember(customGroup.Id, user1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(ldapGroup.Id, user1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(customGroup.Id, user2.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(ldapGroup.Id, user2.Id) require.NoError(t, err) // remove one user from a group to ensure the 'where deleteat = 0' clause is working _, err = ss.Group().DeleteMember(ldapGroup.Id, user1.Id) require.NoError(t, err) defer func() { ss.Group().DeleteMember(ldapGroup.Id, user2.Id) ss.Group().DeleteMember(customGroup.Id, user1.Id) ss.Group().DeleteMember(customGroup.Id, user2.Id) ss.Group().Delete(customGroup.Id) ss.Group().Delete(ldapGroup.Id) ss.User().PermanentDelete(rctx, user1.Id) ss.User().PermanentDelete(rctx, user2.Id) }() customGroupCount, err := ss.Group().DistinctGroupMemberCountForSource(model.GroupSourceCustom) require.NoError(t, err) require.Equal(t, customGroupCountBefore+2, customGroupCount) ldapGroupCount, err := ss.Group().DistinctGroupMemberCountForSource(model.GroupSourceLdap) require.NoError(t, err) require.Equal(t, ldapGroupCountBefore+1, ldapGroupCount) } func groupTestGroupCountBySource(t *testing.T, rctx request.CTX, ss store.Store) { // Get initial counts for different sources customSourceCountBefore, err := ss.Group().GroupCountBySource(model.GroupSourceCustom) require.NoError(t, err) ldapSourceCountBefore, err := ss.Group().GroupCountBySource(model.GroupSourceLdap) require.NoError(t, err) // Create groups with different sources g1 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), } customGroup, err := ss.Group().Create(g1) require.NoError(t, err) g2 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } ldapGroup, err := ss.Group().Create(g2) require.NoError(t, err) g3 := &model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceLdap, RemoteId: model.NewPointer(model.NewId()), } ldapGroup2, err := ss.Group().Create(g3) require.NoError(t, err) defer func() { ss.Group().Delete(customGroup.Id) ss.Group().Delete(ldapGroup.Id) ss.Group().Delete(ldapGroup2.Id) }() // Check counts after creating groups customSourceCountAfter, err := ss.Group().GroupCountBySource(model.GroupSourceCustom) require.NoError(t, err) require.Equal(t, customSourceCountBefore+1, customSourceCountAfter) ldapSourceCountAfter, err := ss.Group().GroupCountBySource(model.GroupSourceLdap) require.NoError(t, err) require.Equal(t, ldapSourceCountBefore+2, ldapSourceCountAfter) // Delete one LDAP group and verify count decreases ss.Group().Delete(ldapGroup.Id) ldapSourceCountAfterDelete, err := ss.Group().GroupCountBySource(model.GroupSourceLdap) require.NoError(t, err) require.Equal(t, ldapSourceCountAfter-1, ldapSourceCountAfterDelete) } func testCountMembersMinusGroupMembers(t *testing.T, rctx request.CTX, ss store.Store) { // Create test users u1, err := ss.User().Save(rctx, &model.User{ Email: MakeEmail(), Username: model.NewUsername(), }) require.NoError(t, err) u2, err := ss.User().Save(rctx, &model.User{ Email: MakeEmail(), Username: model.NewUsername(), }) require.NoError(t, err) u3, err := ss.User().Save(rctx, &model.User{ Email: MakeEmail(), Username: model.NewUsername(), }) require.NoError(t, err) // Create test team team := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, err = ss.Team().Save(team) require.NoError(t, err) // Create a second team to test team-specific counts team2 := &model.Team{ DisplayName: "Name 2", Description: "Some description 2", CompanyName: "Some company name 2", AllowOpenInvite: false, InviteId: "inviteid1", Name: "z-z-" + model.NewId() + "b", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team2, err = ss.Team().Save(team2) require.NoError(t, err) // Create test channel in team 1 channel := &model.Channel{ TeamId: team.Id, DisplayName: "Display Name", Name: "z-z-" + model.NewId() + "a", Type: model.ChannelTypeOpen, } channel, nErr := ss.Channel().Save(rctx, channel, -1) require.NoError(t, nErr) // Create test channel in team 2 channel2 := &model.Channel{ TeamId: team2.Id, DisplayName: "Display Name 2", Name: "z-z-" + model.NewId() + "b", Type: model.ChannelTypeOpen, } channel2, nErr = ss.Channel().Save(rctx, channel2, -1) require.NoError(t, nErr) // Add users to teams and channels // u1 and u2 in team 1 _, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: team.Id, UserId: u1.Id}, -1) require.NoError(t, nErr) _, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: team.Id, UserId: u2.Id}, -1) require.NoError(t, nErr) // u3 in team 2 _, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: team2.Id, UserId: u3.Id}, -1) require.NoError(t, nErr) // u1 and u2 in channel 1 _, nErr = ss.Channel().SaveMember(rctx, &model.ChannelMember{ChannelId: channel.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()}) require.NoError(t, nErr) _, nErr = ss.Channel().SaveMember(rctx, &model.ChannelMember{ChannelId: channel.Id, UserId: u2.Id, NotifyProps: model.GetDefaultChannelNotifyProps()}) require.NoError(t, nErr) // u3 in channel 2 _, nErr = ss.Channel().SaveMember(rctx, &model.ChannelMember{ChannelId: channel2.Id, UserId: u3.Id, NotifyProps: model.GetDefaultChannelNotifyProps()}) require.NoError(t, nErr) // Create groups group1, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) group2, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) // Add u1 to group1, u3 to group2 _, err = ss.Group().UpsertMember(group1.Id, u1.Id) require.NoError(t, err) _, err = ss.Group().UpsertMember(group2.Id, u3.Id) require.NoError(t, err) // Test CountTeamMembersMinusGroupMembers with empty groupIDs count, err := ss.Group().CountTeamMembersMinusGroupMembers(team.Id, []string{}) require.NoError(t, err) require.Equal(t, int64(2), count) // Both u1 and u2 are counted when no groups are excluded // Test CountTeamMembersMinusGroupMembers with group1 ID count, err = ss.Group().CountTeamMembersMinusGroupMembers(team.Id, []string{group1.Id}) require.NoError(t, err) require.Equal(t, int64(1), count) // Only u2 should be counted (not in the group) // Test with non-existent team ID count, err = ss.Group().CountTeamMembersMinusGroupMembers(model.NewId(), []string{group1.Id}) require.NoError(t, err) require.Equal(t, int64(0), count) // No members in a non-existent team // Test with multiple group IDs count, err = ss.Group().CountTeamMembersMinusGroupMembers(team.Id, []string{group1.Id, group2.Id}) require.NoError(t, err) require.Equal(t, int64(1), count) // Only u2 should be counted // Test team 2 count, err = ss.Group().CountTeamMembersMinusGroupMembers(team2.Id, []string{group2.Id}) require.NoError(t, err) require.Equal(t, int64(0), count) // No one should be counted (u3 is in group2) // Test CountChannelMembersMinusGroupMembers with empty groupIDs count, err = ss.Group().CountChannelMembersMinusGroupMembers(channel.Id, []string{}) require.NoError(t, err) require.Equal(t, int64(2), count) // Both users are counted when no groups are excluded // Test CountChannelMembersMinusGroupMembers with the group ID count, err = ss.Group().CountChannelMembersMinusGroupMembers(channel.Id, []string{group1.Id}) require.NoError(t, err) require.Equal(t, int64(1), count) // Only u2 should be counted (not in the group) // Test with multiple group IDs count, err = ss.Group().CountChannelMembersMinusGroupMembers(channel.Id, []string{group1.Id, group2.Id}) require.NoError(t, err) require.Equal(t, int64(1), count) // u2 should be counted // Test channel 2 count, err = ss.Group().CountChannelMembersMinusGroupMembers(channel2.Id, []string{group2.Id}) require.NoError(t, err) require.Equal(t, int64(0), count) // No one should be counted (u3 is in group2) // Test with non-existent channel ID count, err = ss.Group().CountChannelMembersMinusGroupMembers(model.NewId(), []string{group1.Id}) require.NoError(t, err) require.Equal(t, int64(0), count) // No members in a non-existent channel // Test error cases - passing invalid parameters // 1. Empty team ID count, err = ss.Group().CountTeamMembersMinusGroupMembers("", []string{group1.Id}) require.NoError(t, err) // Should handle this gracefully require.Equal(t, int64(0), count) // 2. Empty channel ID count, err = ss.Group().CountChannelMembersMinusGroupMembers("", []string{group1.Id}) require.NoError(t, err) // Should handle this gracefully require.Equal(t, int64(0), count) } func testGroupStoreToModelChannelAssociations(t *testing.T, rctx request.CTX, ss store.Store) { // Create test group group, err := ss.Group().Create(&model.Group{ Name: model.NewPointer(model.NewId()), DisplayName: model.NewId(), Description: model.NewId(), Source: model.GroupSourceCustom, RemoteId: model.NewPointer(model.NewId()), }) require.NoError(t, err) require.NotNil(t, group) // Create test team team := &model.Team{ DisplayName: "Name", Description: "Some description", CompanyName: "Some company name", AllowOpenInvite: false, InviteId: "inviteid0", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TeamOpen, } team, err = ss.Team().Save(team) require.NoError(t, err) require.NotNil(t, team) // Create test channel 1 channel1 := &model.Channel{ TeamId: team.Id, DisplayName: "Display Name 1", Name: "z-z-" + model.NewId() + "a", Type: model.ChannelTypeOpen, } channel1, nErr := ss.Channel().Save(rctx, channel1, -1) require.NoError(t, nErr) require.NotNil(t, channel1) // Create test channel 2 channel2 := &model.Channel{ TeamId: team.Id, DisplayName: "Display Name 2", Name: "z-z-" + model.NewId() + "b", Type: model.ChannelTypeOpen, } channel2, nErr = ss.Channel().Save(rctx, channel2, -1) require.NoError(t, nErr) require.NotNil(t, channel2) // Create group channel syncables _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ GroupId: group.Id, SyncableId: channel1.Id, Type: model.GroupSyncableTypeChannel, SchemeAdmin: true, }) require.NoError(t, err) _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ GroupId: group.Id, SyncableId: channel2.Id, Type: model.GroupSyncableTypeChannel, SchemeAdmin: false, }) require.NoError(t, err) // Test the GetGroupsAssociatedToChannelsByTeam function // This exercises the groupsAssociatedToChannelWithSchemeAdmin.ToModel method result, err := ss.Group().GetGroupsAssociatedToChannelsByTeam(team.Id, model.GroupSearchOpts{}) require.NoError(t, err) require.NotNil(t, result) // Verify channel 1 results require.Contains(t, result, channel1.Id) require.NotEmpty(t, result[channel1.Id]) require.Equal(t, group.Id, result[channel1.Id][0].Id) require.NotNil(t, result[channel1.Id][0].SchemeAdmin) require.True(t, *result[channel1.Id][0].SchemeAdmin) // Verify channel 2 results (with different SchemeAdmin value) require.Contains(t, result, channel2.Id) require.NotEmpty(t, result[channel2.Id]) require.Equal(t, group.Id, result[channel2.Id][0].Id) require.NotNil(t, result[channel2.Id][0].SchemeAdmin) require.False(t, *result[channel2.Id][0].SchemeAdmin) }