diff --git a/app/group.go b/app/group.go index da04ca357c..02805547b4 100644 --- a/app/group.go +++ b/app/group.go @@ -150,41 +150,33 @@ func (a *App) ChannelMembersToRemove() ([]*model.ChannelMember, *model.AppError) } func (a *App) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, int, *model.AppError) { - result := <-a.Srv.Store.Group().GetGroupsByChannel(channelId, opts) - if result.Err != nil { - return nil, 0, result.Err + groups, err := a.Srv.Store.Group().GetGroupsByChannel(channelId, opts) + if err != nil { + return nil, 0, err } - groups := result.Data.([]*model.Group) - result = <-a.Srv.Store.Group().CountGroupsByChannel(channelId, opts) - if result.Err != nil { - return nil, 0, result.Err + count, err := a.Srv.Store.Group().CountGroupsByChannel(channelId, opts) + if err != nil { + return nil, 0, err } - count := result.Data.(int64) return groups, int(count), nil } func (a *App) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, int, *model.AppError) { - result := <-a.Srv.Store.Group().GetGroupsByTeam(teamId, opts) - if result.Err != nil { - return nil, 0, result.Err + groups, err := a.Srv.Store.Group().GetGroupsByTeam(teamId, opts) + if err != nil { + return nil, 0, err } - groups := result.Data.([]*model.Group) - result = <-a.Srv.Store.Group().CountGroupsByTeam(teamId, opts) - if result.Err != nil { - return nil, 0, result.Err + count, err := a.Srv.Store.Group().CountGroupsByTeam(teamId, opts) + if err != nil { + return nil, 0, err } - count := result.Data.(int64) return groups, int(count), nil } func (a *App) GetGroups(page, perPage int, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { - result := <-a.Srv.Store.Group().GetGroups(page, perPage, opts) - if result.Err != nil { - return nil, result.Err - } - return result.Data.([]*model.Group), nil + return a.Srv.Store.Group().GetGroups(page, perPage, opts) } diff --git a/store/sqlstore/group_supplier.go b/store/sqlstore/group_supplier.go index e9ba052fb7..4da8b8fc85 100644 --- a/store/sqlstore/group_supplier.go +++ b/store/sqlstore/group_supplier.go @@ -836,57 +836,43 @@ func (s *SqlGroupStore) TeamMembersToRemove() ([]*model.TeamMember, *model.AppEr return teamMembers, nil } -func (s *SqlGroupStore) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) store.StoreChannel { - return store.Do(func(result *store.StoreResult) { +func (s *SqlGroupStore) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) (int64, *model.AppError) { + countQuery := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectCountGroups, channelId, opts) - countQuery := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectCountGroups, channelId, opts) + countQueryString, args, err := countQuery.ToSql() + if err != nil { + return int64(0), model.NewAppError("SqlGroupStore.CountGroupsByChannel", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) + } - countQueryString, args, err := countQuery.ToSql() - if err != nil { - result.Err = model.NewAppError("SqlGroupStore.CountGroupsByChannel", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } + count, err := s.GetReplica().SelectInt(countQueryString, args...) + if err != nil { + return int64(0), model.NewAppError("SqlGroupStore.CountGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError) + } - count, err := s.GetReplica().SelectInt(countQueryString, args...) - if err != nil { - result.Err = model.NewAppError("SqlGroupStore.CountGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - result.Data = count - - return - }) + return count, nil } -func (s *SqlGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) store.StoreChannel { - return store.Do(func(result *store.StoreResult) { +func (s *SqlGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { + query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectGroups, channelId, opts) - query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectGroups, channelId, opts) + if opts.PageOpts != nil { + offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage) + query = query.OrderBy("ug.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset) + } - if opts.PageOpts != nil { - offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage) - query = query.OrderBy("ug.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset) - } + queryString, args, err := query.ToSql() + if err != nil { + return nil, model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) + } - queryString, args, err := query.ToSql() - if err != nil { - result.Err = model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } + var groups []*model.Group - var groups []*model.Group + _, err = s.GetReplica().Select(&groups, queryString, args...) + if err != nil { + return nil, model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError) + } - _, err = s.GetReplica().Select(&groups, queryString, args...) - if err != nil { - result.Err = model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - result.Data = groups - - return - }) + return groups, nil } // ChannelMembersToRemove returns all channel members that should be removed based on group constraints. @@ -985,86 +971,71 @@ func (s *SqlGroupStore) groupsBySyncableBaseQuery(st model.GroupSyncableType, t return query } -func (s *SqlGroupStore) CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) store.StoreChannel { - return store.Do(func(result *store.StoreResult) { +func (s *SqlGroupStore) CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) (int64, *model.AppError) { + countQuery := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectCountGroups, teamId, opts) - countQuery := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectCountGroups, teamId, opts) + countQueryString, args, err := countQuery.ToSql() + if err != nil { + return int64(0), model.NewAppError("SqlGroupStore.CountGroupsByTeam", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) + } - countQueryString, args, err := countQuery.ToSql() - if err != nil { - result.Err = model.NewAppError("SqlGroupStore.CountGroupsByTeam", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } + count, err := s.GetReplica().SelectInt(countQueryString, args...) + if err != nil { + return int64(0), model.NewAppError("SqlGroupStore.CountGroupsByTeam", "store.select_error", nil, err.Error(), http.StatusInternalServerError) + } - count, err := s.GetReplica().SelectInt(countQueryString, args...) - if err != nil { - result.Err = model.NewAppError("SqlGroupStore.CountGroupsByTeam", "store.select_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - result.Data = count - - return - }) + return count, nil } -func (s *SqlGroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) store.StoreChannel { - return store.Do(func(result *store.StoreResult) { +func (s *SqlGroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { + query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectGroups, teamId, opts) - query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectGroups, teamId, opts) + if opts.PageOpts != nil { + offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage) + query = query.OrderBy("ug.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset) + } - if opts.PageOpts != nil { - offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage) - query = query.OrderBy("ug.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset) - } + queryString, args, err := query.ToSql() + if err != nil { + return nil, model.NewAppError("SqlGroupStore.GetGroupsByTeam", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) + } - queryString, args, err := query.ToSql() - if err != nil { - result.Err = model.NewAppError("SqlGroupStore.GetGroupsByTeam", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } + var groups []*model.Group - var groups []*model.Group + _, err = s.GetReplica().Select(&groups, queryString, args...) + if err != nil { + return nil, model.NewAppError("SqlGroupStore.GetGroupsByTeam", "store.select_error", nil, err.Error(), http.StatusInternalServerError) + } - _, err = s.GetReplica().Select(&groups, queryString, args...) - if err != nil { - result.Err = model.NewAppError("SqlGroupStore.GetGroupsByTeam", "store.select_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - result.Data = groups - - return - }) + return groups, nil } -func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts) store.StoreChannel { - return store.Do(func(result *store.StoreResult) { - var groups []*model.Group +func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { + var groups []*model.Group - groupsQuery := s.getQueryBuilder().Select("g.*").From("UserGroups g").Limit(uint64(perPage)).Offset(uint64(page * perPage)).OrderBy("g.DisplayName") + groupsQuery := s.getQueryBuilder().Select("g.*").From("UserGroups g").Limit(uint64(perPage)).Offset(uint64(page * perPage)).OrderBy("g.DisplayName") - if opts.IncludeMemberCount { - groupsQuery = s.getQueryBuilder(). - Select("g.*, coalesce(Members.MemberCount, 0) AS MemberCount"). - From("UserGroups g"). - LeftJoin("(SELECT GroupMembers.GroupId, COUNT(*) AS MemberCount FROM GroupMembers WHERE GroupMembers.DeleteAt = 0 GROUP BY GroupId) AS Members ON Members.GroupId = g.Id"). - Limit(uint64(perPage)). - Offset(uint64(page * perPage)). - OrderBy("g.DisplayName") + if opts.IncludeMemberCount { + groupsQuery = s.getQueryBuilder(). + Select("g.*, coalesce(Members.MemberCount, 0) AS MemberCount"). + From("UserGroups g"). + LeftJoin("(SELECT GroupMembers.GroupId, COUNT(*) AS MemberCount FROM GroupMembers WHERE GroupMembers.DeleteAt = 0 GROUP BY GroupId) AS Members ON Members.GroupId = g.Id"). + Limit(uint64(perPage)). + Offset(uint64(page * perPage)). + OrderBy("g.DisplayName") + } + + if len(opts.Q) > 0 { + pattern := fmt.Sprintf("%%%s%%", opts.Q) + operatorKeyword := "ILIKE" + if s.DriverName() == model.DATABASE_DRIVER_MYSQL { + operatorKeyword = "LIKE" } + groupsQuery = groupsQuery.Where(fmt.Sprintf("(g.Name %[1]s ? OR g.DisplayName %[1]s ?)", operatorKeyword), pattern, pattern) + } - if len(opts.Q) > 0 { - pattern := fmt.Sprintf("%%%s%%", opts.Q) - operatorKeyword := "ILIKE" - if s.DriverName() == model.DATABASE_DRIVER_MYSQL { - operatorKeyword = "LIKE" - } - groupsQuery = groupsQuery.Where(fmt.Sprintf("(g.Name %[1]s ? OR g.DisplayName %[1]s ?)", operatorKeyword), pattern, pattern) - } - - if len(opts.NotAssociatedToTeam) == 26 { - groupsQuery = groupsQuery.Where(` + if len(opts.NotAssociatedToTeam) == 26 { + groupsQuery = groupsQuery.Where(` g.Id NOT IN ( SELECT Id @@ -1077,10 +1048,10 @@ func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts) AND GroupTeams.TeamId = ? ) `, opts.NotAssociatedToTeam) - } + } - if len(opts.NotAssociatedToChannel) == 26 { - groupsQuery = groupsQuery.Where(` + if len(opts.NotAssociatedToChannel) == 26 { + groupsQuery = groupsQuery.Where(` g.Id NOT IN ( SELECT Id @@ -1093,20 +1064,16 @@ func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts) AND GroupChannels.ChannelId = ? ) `, opts.NotAssociatedToChannel) - } + } - queryString, args, err := groupsQuery.ToSql() - if err != nil { - result.Err = model.NewAppError("SqlGroupStore.GetGroups", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } + queryString, args, err := groupsQuery.ToSql() + if err != nil { + return nil, model.NewAppError("SqlGroupStore.GetGroups", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) + } - if _, err = s.GetReplica().Select(&groups, queryString, args...); err != nil { - result.Err = model.NewAppError("SqlGroupStore.GetGroups", "store.select_error", nil, err.Error(), http.StatusInternalServerError) - return - } + if _, err = s.GetReplica().Select(&groups, queryString, args...); err != nil { + return nil, model.NewAppError("SqlGroupStore.GetGroups", "store.select_error", nil, err.Error(), http.StatusInternalServerError) + } - result.Data = groups - return - }) + return groups, nil } diff --git a/store/store.go b/store/store.go index e0bfb8a580..d54c679914 100644 --- a/store/store.go +++ b/store/store.go @@ -594,13 +594,13 @@ type GroupStore interface { TeamMembersToRemove() ([]*model.TeamMember, *model.AppError) ChannelMembersToRemove() ([]*model.ChannelMember, *model.AppError) - GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) StoreChannel - CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) StoreChannel + GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) + CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) (int64, *model.AppError) - GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) StoreChannel - CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) StoreChannel + GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) + CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) (int64, *model.AppError) - GetGroups(page, perPage int, opts model.GroupSearchOpts) StoreChannel + GetGroups(page, perPage int, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) } type LinkMetadataStore interface { diff --git a/store/storetest/group_supplier.go b/store/storetest/group_supplier.go index 446d92d1cc..9c46b5e689 100644 --- a/store/storetest/group_supplier.go +++ b/store/storetest/group_supplier.go @@ -1729,12 +1729,12 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { } tc.Opts.PageOpts.Page = tc.Page tc.Opts.PageOpts.PerPage = tc.PerPage - res := <-ss.Group().GetGroupsByChannel(tc.ChannelId, tc.Opts) - require.Nil(t, res.Err) - require.ElementsMatch(t, tc.Result, res.Data.([]*model.Group)) + groups, err := ss.Group().GetGroupsByChannel(tc.ChannelId, tc.Opts) + require.Nil(t, err) + require.ElementsMatch(t, tc.Result, groups) if tc.TotalCount != nil { - res = <-ss.Group().CountGroupsByChannel(tc.ChannelId, tc.Opts) - count := res.Data.(int64) + var count int64 + count, err = ss.Group().CountGroupsByChannel(tc.ChannelId, tc.Opts) require.Equal(t, *tc.TotalCount, count) } }) @@ -1931,13 +1931,12 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { } tc.Opts.PageOpts.Page = tc.Page tc.Opts.PageOpts.PerPage = tc.PerPage - res := <-ss.Group().GetGroupsByTeam(tc.TeamId, tc.Opts) - require.Nil(t, res.Err) - groups := res.Data.([]*model.Group) + groups, err := ss.Group().GetGroupsByTeam(tc.TeamId, tc.Opts) + require.Nil(t, err) require.ElementsMatch(t, tc.Result, groups) if tc.TotalCount != nil { - res = <-ss.Group().CountGroupsByTeam(tc.TeamId, tc.Opts) - count := res.Data.(int64) + var count int64 + count, err = ss.Group().CountGroupsByTeam(tc.TeamId, tc.Opts) require.Equal(t, *tc.TotalCount, count) } }) @@ -2209,9 +2208,8 @@ func testGetGroups(t *testing.T, ss store.Store) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - res := <-ss.Group().GetGroups(tc.Page, tc.PerPage, tc.Opts) - require.Nil(t, res.Err) - groups := res.Data.([]*model.Group) + groups, err := ss.Group().GetGroups(tc.Page, tc.PerPage, tc.Opts) + require.Nil(t, err) require.True(t, tc.Resultf(groups)) }) } diff --git a/store/storetest/mocks/GroupStore.go b/store/storetest/mocks/GroupStore.go index 5274f525c0..e416cc92b4 100644 --- a/store/storetest/mocks/GroupStore.go +++ b/store/storetest/mocks/GroupStore.go @@ -64,35 +64,49 @@ func (_m *GroupStore) ChannelMembersToRemove() ([]*model.ChannelMember, *model.A } // CountGroupsByChannel provides a mock function with given fields: channelId, opts -func (_m *GroupStore) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) store.StoreChannel { +func (_m *GroupStore) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) (int64, *model.AppError) { ret := _m.Called(channelId, opts) - var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) store.StoreChannel); ok { + var r0 int64 + if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) int64); ok { r0 = rf(channelId, opts) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(store.StoreChannel) + r0 = ret.Get(0).(int64) + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, model.GroupSearchOpts) *model.AppError); ok { + r1 = rf(channelId, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) } } - return r0 + return r0, r1 } // CountGroupsByTeam provides a mock function with given fields: teamId, opts -func (_m *GroupStore) CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) store.StoreChannel { +func (_m *GroupStore) CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) (int64, *model.AppError) { ret := _m.Called(teamId, opts) - var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) store.StoreChannel); ok { + var r0 int64 + if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) int64); ok { r0 = rf(teamId, opts) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(store.StoreChannel) + r0 = ret.Get(0).(int64) + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, model.GroupSearchOpts) *model.AppError); ok { + r1 = rf(teamId, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) } } - return r0 + return r0, r1 } // Create provides a mock function with given fields: group @@ -272,51 +286,78 @@ func (_m *GroupStore) GetGroupSyncable(groupID string, syncableID string, syncab } // GetGroups provides a mock function with given fields: page, perPage, opts -func (_m *GroupStore) GetGroups(page int, perPage int, opts model.GroupSearchOpts) store.StoreChannel { +func (_m *GroupStore) GetGroups(page int, perPage int, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { ret := _m.Called(page, perPage, opts) - var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(int, int, model.GroupSearchOpts) store.StoreChannel); ok { + var r0 []*model.Group + if rf, ok := ret.Get(0).(func(int, int, model.GroupSearchOpts) []*model.Group); ok { r0 = rf(page, perPage, opts) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(store.StoreChannel) + r0 = ret.Get(0).([]*model.Group) } } - return r0 + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(int, int, model.GroupSearchOpts) *model.AppError); ok { + r1 = rf(page, perPage, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 } // GetGroupsByChannel provides a mock function with given fields: channelId, opts -func (_m *GroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) store.StoreChannel { +func (_m *GroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { ret := _m.Called(channelId, opts) - var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) store.StoreChannel); ok { + var r0 []*model.Group + if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) []*model.Group); ok { r0 = rf(channelId, opts) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(store.StoreChannel) + r0 = ret.Get(0).([]*model.Group) } } - return r0 + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, model.GroupSearchOpts) *model.AppError); ok { + r1 = rf(channelId, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 } // GetGroupsByTeam provides a mock function with given fields: teamId, opts -func (_m *GroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) store.StoreChannel { +func (_m *GroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { ret := _m.Called(teamId, opts) - var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) store.StoreChannel); ok { + var r0 []*model.Group + if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) []*model.Group); ok { r0 = rf(teamId, opts) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(store.StoreChannel) + r0 = ret.Get(0).([]*model.Group) } } - return r0 + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, model.GroupSearchOpts) *model.AppError); ok { + r1 = rf(teamId, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 } // GetMemberCount provides a mock function with given fields: groupID