[MM-13500] Adds channel /search_group endpoint (#10805)

* [MM-13500] Adds channel /search_group endpoint

* Add LIMIT to the queries

* Fix i18n extract

* Fix tests

* Add a new endpoint to get profiles by group channel ids

* Rebase fix
Этот коммит содержится в:
Miguel de la Cruz
2019-06-22 00:14:21 +01:00
коммит произвёл GitHub
родитель 604e247135
Коммит 9e9b008f3d
15 изменённых файлов: 655 добавлений и 5 удалений

Просмотреть файл

@@ -18,6 +18,7 @@ func (api *API) InitChannel() {
api.BaseRoutes.Channels.Handle("", api.ApiSessionRequired(createChannel)).Methods("POST")
api.BaseRoutes.Channels.Handle("/direct", api.ApiSessionRequired(createDirectChannel)).Methods("POST")
api.BaseRoutes.Channels.Handle("/search", api.ApiSessionRequired(searchAllChannels)).Methods("POST")
api.BaseRoutes.Channels.Handle("/group/search", api.ApiSessionRequired(searchGroupChannels)).Methods("POST")
api.BaseRoutes.Channels.Handle("/group", api.ApiSessionRequired(createGroupChannel)).Methods("POST")
api.BaseRoutes.Channels.Handle("/members/{user_id:[A-Za-z0-9]+}/view", api.ApiSessionRequired(viewChannel)).Methods("POST")
api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/scheme", api.ApiSessionRequired(updateChannelScheme)).Methods("PUT")
@@ -356,6 +357,22 @@ func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(sc.ToJson()))
}
func searchGroupChannels(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.ChannelSearchFromJson(r.Body)
if props == nil {
c.SetInvalidParam("channel_search")
return
}
groupChannels, err := c.App.SearchGroupChannels(c.App.Session.UserId, props.Term)
if err != nil {
c.Err = err
return
}
w.Write([]byte(groupChannels.ToJson()))
}
func createGroupChannel(c *Context, w http.ResponseWriter, r *http.Request) {
userIds := model.ArrayFromJson(r.Body)

Просмотреть файл

@@ -918,6 +918,64 @@ func TestSearchAllChannels(t *testing.T) {
CheckForbiddenStatus(t, resp)
}
func TestSearchGroupChannels(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
u1 := th.CreateUserWithClient(th.SystemAdminClient)
// Create a group channel in which base user belongs but not sysadmin
gc1, resp := th.Client.CreateGroupChannel([]string{th.BasicUser.Id, th.BasicUser2.Id, u1.Id})
CheckNoError(t, resp)
defer th.Client.DeleteChannel(gc1.Id)
gc2, resp := th.Client.CreateGroupChannel([]string{th.BasicUser.Id, th.BasicUser2.Id, th.SystemAdminUser.Id})
CheckNoError(t, resp)
defer th.Client.DeleteChannel(gc2.Id)
search := &model.ChannelSearch{Term: th.BasicUser2.Username}
// sysadmin should only find gc2 as he doesn't belong to gc1
channels, resp := th.SystemAdminClient.SearchGroupChannels(search)
CheckNoError(t, resp)
assert.Len(t, channels, 1)
assert.Equal(t, channels[0].Id, gc2.Id)
// basic user should find both
Client.Login(th.BasicUser.Username, th.BasicUser.Password)
channels, resp = Client.SearchGroupChannels(search)
CheckNoError(t, resp)
assert.Len(t, channels, 2)
channelIds := []string{}
for _, c := range channels {
channelIds = append(channelIds, c.Id)
}
assert.ElementsMatch(t, channelIds, []string{gc1.Id, gc2.Id})
// searching for sysadmin, it should only find gc1
search = &model.ChannelSearch{Term: th.SystemAdminUser.Username}
channels, resp = Client.SearchGroupChannels(search)
CheckNoError(t, resp)
assert.Len(t, channels, 1)
assert.Equal(t, channels[0].Id, gc2.Id)
// with an empty search, response should be empty
search = &model.ChannelSearch{Term: ""}
channels, resp = Client.SearchGroupChannels(search)
CheckNoError(t, resp)
assert.Len(t, channels, 0)
// search unprivileged, forbidden
th.Client.Logout()
_, resp = Client.SearchAllChannels(search)
CheckUnauthorizedStatus(t, resp)
}
func TestDeleteChannel(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()

Просмотреть файл

@@ -4,6 +4,7 @@
package api4
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
@@ -25,6 +26,7 @@ func (api *API) InitUser() {
api.BaseRoutes.Users.Handle("/search", api.ApiSessionRequired(searchUsers)).Methods("POST")
api.BaseRoutes.Users.Handle("/autocomplete", api.ApiSessionRequired(autocompleteUsers)).Methods("GET")
api.BaseRoutes.Users.Handle("/stats", api.ApiSessionRequired(getTotalUsersStats)).Methods("GET")
api.BaseRoutes.Users.Handle("/group_channels", api.ApiSessionRequired(getUsersByGroupChannelIds)).Methods("POST")
api.BaseRoutes.User.Handle("", api.ApiSessionRequired(getUser)).Methods("GET")
api.BaseRoutes.User.Handle("/image/default", api.ApiSessionRequiredTrustRequester(getDefaultProfileImage)).Methods("GET")
@@ -447,6 +449,24 @@ func getTotalUsersStats(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(stats.ToJson()))
}
func getUsersByGroupChannelIds(c *Context, w http.ResponseWriter, r *http.Request) {
channelIds := model.ArrayFromJson(r.Body)
if len(channelIds) == 0 {
c.SetInvalidParam("channel_ids")
return
}
usersByChannelId, err := c.App.GetUsersByGroupChannelIds(channelIds, c.IsSystemAdmin())
if err != nil {
c.Err = err
return
}
b, _ := json.Marshal(usersByChannelId)
w.Write(b)
}
func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
inTeamId := r.URL.Query().Get("in_team")
notInTeamId := r.URL.Query().Get("not_in_team")

Просмотреть файл

@@ -1127,6 +1127,35 @@ func TestGetUsersByIds(t *testing.T) {
CheckUnauthorizedStatus(t, resp)
}
func TestGetUsersByGroupChannelIds(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
gc1, err := th.App.CreateGroupChannel([]string{th.BasicUser.Id, th.SystemAdminUser.Id, th.TeamAdminUser.Id}, th.BasicUser.Id)
require.Nil(t, err)
usersByChannelId, resp := th.Client.GetUsersByGroupChannelIds([]string{gc1.Id})
CheckNoError(t, resp)
users, _ := usersByChannelId[gc1.Id]
userIds := []string{}
for _, user := range users {
userIds = append(userIds, user.Id)
}
require.ElementsMatch(t, []string{th.SystemAdminUser.Id, th.TeamAdminUser.Id}, userIds)
th.LoginBasic2()
usersByChannelId, resp = th.Client.GetUsersByGroupChannelIds([]string{gc1.Id})
_, ok := usersByChannelId[gc1.Id]
require.False(t, ok)
th.Client.Logout()
_, resp = th.Client.GetUsersByGroupChannelIds([]string{gc1.Id})
CheckUnauthorizedStatus(t, resp)
}
func TestGetUsersByUsernames(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()

Просмотреть файл

@@ -1801,6 +1801,18 @@ func (a *App) SearchChannels(teamId string, term string) (*model.ChannelList, *m
return a.Srv.Store.Channel().SearchInTeam(teamId, term, includeDeleted)
}
func (a *App) SearchGroupChannels(userId, term string) (*model.ChannelList, *model.AppError) {
if term == "" {
return &model.ChannelList{}, nil
}
channelList, err := a.Srv.Store.Channel().SearchGroupChannels(userId, term)
if err != nil {
return nil, err
}
return channelList, nil
}
func (a *App) SearchChannelsUserNotIn(teamId string, userId string, term string) (*model.ChannelList, *model.AppError) {
term = strings.TrimSpace(term)
return a.Srv.Store.Channel().SearchMore(userId, teamId, term)

Просмотреть файл

@@ -637,6 +637,18 @@ func (a *App) GetUsersByIds(userIds []string, asAdmin bool, viewRestrictions *mo
return a.sanitizeProfiles(result.Data.([]*model.User), asAdmin), nil
}
func (a *App) GetUsersByGroupChannelIds(channelIds []string, asAdmin bool) (map[string][]*model.User, *model.AppError) {
usersByChannelId, err := a.Srv.Store.User().GetProfileByGroupChannelIdsForUser(a.Session.UserId, channelIds)
if err != nil {
return nil, err
}
for channelId, userList := range usersByChannelId {
usersByChannelId[channelId] = a.sanitizeProfiles(userList, asAdmin)
}
return usersByChannelId, nil
}
func (a *App) GetUsersByUsernames(usernames []string, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
result := <-a.Srv.Store.User().GetProfilesByUsernames(usernames, viewRestrictions)
if result.Err != nil {

Просмотреть файл

@@ -5658,6 +5658,10 @@
"id": "store.sql_channel.search.app_error",
"translation": "We encountered an error searching channels"
},
{
"id": "store.sql_channel.search_group_channels.app_error",
"translation": "Unable to get the group channels for the given user and term"
},
{
"id": "store.sql_channel.set_delete_at.commit_transaction.app_error",
"translation": "Unable to commit transaction"
@@ -6758,6 +6762,10 @@
"id": "store.sql_user.get_new_users.app_error",
"translation": "We encountered an error while finding the new users"
},
{
"id": "store.sql_user.get_profile_by_group_channel_ids_for_user.app_error",
"translation": "We encountered an error while finding user profiles"
},
{
"id": "store.sql_user.get_profiles.app_error",
"translation": "We encountered an error while finding user profiles"

Просмотреть файл

@@ -946,6 +946,20 @@ func (c *Client4) GetUsersByUsernames(usernames []string) ([]*User, *Response) {
return UserListFromJson(r.Body), BuildResponse(r)
}
// GetUsersByGroupChannelIds returns a map with channel ids as keys
// and a list of users as values based on the provided user ids.
func (c *Client4) GetUsersByGroupChannelIds(groupChannelIds []string) (map[string][]*User, *Response) {
r, err := c.DoApiPost(c.GetUsersRoute()+"/group_channels", ArrayToJson(groupChannelIds))
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
usersByChannelId := map[string][]*User{}
json.NewDecoder(r.Body).Decode(&usersByChannelId)
return usersByChannelId, BuildResponse(r)
}
// SearchUsers returns a list of users based on some search criteria.
func (c *Client4) SearchUsers(search *UserSearch) ([]*User, *Response) {
r, err := c.doApiPostBytes(c.GetUsersRoute()+"/search", search.ToJson())
@@ -2056,6 +2070,16 @@ func (c *Client4) SearchAllChannels(search *ChannelSearch) (*ChannelListWithTeam
return ChannelListWithTeamDataFromJson(r.Body), BuildResponse(r)
}
// SearchGroupChannels returns the group channels of the user whose members' usernames match the search term.
func (c *Client4) SearchGroupChannels(search *ChannelSearch) ([]*Channel, *Response) {
r, err := c.DoApiPost(c.GetChannelsRoute()+"/group/search", search.ToJson())
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return ChannelSliceFromJson(r.Body), BuildResponse(r)
}
// DeleteChannel deletes channel based on the provided channel id string.
func (c *Client4) DeleteChannel(channelId string) (bool, *Response) {
r, err := c.DoApiDelete(c.GetChannelRoute(channelId))

Просмотреть файл

@@ -1473,7 +1473,7 @@ func (s SqlChannelStore) GetMemberForPost(postId string, userId string) (*model.
Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id
WHERE
ChannelMembers.UserId = :UserId
AND
AND
Posts.Id = :PostId`
if err := s.GetReplica().SelectOne(&dbMember, query, map[string]interface{}{"UserId": userId, "PostId": postId}); err != nil {
return nil, model.NewAppError("SqlChannelStore.GetMemberForPost", "store.sql_channel.get_member_for_post.app_error", nil, "postId="+postId+", err="+err.Error(), http.StatusInternalServerError)
@@ -2237,6 +2237,104 @@ func (s SqlChannelStore) performSearch(searchQuery string, term string, paramete
return &channels, nil
}
func (s SqlChannelStore) getSearchGroupChannelsQuery(userId, term string, isPostgreSQL bool) (string, map[string]interface{}) {
var query, baseLikeClause string
if isPostgreSQL {
baseLikeClause = "ARRAY_TO_STRING(ARRAY_AGG(u.Username), ', ') LIKE %s"
query = `
SELECT
*
FROM
Channels
WHERE
Id IN (
SELECT
cc.Id
FROM (
SELECT
c.Id
FROM
Channels c
JOIN
ChannelMembers cm on c.Id = cm.ChannelId
JOIN
Users u on u.Id = cm.UserId
WHERE
c.Type = 'G'
AND
u.Id = :UserId
GROUP BY
c.Id
) cc
JOIN
ChannelMembers cm on cc.Id = cm.ChannelId
JOIN
Users u on u.Id = cm.UserId
GROUP BY
cc.Id
HAVING
%s
LIMIT
` + strconv.Itoa(model.CHANNEL_SEARCH_DEFAULT_LIMIT) + `
)`
} else {
baseLikeClause = "GROUP_CONCAT(u.Username SEPARATOR ', ') LIKE %s"
query = `
SELECT
cc.*
FROM (
SELECT
c.*
FROM
Channels c
JOIN
ChannelMembers cm on c.Id = cm.ChannelId
JOIN
Users u on u.Id = cm.UserId
WHERE
c.Type = 'G'
AND
u.Id = :UserId
GROUP BY
c.Id
) cc
JOIN
ChannelMembers cm on cc.Id = cm.ChannelId
JOIN
Users u on u.Id = cm.UserId
GROUP BY
cc.Id
HAVING
%s
LIMIT
` + strconv.Itoa(model.CHANNEL_SEARCH_DEFAULT_LIMIT)
}
var likeClauses []string
args := map[string]interface{}{"UserId": userId}
terms := strings.Split(strings.ToLower(strings.Trim(term, " ")), " ")
for idx, term := range terms {
argName := fmt.Sprintf("Term%v", idx)
likeClauses = append(likeClauses, fmt.Sprintf(baseLikeClause, ":"+argName))
args[argName] = "%" + term + "%"
}
query = fmt.Sprintf(query, strings.Join(likeClauses, " AND "))
return query, args
}
func (s SqlChannelStore) SearchGroupChannels(userId, term string) (*model.ChannelList, *model.AppError) {
isPostgreSQL := s.DriverName() == model.DATABASE_DRIVER_POSTGRES
queryString, args := s.getSearchGroupChannelsQuery(userId, term, isPostgreSQL)
var groupChannels model.ChannelList
if _, err := s.GetReplica().Select(&groupChannels, queryString, args); err != nil {
return nil, model.NewAppError("SqlChannelStore.SearchGroupChannels", "store.sql_channel.search_group_channels.app_error", nil, "userId="+userId+", term="+term+", err="+err.Error(), http.StatusInternalServerError)
}
return &groupChannels, nil
}
func (s SqlChannelStore) GetMembersByIds(channelId string, userIds []string) (*model.ChannelMembers, *model.AppError) {
var dbMembers channelMemberWithSchemeRolesList
props := make(map[string]interface{})

Просмотреть файл

@@ -20,10 +20,11 @@ import (
)
const (
PROFILES_IN_CHANNEL_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins
PROFILE_BY_IDS_CACHE_SIZE = model.SESSION_CACHE_SIZE
PROFILE_BY_IDS_CACHE_SEC = 900 // 15 mins
PROFILES_IN_CHANNEL_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins
PROFILE_BY_IDS_CACHE_SIZE = model.SESSION_CACHE_SIZE
PROFILE_BY_IDS_CACHE_SEC = 900 // 15 mins
MAX_GROUP_CHANNELS_FOR_PROFILES = 50
)
var (
@@ -920,6 +921,60 @@ func (us SqlUserStore) GetProfileByIds(userIds []string, allowFromCache bool, vi
})
}
type UserWithChannel struct {
model.User
ChannelId string
}
func (us SqlUserStore) GetProfileByGroupChannelIdsForUser(userId string, channelIds []string) (map[string][]*model.User, *model.AppError) {
if len(channelIds) > MAX_GROUP_CHANNELS_FOR_PROFILES {
channelIds = channelIds[0:MAX_GROUP_CHANNELS_FOR_PROFILES]
}
isMemberQuery := fmt.Sprintf(`
EXISTS(
SELECT
1
FROM
ChannelMembers
WHERE
UserId = '%s'
AND
ChannelId = cm.ChannelId
)`, userId)
query := us.getQueryBuilder().
Select("u.*, cm.ChannelId").
From("Users u").
Join("ChannelMembers cm ON u.Id = cm.UserId").
Join("Channels c ON cm.ChannelId = c.Id").
Where(sq.Eq{"c.Type": model.CHANNEL_GROUP, "cm.ChannelId": channelIds}).
Where(isMemberQuery).
Where(sq.NotEq{"u.Id": userId}).
OrderBy("u.Username ASC")
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlUserStore.GetProfileByGroupChannelIdsForUser", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
usersWithChannel := []*UserWithChannel{}
if _, err := us.GetReplica().Select(&usersWithChannel, queryString, args...); err != nil {
return nil, model.NewAppError("SqlUserStore.GetProfileByGroupChannelIdsForUser", "store.sql_user.get_profile_by_group_channel_ids_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
usersByChannelId := map[string][]*model.User{}
for _, user := range usersWithChannel {
if val, ok := usersByChannelId[user.ChannelId]; ok {
usersByChannelId[user.ChannelId] = append(val, &user.User)
} else {
usersByChannelId[user.ChannelId] = []*model.User{&user.User}
}
}
return usersByChannelId, nil
}
func (us SqlUserStore) GetSystemAdminProfiles() store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := us.usersQuery.

Просмотреть файл

@@ -185,6 +185,7 @@ type ChannelStore interface {
SearchAllChannels(term string, opts ChannelSearchOpts) StoreChannel
SearchInTeam(teamId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError)
SearchMore(userId string, teamId string, term string) (*model.ChannelList, *model.AppError)
SearchGroupChannels(userId, term string) (*model.ChannelList, *model.AppError)
GetMembersByIds(channelId string, userIds []string) (*model.ChannelMembers, *model.AppError)
AnalyticsDeletedTypeCount(teamId string, channelType string) (int64, *model.AppError)
GetChannelUnread(channelId, userId string) (*model.ChannelUnread, *model.AppError)
@@ -267,6 +268,7 @@ type UserStore interface {
GetAllProfiles(options *model.UserGetOptions) StoreChannel
GetProfiles(options *model.UserGetOptions) StoreChannel
GetProfileByIds(userId []string, allowFromCache bool, viewRestrictions *model.ViewUsersRestrictions) StoreChannel
GetProfileByGroupChannelIdsForUser(userId string, channelIds []string) (map[string][]*model.User, *model.AppError)
InvalidatProfileCacheForUser(userId string)
GetByEmail(email string) (*model.User, *model.AppError)
GetByAuth(authData *string, authService string) (*model.User, *model.AppError)

Просмотреть файл

@@ -70,6 +70,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("SearchAllChannels", func(t *testing.T) { testChannelStoreSearchAllChannels(t, ss) })
t.Run("AutocompleteInTeamForSearch", func(t *testing.T) { testChannelStoreAutocompleteInTeamForSearch(t, ss, s) })
t.Run("GetMembersByIds", func(t *testing.T) { testChannelStoreGetMembersByIds(t, ss) })
t.Run("SearchGroupChannels", func(t *testing.T) { testChannelStoreSearchGroupChannels(t, ss) })
t.Run("AnalyticsDeletedTypeCount", func(t *testing.T) { testChannelStoreAnalyticsDeletedTypeCount(t, ss) })
t.Run("GetPinnedPosts", func(t *testing.T) { testChannelStoreGetPinnedPosts(t, ss) })
t.Run("MaxChannelsPerTeam", func(t *testing.T) { testChannelStoreMaxChannelsPerTeam(t, ss) })
@@ -2716,6 +2717,153 @@ func testChannelStoreGetMembersByIds(t *testing.T, ss store.Store) {
}
}
func testChannelStoreSearchGroupChannels(t *testing.T, ss store.Store) {
// Users
u1 := &model.User{}
u1.Username = "user.one"
u1.Email = MakeEmail()
u1.Nickname = model.NewId()
store.Must(ss.User().Save(u1))
u2 := &model.User{}
u2.Username = "user.two"
u2.Email = MakeEmail()
u2.Nickname = model.NewId()
store.Must(ss.User().Save(u2))
u3 := &model.User{}
u3.Username = "user.three"
u3.Email = MakeEmail()
u3.Nickname = model.NewId()
store.Must(ss.User().Save(u3))
u4 := &model.User{}
u4.Username = "user.four"
u4.Email = MakeEmail()
u4.Nickname = model.NewId()
store.Must(ss.User().Save(u4))
// Group channels
userIds := []string{u1.Id, u2.Id, u3.Id}
gc1 := model.Channel{}
gc1.Name = model.GetGroupNameFromUserIds(userIds)
gc1.DisplayName = "GroupChannel" + model.NewId()
gc1.Type = model.CHANNEL_GROUP
_, err := ss.Channel().Save(&gc1, -1)
require.Nil(t, err)
for _, userId := range userIds {
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: gc1.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
}))
}
userIds = []string{u1.Id, u4.Id}
gc2 := model.Channel{}
gc2.Name = model.GetGroupNameFromUserIds(userIds)
gc2.DisplayName = "GroupChannel" + model.NewId()
gc2.Type = model.CHANNEL_GROUP
_, err = ss.Channel().Save(&gc2, -1)
require.Nil(t, err)
for _, userId := range userIds {
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: gc2.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
}))
}
userIds = []string{u1.Id, u2.Id, u3.Id, u4.Id}
gc3 := model.Channel{}
gc3.Name = model.GetGroupNameFromUserIds(userIds)
gc3.DisplayName = "GroupChannel" + model.NewId()
gc3.Type = model.CHANNEL_GROUP
_, err = ss.Channel().Save(&gc3, -1)
require.Nil(t, err)
for _, userId := range userIds {
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: gc3.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
}))
}
defer func() {
for _, gc := range []model.Channel{gc1, gc2, gc3} {
ss.Channel().PermanentDeleteMembersByChannel(gc3.Id)
<-ss.Channel().PermanentDelete(gc.Id)
}
}()
testCases := []struct {
Name string
UserId string
Term string
ExpectedResult []string
}{
{
Name: "Get all group channels for user1",
UserId: u1.Id,
Term: "",
ExpectedResult: []string{gc1.Id, gc2.Id, gc3.Id},
},
{
Name: "Get group channels for user1 and term 'three'",
UserId: u1.Id,
Term: "three",
ExpectedResult: []string{gc1.Id, gc3.Id},
},
{
Name: "Get group channels for user1 and term 'four two'",
UserId: u1.Id,
Term: "four two",
ExpectedResult: []string{gc3.Id},
},
{
Name: "Get all group channels for user2",
UserId: u2.Id,
Term: "",
ExpectedResult: []string{gc1.Id, gc3.Id},
},
{
Name: "Get group channels for user2 and term 'four'",
UserId: u2.Id,
Term: "four",
ExpectedResult: []string{gc3.Id},
},
{
Name: "Get all group channels for user4",
UserId: u4.Id,
Term: "",
ExpectedResult: []string{gc2.Id, gc3.Id},
},
{
Name: "Get group channels for user4 and term 'one five'",
UserId: u4.Id,
Term: "one five",
ExpectedResult: []string{},
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result, err := ss.Channel().SearchGroupChannels(tc.UserId, tc.Term)
require.Nil(t, err)
resultIds := []string{}
for _, gc := range *result {
resultIds = append(resultIds, gc.Id)
}
require.ElementsMatch(t, tc.ExpectedResult, resultIds)
})
}
}
func testChannelStoreAnalyticsDeletedTypeCount(t *testing.T, ss store.Store) {
o1 := model.Channel{}
o1.TeamId = model.NewId()

Просмотреть файл

@@ -1283,6 +1283,31 @@ func (_m *ChannelStore) SearchAllChannels(term string, opts store.ChannelSearchO
return r0
}
// SearchGroupChannels provides a mock function with given fields: userId, term
func (_m *ChannelStore) SearchGroupChannels(userId string, term string) (*model.ChannelList, *model.AppError) {
ret := _m.Called(userId, term)
var r0 *model.ChannelList
if rf, ok := ret.Get(0).(func(string, string) *model.ChannelList); ok {
r0 = rf(userId, term)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.ChannelList)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
r1 = rf(userId, term)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// SearchInTeam provides a mock function with given fields: teamId, term, includeDeleted
func (_m *ChannelStore) SearchInTeam(teamId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError) {
ret := _m.Called(teamId, term, includeDeleted)

Просмотреть файл

@@ -381,6 +381,31 @@ func (_m *UserStore) GetNewUsersForTeam(teamId string, offset int, limit int, vi
return r0
}
// GetProfileByGroupChannelIdsForUser provides a mock function with given fields: userId, channelIds
func (_m *UserStore) GetProfileByGroupChannelIdsForUser(userId string, channelIds []string) (map[string][]*model.User, *model.AppError) {
ret := _m.Called(userId, channelIds)
var r0 map[string][]*model.User
if rf, ok := ret.Get(0).(func(string, []string) map[string][]*model.User); ok {
r0 = rf(userId, channelIds)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string][]*model.User)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, []string) *model.AppError); ok {
r1 = rf(userId, channelIds)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetProfileByIds provides a mock function with given fields: userId, allowFromCache, viewRestrictions
func (_m *UserStore) GetProfileByIds(userId []string, allowFromCache bool, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel {
ret := _m.Called(userId, allowFromCache, viewRestrictions)

Просмотреть файл

@@ -42,6 +42,7 @@ func TestUserStore(t *testing.T, ss store.Store) {
t.Run("GetAllProfilesInChannel", func(t *testing.T) { testUserStoreGetAllProfilesInChannel(t, ss) })
t.Run("GetProfilesNotInChannel", func(t *testing.T) { testUserStoreGetProfilesNotInChannel(t, ss) })
t.Run("GetProfilesByIds", func(t *testing.T) { testUserStoreGetProfilesByIds(t, ss) })
t.Run("GetProfileByGroupChannelIdsForUser", func(t *testing.T) { testUserStoreGetProfileByGroupChannelIdsForUser(t, ss) })
t.Run("GetProfilesByUsernames", func(t *testing.T) { testUserStoreGetProfilesByUsernames(t, ss) })
t.Run("GetSystemAdminProfiles", func(t *testing.T) { testUserStoreGetSystemAdminProfiles(t, ss) })
t.Run("GetByEmail", func(t *testing.T) { testUserStoreGetByEmail(t, ss) })
@@ -1217,6 +1218,122 @@ func testUserStoreGetProfilesByIds(t *testing.T, ss store.Store) {
})
}
func testUserStoreGetProfileByGroupChannelIdsForUser(t *testing.T, ss store.Store) {
u1 := store.Must(ss.User().Save(&model.User{
Email: MakeEmail(),
Username: "u1" + model.NewId(),
})).(*model.User)
defer func() { require.Nil(t, ss.User().PermanentDelete(u1.Id)) }()
u2 := store.Must(ss.User().Save(&model.User{
Email: MakeEmail(),
Username: "u2" + model.NewId(),
})).(*model.User)
defer func() { require.Nil(t, ss.User().PermanentDelete(u2.Id)) }()
u3 := store.Must(ss.User().Save(&model.User{
Email: MakeEmail(),
Username: "u3" + model.NewId(),
})).(*model.User)
defer func() { require.Nil(t, ss.User().PermanentDelete(u3.Id)) }()
u4 := store.Must(ss.User().Save(&model.User{
Email: MakeEmail(),
Username: "u4" + model.NewId(),
})).(*model.User)
defer func() { require.Nil(t, ss.User().PermanentDelete(u4.Id)) }()
gc1, err := ss.Channel().Save(&model.Channel{
DisplayName: "Profiles in private",
Name: "profiles-" + model.NewId(),
Type: model.CHANNEL_GROUP,
}, -1)
require.Nil(t, err)
for _, uId := range []string{u1.Id, u2.Id, u3.Id} {
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: gc1.Id,
UserId: uId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
}))
}
gc2, err := ss.Channel().Save(&model.Channel{
DisplayName: "Profiles in private",
Name: "profiles-" + model.NewId(),
Type: model.CHANNEL_GROUP,
}, -1)
require.Nil(t, err)
for _, uId := range []string{u1.Id, u3.Id, u4.Id} {
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: gc2.Id,
UserId: uId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
}))
}
testCases := []struct {
Name string
UserId string
ChannelIds []string
ExpectedUserIdsByChannel map[string][]string
EnsureChannelsNotInResults []string
}{
{
Name: "Get group 1 as user 1",
UserId: u1.Id,
ChannelIds: []string{gc1.Id},
ExpectedUserIdsByChannel: map[string][]string{
gc1.Id: {u2.Id, u3.Id},
},
EnsureChannelsNotInResults: []string{},
},
{
Name: "Get groups 1 and 2 as user 1",
UserId: u1.Id,
ChannelIds: []string{gc1.Id, gc2.Id},
ExpectedUserIdsByChannel: map[string][]string{
gc1.Id: {u2.Id, u3.Id},
gc2.Id: {u3.Id, u4.Id},
},
EnsureChannelsNotInResults: []string{},
},
{
Name: "Get groups 1 and 2 as user 2",
UserId: u2.Id,
ChannelIds: []string{gc1.Id, gc2.Id},
ExpectedUserIdsByChannel: map[string][]string{
gc1.Id: {u1.Id, u3.Id},
},
EnsureChannelsNotInResults: []string{gc2.Id},
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
res, err := ss.User().GetProfileByGroupChannelIdsForUser(tc.UserId, tc.ChannelIds)
require.Nil(t, err)
for channelId, expectedUsers := range tc.ExpectedUserIdsByChannel {
users, ok := res[channelId]
require.True(t, ok)
userIds := []string{}
for _, user := range users {
userIds = append(userIds, user.Id)
}
require.ElementsMatch(t, expectedUsers, userIds)
}
for _, channelId := range tc.EnsureChannelsNotInResults {
_, ok := res[channelId]
require.False(t, ok)
}
})
}
}
func testUserStoreGetProfilesByUsernames(t *testing.T, ss store.Store) {
teamId := model.NewId()
team2Id := model.NewId()