diff --git a/api4/group.go b/api4/group.go index d90082f10c..33cc8c273f 100644 --- a/api4/group.go +++ b/api4/group.go @@ -55,6 +55,14 @@ func (api *API) InitGroup() { // GET /api/v4/groups/:group_id/members?page=0&per_page=100 api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members", api.ApiSessionRequired(getGroupMembers)).Methods("GET") + + // GET /api/v4/channels/:channel_id/groups?page=0&per_page=100 + api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/groups", + api.ApiSessionRequired(getGroupsByChannel)).Methods("GET") + + // GET /api/v4/teams/:team_id/groups?page=0&per_page=100 + api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/groups", + api.ApiSessionRequired(getGroupsByTeam)).Methods("GET") } func getGroup(c *Context, w http.ResponseWriter, r *http.Request) { @@ -431,3 +439,65 @@ func getGroupMembers(c *Context, w http.ResponseWriter, r *http.Request) { w.Write(b) } + +func getGroupsByChannel(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireChannelId() + if c.Err != nil { + return + } + + if c.App.License() == nil || !*c.App.License().Features.LDAPGroups { + c.Err = model.NewAppError("Api4.getGroupsByChannel", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented) + return + } + + if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + groups, err := c.App.GetGroupsByChannel(c.Params.ChannelId, c.Params.Page, c.Params.PerPage) + if err != nil { + c.Err = err + return + } + + b, marshalErr := json.Marshal(groups) + if marshalErr != nil { + c.Err = model.NewAppError("Api4.getGroupsByChannel", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError) + return + } + + w.Write(b) +} + +func getGroupsByTeam(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireTeamId() + if c.Err != nil { + return + } + + if c.App.License() == nil || !*c.App.License().Features.LDAPGroups { + c.Err = model.NewAppError("Api4.getGroupsByTeam", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented) + return + } + + if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + groups, err := c.App.GetGroupsByTeam(c.Params.TeamId, c.Params.Page, c.Params.PerPage) + if err != nil { + c.Err = err + return + } + + b, marshalErr := json.Marshal(groups) + if marshalErr != nil { + c.Err = model.NewAppError("Api4.getGroupsByTeam", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError) + return + } + + w.Write(b) +} diff --git a/api4/group_test.go b/api4/group_test.go index 9ed4211ecd..82846e3343 100644 --- a/api4/group_test.go +++ b/api4/group_test.go @@ -594,3 +594,91 @@ func TestPatchGroupChannel(t *testing.T) { _, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch) CheckUnauthorizedStatus(t, response) } + +func TestGetGroupsByChannel(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + id := model.NewId() + group, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id, + Name: "name" + id, + Source: model.GroupSourceLdap, + Description: "description_" + id, + RemoteId: model.NewId(), + }) + assert.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: th.BasicChannel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group.Id, + }) + assert.Nil(t, err) + + _, response := th.SystemAdminClient.GetGroupsByChannel("asdfasdf", 0, 60) + CheckBadRequestStatus(t, response) + + th.App.SetLicense(nil) + + _, response = th.SystemAdminClient.GetGroupsByChannel(th.BasicChannel.Id, 0, 60) + CheckNotImplementedStatus(t, response) + + th.App.SetLicense(model.NewTestLicense("ldap")) + + _, response = th.Client.GetGroupsByChannel(th.BasicChannel.Id, 0, 60) + CheckForbiddenStatus(t, response) + + groups, response := th.SystemAdminClient.GetGroupsByChannel(th.BasicChannel.Id, 0, 60) + assert.Nil(t, response.Error) + assert.ElementsMatch(t, []*model.Group{group}, groups) + + groups, response = th.SystemAdminClient.GetGroupsByChannel(model.NewId(), 0, 60) + assert.Nil(t, response.Error) + assert.Empty(t, groups) +} + +func TestGetGroupsByTeam(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + id := model.NewId() + group, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id, + Name: "name" + id, + Source: model.GroupSourceLdap, + Description: "description_" + id, + RemoteId: model.NewId(), + }) + assert.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: th.BasicTeam.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group.Id, + }) + assert.Nil(t, err) + + _, response := th.SystemAdminClient.GetGroupsByTeam("asdfasdf", 0, 60) + CheckBadRequestStatus(t, response) + + th.App.SetLicense(nil) + + _, response = th.SystemAdminClient.GetGroupsByTeam(th.BasicTeam.Id, 0, 60) + CheckNotImplementedStatus(t, response) + + th.App.SetLicense(model.NewTestLicense("ldap")) + + _, response = th.Client.GetGroupsByTeam(th.BasicTeam.Id, 0, 60) + CheckForbiddenStatus(t, response) + + groups, response := th.SystemAdminClient.GetGroupsByTeam(th.BasicTeam.Id, 0, 60) + assert.Nil(t, response.Error) + assert.ElementsMatch(t, []*model.Group{group}, groups) + + groups, response = th.SystemAdminClient.GetGroupsByTeam(model.NewId(), 0, 60) + assert.Nil(t, response.Error) + assert.Empty(t, groups) +} diff --git a/app/group.go b/app/group.go index a8b14f6fa6..32719c14bd 100644 --- a/app/group.go +++ b/app/group.go @@ -164,3 +164,19 @@ func (a *App) ChannelMembersToRemove() ([]*model.ChannelMember, *model.AppError) } return result.Data.([]*model.ChannelMember), nil } + +func (a *App) GetGroupsByChannel(channelId string, page, perPage int) ([]*model.Group, *model.AppError) { + result := <-a.Srv.Store.Group().GetGroupsByChannel(channelId, page, perPage) + if result.Err != nil { + return nil, result.Err + } + return result.Data.([]*model.Group), nil +} + +func (a *App) GetGroupsByTeam(teamId string, page, perPage int) ([]*model.Group, *model.AppError) { + result := <-a.Srv.Store.Group().GetGroupsByTeam(teamId, page, perPage) + if result.Err != nil { + return nil, result.Err + } + return result.Data.([]*model.Group), nil +} diff --git a/app/group_test.go b/app/group_test.go index 60afa3f008..c528a8bb36 100644 --- a/app/group_test.go +++ b/app/group_test.go @@ -197,3 +197,55 @@ func TestDeleteGroupSyncable(t *testing.T) { require.NotNil(t, err) require.Nil(t, gs) } + +func TestGetGroupsByChannel(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + group := th.CreateGroup() + + // Create a group channel + groupSyncable := &model.GroupSyncable{ + GroupId: group.Id, + AutoAdd: false, + SyncableId: th.BasicChannel.Id, + Type: model.GroupSyncableTypeChannel, + } + + gs, err := th.App.CreateGroupSyncable(groupSyncable) + require.Nil(t, err) + require.NotNil(t, gs) + + groups, err := th.App.GetGroupsByChannel(th.BasicChannel.Id, 0, 60) + require.Nil(t, err) + require.ElementsMatch(t, []*model.Group{group}, groups) + + groups, err = th.App.GetGroupsByChannel(model.NewId(), 0, 60) + require.Nil(t, err) + require.Empty(t, groups) +} + +func TestGetGroupsByTeam(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + group := th.CreateGroup() + + // Create a group team + groupSyncable := &model.GroupSyncable{ + GroupId: group.Id, + AutoAdd: false, + SyncableId: th.BasicTeam.Id, + Type: model.GroupSyncableTypeTeam, + } + + gs, err := th.App.CreateGroupSyncable(groupSyncable) + require.Nil(t, err) + require.NotNil(t, gs) + + groups, err := th.App.GetGroupsByTeam(th.BasicTeam.Id, 0, 60) + require.Nil(t, err) + require.ElementsMatch(t, []*model.Group{group}, groups) + + groups, err = th.App.GetGroupsByTeam(model.NewId(), 0, 60) + require.Nil(t, err) + require.Empty(t, groups) +} diff --git a/model/client4.go b/model/client4.go index 2736058259..3622c85310 100644 --- a/model/client4.go +++ b/model/client4.go @@ -3290,6 +3290,30 @@ func (c *Client4) UnlinkLdapGroup(dn string) (*Group, *Response) { return GroupFromJson(r.Body), BuildResponse(r) } +// GetLdapGroupsByChannel retrieves the Mattermost Groups associated with a given channel +func (c *Client4) GetGroupsByChannel(channelId string, page, perPage int) ([]*Group, *Response) { + path := fmt.Sprintf("%s/groups?page=%v&per_page=%v", c.GetChannelRoute(channelId), page, perPage) + r, appErr := c.DoApiGet(path, "") + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + defer closeBody(r) + + return GroupsFromJson(r.Body), BuildResponse(r) +} + +// GetLdapGroupsByTeam retrieves the Mattermost Groups associated with a given team +func (c *Client4) GetGroupsByTeam(teamId string, page, perPage int) ([]*Group, *Response) { + path := fmt.Sprintf("%s/groups?page=%v&per_page=%v", c.GetTeamRoute(teamId), page, perPage) + r, appErr := c.DoApiGet(path, "") + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + defer closeBody(r) + + return GroupsFromJson(r.Body), BuildResponse(r) +} + // Audits Section // GetAudits returns a list of audits for the whole system. diff --git a/store/layered_store.go b/store/layered_store.go index 70d72be571..bf008f20b1 100644 --- a/store/layered_store.go +++ b/store/layered_store.go @@ -477,3 +477,15 @@ func (s *LayeredGroupStore) ChannelMembersToRemove() StoreChannel { return supplier.ChannelMembersToRemove(s.TmpContext) }) } + +func (s *LayeredGroupStore) GetGroupsByChannel(channelId string, page, perPage int) StoreChannel { + return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult { + return supplier.GetGroupsByChannel(s.TmpContext, channelId, page, perPage) + }) +} + +func (s *LayeredGroupStore) GetGroupsByTeam(teamId string, page, perPage int) StoreChannel { + return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult { + return supplier.GetGroupsByTeam(s.TmpContext, teamId, page, perPage) + }) +} diff --git a/store/layered_store_supplier.go b/store/layered_store_supplier.go index 3487806a63..f263d2695d 100644 --- a/store/layered_store_supplier.go +++ b/store/layered_store_supplier.go @@ -73,4 +73,7 @@ type LayeredStoreSupplier interface { TeamMembersToRemove(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult ChannelMembersToRemove(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult + + GetGroupsByChannel(ctx context.Context, channelId string, page, perPage int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult + GetGroupsByTeam(ctx context.Context, teamId string, page, perPage int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult } diff --git a/store/local_cache_supplier_groups.go b/store/local_cache_supplier_groups.go index 2d62193bed..60ee2152f6 100644 --- a/store/local_cache_supplier_groups.go +++ b/store/local_cache_supplier_groups.go @@ -108,3 +108,11 @@ func (s *LocalCacheSupplier) TeamMembersToRemove(ctx context.Context, hints ...L func (s *LocalCacheSupplier) ChannelMembersToRemove(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { return s.Next().ChannelMembersToRemove(ctx, hints...) } + +func (s *LocalCacheSupplier) GetGroupsByChannel(ctx context.Context, channelId string, page, perPage int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + return s.Next().GetGroupsByChannel(ctx, channelId, page, perPage, hints...) +} + +func (s *LocalCacheSupplier) GetGroupsByTeam(ctx context.Context, teamId string, page, perPage int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + return s.Next().GetGroupsByTeam(ctx, teamId, page, perPage, hints...) +} diff --git a/store/redis_supplier_groups.go b/store/redis_supplier_groups.go index e057a20c75..aa4bbb4279 100644 --- a/store/redis_supplier_groups.go +++ b/store/redis_supplier_groups.go @@ -108,3 +108,13 @@ func (s *RedisSupplier) ChannelMembersToRemove(ctx context.Context, hints ...Lay // TODO: Redis caching. return s.Next().ChannelMembersToRemove(ctx, hints...) } + +func (s *RedisSupplier) GetGroupsByChannel(ctx context.Context, channelId string, page, perPage int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // TODO: Redis caching. + return s.Next().GetGroupsByChannel(ctx, channelId, page, perPage, hints...) +} + +func (s *RedisSupplier) GetGroupsByTeam(ctx context.Context, teamId string, page, perPage int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // TODO: Redis caching. + return s.Next().GetGroupsByTeam(ctx, teamId, page, perPage, hints...) +} diff --git a/store/sqlstore/group_supplier.go b/store/sqlstore/group_supplier.go index 11b05fa2d0..cc802dfacf 100644 --- a/store/sqlstore/group_supplier.go +++ b/store/sqlstore/group_supplier.go @@ -222,13 +222,13 @@ func (s *SqlSupplier) GroupGetMemberUsers(stc context.Context, groupID string, h var groupMembers []*model.User query := ` - SELECT - Users.* - FROM - GroupMembers + SELECT + Users.* + FROM + GroupMembers JOIN Users ON Users.Id = GroupMembers.UserId - WHERE - GroupMembers.DeleteAt = 0 + WHERE + GroupMembers.DeleteAt = 0 AND Users.DeleteAt = 0 AND GroupId = :GroupId` @@ -248,13 +248,13 @@ func (s *SqlSupplier) GroupGetMemberUsersPage(stc context.Context, groupID strin var groupMembers []*model.User query := ` - SELECT - Users.* - FROM - GroupMembers + SELECT + Users.* + FROM + GroupMembers JOIN Users ON Users.Id = GroupMembers.UserId - WHERE - GroupMembers.DeleteAt = 0 + WHERE + GroupMembers.DeleteAt = 0 AND Users.DeleteAt = 0 AND GroupId = :GroupId ORDER BY @@ -281,11 +281,11 @@ func (s *SqlSupplier) GroupGetMemberCount(stc context.Context, groupID string, h var err error query := ` - SELECT - count(*) - FROM - GroupMembers - WHERE + SELECT + count(*) + FROM + GroupMembers + WHERE GroupMembers.GroupId = :GroupId` if count, err = s.GetReplica().SelectInt(query, map[string]interface{}{"GroupId": groupID}); err != nil { @@ -506,13 +506,13 @@ func (s *SqlSupplier) GroupGetAllGroupSyncablesByGroup(ctx context.Context, grou case model.GroupSyncableTypeTeam: sqlQuery := ` SELECT - GroupTeams.*, - Teams.DisplayName AS TeamDisplayName, + GroupTeams.*, + Teams.DisplayName AS TeamDisplayName, Teams.Type AS TeamType - FROM + FROM GroupTeams JOIN Teams ON Teams.Id = GroupTeams.TeamId - WHERE + WHERE GroupId = :GroupId AND GroupTeams.DeleteAt = 0` results := []*groupTeamJoin{} @@ -538,17 +538,17 @@ func (s *SqlSupplier) GroupGetAllGroupSyncablesByGroup(ctx context.Context, grou case model.GroupSyncableTypeChannel: sqlQuery := ` SELECT - GroupChannels.*, - Channels.DisplayName AS ChannelDisplayName, + GroupChannels.*, + Channels.DisplayName AS ChannelDisplayName, Teams.DisplayName AS TeamDisplayName, Channels.Type As ChannelType, Teams.Type As TeamType, Teams.Id AS TeamId - FROM - GroupChannels + FROM + GroupChannels JOIN Channels ON Channels.Id = GroupChannels.ChannelId JOIN Teams ON Teams.Id = Channels.TeamId - WHERE + WHERE GroupId = :GroupId AND GroupChannels.DeleteAt = 0` results := []*groupChannelJoin{} @@ -676,19 +676,19 @@ func (s *SqlSupplier) TeamMembersToAdd(ctx context.Context, since int64, hints . result := store.NewSupplierResult() sql := ` - SELECT + SELECT GroupMembers.UserId, GroupTeams.TeamId - FROM + FROM GroupMembers - JOIN GroupTeams + JOIN GroupTeams ON GroupTeams.GroupId = GroupMembers.GroupId JOIN UserGroups ON UserGroups.Id = GroupMembers.GroupId JOIN Teams ON Teams.Id = GroupTeams.TeamId - LEFT OUTER JOIN TeamMembers - ON - TeamMembers.TeamId = GroupTeams.TeamId + LEFT OUTER JOIN TeamMembers + ON + TeamMembers.TeamId = GroupTeams.TeamId AND TeamMembers.UserId = GroupMembers.UserId - WHERE + WHERE TeamMembers.UserId IS NULL AND UserGroups.DeleteAt = 0 AND GroupTeams.DeleteAt = 0 @@ -718,16 +718,16 @@ func (s *SqlSupplier) ChannelMembersToAdd(ctx context.Context, since int64, hint result := store.NewSupplierResult() sql := ` - SELECT + SELECT GroupMembers.UserId, GroupChannels.ChannelId - FROM + FROM GroupMembers JOIN GroupChannels ON GroupChannels.GroupId = GroupMembers.GroupId JOIN UserGroups ON UserGroups.Id = GroupMembers.GroupId JOIN Channels ON Channels.Id = GroupChannels.ChannelId - LEFT OUTER JOIN ChannelMemberHistory - ON - ChannelMemberHistory.ChannelId = GroupChannels.ChannelId + LEFT OUTER JOIN ChannelMemberHistory + ON + ChannelMemberHistory.ChannelId = GroupChannels.ChannelId AND ChannelMemberHistory.UserId = GroupMembers.UserId WHERE ChannelMemberHistory.UserId IS NULL @@ -811,6 +811,40 @@ func (s *SqlSupplier) TeamMembersToRemove(ctx context.Context, hints ...store.La return result } +func (s *SqlSupplier) GetGroupsByChannel(ctx context.Context, channelId string, page, perPage int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + result := store.NewSupplierResult() + + var groups []*model.Group + offset := page * perPage + _, err := s.GetReplica().Select(&groups, ` + SELECT + ug.* + FROM + GroupChannels gc + LEFT JOIN + UserGroups ug + ON + gc.GroupId = ug.Id + WHERE + ug.DeleteAt = 0 + AND + gc.ChannelId = :ChannelId + ORDER BY + ug.DisplayName + LIMIT :Limit + OFFSET :Offset`, + map[string]interface{}{"ChannelId": channelId, "Limit": perPage, "Offset": offset}) + + if err != nil { + result.Err = model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError) + return result + } + + result.Data = groups + + return result +} + // ChannelMembersToRemove returns all channel members that should be removed based on group constraints. func (s *SqlSupplier) ChannelMembersToRemove(ctx context.Context, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { result := store.NewSupplierResult() @@ -854,3 +888,37 @@ func (s *SqlSupplier) ChannelMembersToRemove(ctx context.Context, hints ...store return result } + +func (s *SqlSupplier) GetGroupsByTeam(ctx context.Context, teamId string, page, perPage int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + result := store.NewSupplierResult() + + var groups []*model.Group + offset := page * perPage + _, err := s.GetReplica().Select(&groups, ` + SELECT + ug.* + FROM + GroupTeams gt + LEFT JOIN + UserGroups ug + ON + gt.GroupId = ug.Id + WHERE + ug.DeleteAt = 0 + AND + gt.TeamId = :TeamId + ORDER BY + ug.DisplayName + LIMIT :Limit + OFFSET :Offset`, + map[string]interface{}{"TeamId": teamId, "Limit": perPage, "Offset": offset}) + + if err != nil { + result.Err = model.NewAppError("SqlGroupStore.GetGroupsByTeam", "store.select_error", nil, err.Error(), http.StatusInternalServerError) + return result + } + + result.Data = groups + + return result +} diff --git a/store/store.go b/store/store.go index af2255e21e..47d008e0ed 100644 --- a/store/store.go +++ b/store/store.go @@ -585,6 +585,9 @@ type GroupStore interface { TeamMembersToRemove() StoreChannel ChannelMembersToRemove() StoreChannel + + GetGroupsByChannel(channelId string, page, perPage int) StoreChannel + GetGroupsByTeam(teamId string, page, perPage int) StoreChannel } type LinkMetadataStore interface { diff --git a/store/storetest/group_supplier.go b/store/storetest/group_supplier.go index 1f5ed7e5ff..479b451747 100644 --- a/store/storetest/group_supplier.go +++ b/store/storetest/group_supplier.go @@ -36,6 +36,9 @@ func TestGroupStore(t *testing.T, ss store.Store) { t.Run("TeamMembersToRemove", func(t *testing.T) { testPendingTeamMemberRemovals(t, ss) }) t.Run("ChannelMembersToRemove", func(t *testing.T) { testPendingChannelMemberRemovals(t, ss) }) + + t.Run("GetGroupsByChannel", func(t *testing.T) { testGetGroupsByChannel(t, ss) }) + t.Run("GetGroupsByTeam", func(t *testing.T) { testGetGroupsByTeam(t, ss) }) } func testGroupStoreCreate(t *testing.T, ss store.Store) { @@ -1507,3 +1510,261 @@ func pendingMemberRemovalsDataSetup(t *testing.T, ss store.Store) *removalsData Group: group, } } + +func testGetGroupsByChannel(t *testing.T, ss store.Store) { + // Create Channel1 + channel1 := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "Channel1", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + } + res := <-ss.Channel().Save(channel1, 9999) + require.Nil(t, res.Err) + channel1 = res.Data.(*model.Channel) + + // Create Groups 1 and 2 + res = <-ss.Group().Create(&model.Group{ + Name: model.NewId(), + DisplayName: "group-1", + RemoteId: model.NewId(), + Source: model.GroupSourceLdap, + }) + require.Nil(t, res.Err) + group1 := res.Data.(*model.Group) + + res = <-ss.Group().Create(&model.Group{ + Name: model.NewId(), + DisplayName: "group-2", + RemoteId: model.NewId(), + Source: model.GroupSourceLdap, + }) + require.Nil(t, res.Err) + group2 := res.Data.(*model.Group) + + // And associate them with Channel1 + for _, g := range []*model.Group{group1, group2} { + res = <-ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel1.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: g.Id, + }) + require.Nil(t, res.Err) + } + + // Create Channel2 + channel2 := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "Channel2", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + } + res = <-ss.Channel().Save(channel2, 9999) + require.Nil(t, res.Err) + channel2 = res.Data.(*model.Channel) + + // Create Group3 + res = <-ss.Group().Create(&model.Group{ + Name: model.NewId(), + DisplayName: "group-3", + RemoteId: model.NewId(), + Source: model.GroupSourceLdap, + }) + require.Nil(t, res.Err) + group3 := res.Data.(*model.Group) + + // And associate it to Channel2 + res = <-ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel2.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group3.Id, + }) + require.Nil(t, res.Err) + + testCases := []struct { + Name string + ChannelId string + Page int + PerPage int + Result []*model.Group + }{ + { + Name: "Get the two Groups for Channel1", + ChannelId: channel1.Id, + Page: 0, + PerPage: 60, + Result: []*model.Group{group1, group2}, + }, + { + Name: "Get first Group for Channel1 with page 0 with 1 element", + ChannelId: channel1.Id, + Page: 0, + PerPage: 1, + Result: []*model.Group{group1}, + }, + { + Name: "Get second Group for Channel1 with page 1 with 1 element", + ChannelId: channel1.Id, + Page: 1, + PerPage: 1, + Result: []*model.Group{group2}, + }, + { + Name: "Get third Group for Channel2", + ChannelId: channel2.Id, + Page: 0, + PerPage: 60, + Result: []*model.Group{group3}, + }, + { + Name: "Get empty Groups for a fake id", + ChannelId: model.NewId(), + Page: 0, + PerPage: 60, + Result: []*model.Group{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + res := <-ss.Group().GetGroupsByChannel(tc.ChannelId, tc.Page, tc.PerPage) + require.Nil(t, res.Err) + require.ElementsMatch(t, tc.Result, res.Data.([]*model.Group)) + }) + } +} + +func testGetGroupsByTeam(t *testing.T, ss store.Store) { + // Create Team1 + team1 := &model.Team{ + DisplayName: "Team1", + Description: model.NewId(), + CompanyName: model.NewId(), + AllowOpenInvite: false, + InviteId: model.NewId(), + Name: model.NewId(), + Email: "success+" + model.NewId() + "@simulator.amazonses.com", + Type: model.TEAM_OPEN, + } + res := <-ss.Team().Save(team1) + require.Nil(t, res.Err) + team1 = res.Data.(*model.Team) + + // Create Groups 1 and 2 + res = <-ss.Group().Create(&model.Group{ + Name: model.NewId(), + DisplayName: "group-1", + RemoteId: model.NewId(), + Source: model.GroupSourceLdap, + }) + require.Nil(t, res.Err) + group1 := res.Data.(*model.Group) + + res = <-ss.Group().Create(&model.Group{ + Name: model.NewId(), + DisplayName: "group-2", + RemoteId: model.NewId(), + Source: model.GroupSourceLdap, + }) + require.Nil(t, res.Err) + group2 := res.Data.(*model.Group) + + // And associate them with Team1 + for _, g := range []*model.Group{group1, group2} { + res = <-ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: team1.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: g.Id, + }) + require.Nil(t, res.Err) + } + + // Create Team2 + team2 := &model.Team{ + DisplayName: "Team2", + Description: model.NewId(), + CompanyName: model.NewId(), + AllowOpenInvite: false, + InviteId: model.NewId(), + Name: model.NewId(), + Email: "success+" + model.NewId() + "@simulator.amazonses.com", + Type: model.TEAM_INVITE, + } + res = <-ss.Team().Save(team2) + require.Nil(t, res.Err) + team2 = res.Data.(*model.Team) + + // Create Group3 + res = <-ss.Group().Create(&model.Group{ + Name: model.NewId(), + DisplayName: "group-3", + RemoteId: model.NewId(), + Source: model.GroupSourceLdap, + }) + require.Nil(t, res.Err) + group3 := res.Data.(*model.Group) + + // And associate it to Team2 + res = <-ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: team2.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group3.Id, + }) + require.Nil(t, res.Err) + + testCases := []struct { + Name string + TeamId string + Page int + PerPage int + Result []*model.Group + }{ + { + Name: "Get the two Groups for Team1", + TeamId: team1.Id, + Page: 0, + PerPage: 60, + Result: []*model.Group{group1, group2}, + }, + { + Name: "Get first Group for Team1 with page 0 with 1 element", + TeamId: team1.Id, + Page: 0, + PerPage: 1, + Result: []*model.Group{group1}, + }, + { + Name: "Get second Group for Team1 with page 1 with 1 element", + TeamId: team1.Id, + Page: 1, + PerPage: 1, + Result: []*model.Group{group2}, + }, + { + Name: "Get third Group for Team2", + TeamId: team2.Id, + Page: 0, + PerPage: 60, + Result: []*model.Group{group3}, + }, + { + Name: "Get empty Groups for a fake id", + TeamId: model.NewId(), + Page: 0, + PerPage: 60, + Result: []*model.Group{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + res := <-ss.Group().GetGroupsByTeam(tc.TeamId, tc.Page, tc.PerPage) + require.Nil(t, res.Err) + require.ElementsMatch(t, tc.Result, res.Data.([]*model.Group)) + }) + } +} diff --git a/store/storetest/mocks/GroupStore.go b/store/storetest/mocks/GroupStore.go index 6933ffcffd..b66202bf4b 100644 --- a/store/storetest/mocks/GroupStore.go +++ b/store/storetest/mocks/GroupStore.go @@ -189,6 +189,38 @@ func (_m *GroupStore) GetGroupSyncable(groupID string, syncableID string, syncab return r0 } +// GetGroupsByChannel provides a mock function with given fields: channelId, page, perPage +func (_m *GroupStore) GetGroupsByChannel(channelId string, page int, perPage int) store.StoreChannel { + ret := _m.Called(channelId, page, perPage) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func(string, int, int) store.StoreChannel); ok { + r0 = rf(channelId, page, perPage) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} + +// GetGroupsByTeam provides a mock function with given fields: teamId, page, perPage +func (_m *GroupStore) GetGroupsByTeam(teamId string, page int, perPage int) store.StoreChannel { + ret := _m.Called(teamId, page, perPage) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func(string, int, int) store.StoreChannel); ok { + r0 = rf(teamId, page, perPage) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} + // GetMemberCount provides a mock function with given fields: groupID func (_m *GroupStore) GetMemberCount(groupID string) store.StoreChannel { ret := _m.Called(groupID) diff --git a/store/storetest/mocks/LayeredStoreDatabaseLayer.go b/store/storetest/mocks/LayeredStoreDatabaseLayer.go index 167cdc9196..fe803cbc2c 100644 --- a/store/storetest/mocks/LayeredStoreDatabaseLayer.go +++ b/store/storetest/mocks/LayeredStoreDatabaseLayer.go @@ -184,6 +184,52 @@ func (_m *LayeredStoreDatabaseLayer) FileInfo() store.FileInfoStore { return r0 } +// GetGroupsByChannel provides a mock function with given fields: ctx, channelId, page, perPage, hints +func (_m *LayeredStoreDatabaseLayer) GetGroupsByChannel(ctx context.Context, channelId string, page int, perPage int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, channelId, page, perPage) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, channelId, page, perPage, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + +// GetGroupsByTeam provides a mock function with given fields: ctx, teamId, page, perPage, hints +func (_m *LayeredStoreDatabaseLayer) GetGroupsByTeam(ctx context.Context, teamId string, page int, perPage int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, teamId, page, perPage) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, teamId, page, perPage, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + // Group provides a mock function with given fields: func (_m *LayeredStoreDatabaseLayer) Group() store.GroupStore { ret := _m.Called() diff --git a/store/storetest/mocks/LayeredStoreSupplier.go b/store/storetest/mocks/LayeredStoreSupplier.go index 6f37fb5867..070f441cf3 100644 --- a/store/storetest/mocks/LayeredStoreSupplier.go +++ b/store/storetest/mocks/LayeredStoreSupplier.go @@ -14,6 +14,52 @@ type LayeredStoreSupplier struct { mock.Mock } +// GetGroupsByChannel provides a mock function with given fields: ctx, channelId, page, perPage, hints +func (_m *LayeredStoreSupplier) GetGroupsByChannel(ctx context.Context, channelId string, page int, perPage int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, channelId, page, perPage) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, channelId, page, perPage, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + +// GetGroupsByTeam provides a mock function with given fields: ctx, teamId, page, perPage, hints +func (_m *LayeredStoreSupplier) GetGroupsByTeam(ctx context.Context, teamId string, page int, perPage int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, teamId, page, perPage) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, teamId, page, perPage, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + // GroupCreate provides a mock function with given fields: ctx, group, hints func (_m *LayeredStoreSupplier) GroupCreate(ctx context.Context, group *model.Group, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { _va := make([]interface{}, len(hints))