summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Hallam <jesse.hallam@gmail.com>2018-10-17 11:24:12 -0400
committerHarrison Healey <harrisonmhealey@gmail.com>2018-10-17 11:24:12 -0400
commit715097cc76510a3d78ba83e8544ee7c956ed26e9 (patch)
tree0b6d41e88bf75ad34c585d9db80f04cf8d780338
parente8c9ccaa7e47f1cba3d2b126f6ebbb092fa43235 (diff)
downloadchat-715097cc76510a3d78ba83e8544ee7c956ed26e9.tar.gz
chat-715097cc76510a3d78ba83e8544ee7c956ed26e9.tar.bz2
chat-715097cc76510a3d78ba83e8544ee7c956ed26e9.zip
MM-12234: configurable limit to user autocomplete and search matches (#9499)
* unit test cleanup * allow limiting user search results * clean up test users before starting * model UserSearchOptions to simplify parameters
-rw-r--r--api4/apitestlib.go7
-rw-r--r--api4/user.go59
-rw-r--r--api4/user_test.go44
-rw-r--r--app/user.go58
-rw-r--r--model/client4.go28
-rw-r--r--model/user_search.go29
-rw-r--r--model/user_search_test.go4
-rw-r--r--store/sqlstore/user_store.go102
-rw-r--r--store/store.go10
-rw-r--r--store/storetest/mocks/ChannelStore.go31
-rw-r--r--store/storetest/mocks/UserStore.go20
-rw-r--r--store/storetest/user_store.go1434
12 files changed, 1150 insertions, 676 deletions
diff --git a/api4/apitestlib.go b/api4/apitestlib.go
index 37dbcad25..9976932a9 100644
--- a/api4/apitestlib.go
+++ b/api4/apitestlib.go
@@ -184,8 +184,11 @@ func (me *TestHelper) TearDown() {
go func() {
defer wg.Done()
- options := map[string]bool{}
- options[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true
+ options := &model.UserSearchOptions{
+ AllowEmails: false,
+ AllowFullNames: false,
+ Limit: model.USER_SEARCH_MAX_LIMIT,
+ }
if result := <-me.App.Srv.Store.User().Search("", "fakeuser", options); result.Err != nil {
mlog.Error("Error tearing down test users")
} else {
diff --git a/api4/user.go b/api4/user.go
index c97e90e89..b827857a7 100644
--- a/api4/user.go
+++ b/api4/user.go
@@ -14,7 +14,6 @@ import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/store"
)
func (api *API) InitUser() {
@@ -547,23 +546,26 @@ func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- searchOptions := map[string]bool{}
- searchOptions[store.USER_SEARCH_OPTION_ALLOW_INACTIVE] = props.AllowInactive
+ if props.Limit <= 0 || props.Limit > model.USER_SEARCH_MAX_LIMIT {
+ c.SetInvalidParam("limit")
+ return
+ }
- if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
- hideFullName := !c.App.Config().PrivacySettings.ShowFullName
- hideEmail := !c.App.Config().PrivacySettings.ShowEmailAddress
-
- if hideFullName && hideEmail {
- searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true
- } else if hideFullName {
- searchOptions[store.USER_SEARCH_OPTION_ALL_NO_FULL_NAME] = true
- } else if hideEmail {
- searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
- }
+ options := &model.UserSearchOptions{
+ IsAdmin: c.IsSystemAdmin(),
+ AllowInactive: props.AllowInactive,
+ Limit: props.Limit,
}
- profiles, err := c.App.SearchUsers(props, searchOptions, c.IsSystemAdmin())
+ if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
+ options.AllowEmails = true
+ options.AllowFullNames = true
+ } else {
+ options.AllowEmails = c.App.Config().PrivacySettings.ShowEmailAddress
+ options.AllowFullNames = c.App.Config().PrivacySettings.ShowFullName
+ }
+
+ profiles, err := c.App.SearchUsers(props, options)
if err != nil {
c.Err = err
return
@@ -576,17 +578,26 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
channelId := r.URL.Query().Get("in_channel")
teamId := r.URL.Query().Get("in_team")
name := r.URL.Query().Get("name")
+ limitStr := r.URL.Query().Get("limit")
+ limit, _ := strconv.Atoi(limitStr)
+ if limitStr == "" {
+ limit = model.USER_SEARCH_DEFAULT_LIMIT
+ }
autocomplete := new(model.UserAutocomplete)
var err *model.AppError
- searchOptions := map[string]bool{}
+ options := &model.UserSearchOptions{
+ IsAdmin: c.IsSystemAdmin(),
+ // Never autocomplete on emails.
+ AllowEmails: false,
+ Limit: limit,
+ }
- hideFullName := !c.App.Config().PrivacySettings.ShowFullName
- if hideFullName && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
- searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true
+ if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
+ options.AllowFullNames = true
} else {
- searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
+ options.AllowFullNames = c.App.Config().PrivacySettings.ShowFullName
}
if len(channelId) > 0 {
@@ -606,8 +617,8 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
if len(channelId) > 0 {
// Applying the provided teamId here is useful for DMs and GMs which don't belong
// to a team. Applying it when the channel does belong to a team makes less sense,
- //t but the permissions are checked above regardless.
- result, err := c.App.AutocompleteUsersInChannel(teamId, channelId, name, searchOptions, c.IsSystemAdmin())
+ // but the permissions are checked above regardless.
+ result, err := c.App.AutocompleteUsersInChannel(teamId, channelId, name, options)
if err != nil {
c.Err = err
return
@@ -616,7 +627,7 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
autocomplete.Users = result.InChannel
autocomplete.OutOfChannel = result.OutOfChannel
} else if len(teamId) > 0 {
- result, err := c.App.AutocompleteUsersInTeam(teamId, name, searchOptions, c.IsSystemAdmin())
+ result, err := c.App.AutocompleteUsersInTeam(teamId, name, options)
if err != nil {
c.Err = err
return
@@ -625,7 +636,7 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
autocomplete.Users = result.InTeam
} else {
// No permission check required
- result, err := c.App.SearchUsersInTeam("", name, searchOptions, c.IsSystemAdmin())
+ result, err := c.App.SearchUsersInTeam("", name, options)
if err != nil {
c.Err = err
return
diff --git a/api4/user_test.go b/api4/user_test.go
index b99011aeb..9df062a2a 100644
--- a/api4/user_test.go
+++ b/api4/user_test.go
@@ -759,92 +759,92 @@ func TestAutocompleteUsers(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = showFullName })
}()
- rusers, resp := Client.AutocompleteUsersInChannel(teamId, channelId, username, "")
+ rusers, resp := Client.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if len(rusers.Users) != 1 {
t.Fatal("should have returned 1 user")
}
- rusers, resp = Client.AutocompleteUsersInChannel(teamId, channelId, "amazonses", "")
+ rusers, resp = Client.AutocompleteUsersInChannel(teamId, channelId, "amazonses", model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if len(rusers.Users) != 0 {
t.Fatal("should have returned 0 users")
}
- rusers, resp = Client.AutocompleteUsersInChannel(teamId, channelId, "", "")
+ rusers, resp = Client.AutocompleteUsersInChannel(teamId, channelId, "", model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if len(rusers.Users) < 2 {
t.Fatal("should have many users")
}
- rusers, resp = Client.AutocompleteUsersInChannel("", channelId, "", "")
+ rusers, resp = Client.AutocompleteUsersInChannel("", channelId, "", model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if len(rusers.Users) < 2 {
t.Fatal("should have many users")
}
- rusers, resp = Client.AutocompleteUsersInTeam(teamId, username, "")
+ rusers, resp = Client.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if len(rusers.Users) != 1 {
t.Fatal("should have returned 1 user")
}
- rusers, resp = Client.AutocompleteUsers(username, "")
+ rusers, resp = Client.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if len(rusers.Users) != 1 {
t.Fatal("should have returned 1 users")
}
- rusers, resp = Client.AutocompleteUsers("", "")
+ rusers, resp = Client.AutocompleteUsers("", model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if len(rusers.Users) < 2 {
t.Fatal("should have returned many users")
}
- rusers, resp = Client.AutocompleteUsersInTeam(teamId, "amazonses", "")
+ rusers, resp = Client.AutocompleteUsersInTeam(teamId, "amazonses", model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if len(rusers.Users) != 0 {
t.Fatal("should have returned 0 users")
}
- rusers, resp = Client.AutocompleteUsersInTeam(teamId, "", "")
+ rusers, resp = Client.AutocompleteUsersInTeam(teamId, "", model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if len(rusers.Users) < 2 {
t.Fatal("should have many users")
}
Client.Logout()
- _, resp = Client.AutocompleteUsersInChannel(teamId, channelId, username, "")
+ _, resp = Client.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckUnauthorizedStatus(t, resp)
- _, resp = Client.AutocompleteUsersInTeam(teamId, username, "")
+ _, resp = Client.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckUnauthorizedStatus(t, resp)
- _, resp = Client.AutocompleteUsers(username, "")
+ _, resp = Client.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckUnauthorizedStatus(t, resp)
user := th.CreateUser()
Client.Login(user.Email, user.Password)
- _, resp = Client.AutocompleteUsersInChannel(teamId, channelId, username, "")
+ _, resp = Client.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckForbiddenStatus(t, resp)
- _, resp = Client.AutocompleteUsersInTeam(teamId, username, "")
+ _, resp = Client.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckForbiddenStatus(t, resp)
- _, resp = Client.AutocompleteUsers(username, "")
+ _, resp = Client.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
- _, resp = th.SystemAdminClient.AutocompleteUsersInChannel(teamId, channelId, username, "")
+ _, resp = th.SystemAdminClient.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
- _, resp = th.SystemAdminClient.AutocompleteUsersInTeam(teamId, username, "")
+ _, resp = th.SystemAdminClient.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
- _, resp = th.SystemAdminClient.AutocompleteUsers(username, "")
+ _, resp = th.SystemAdminClient.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
// Check against privacy config settings
@@ -852,21 +852,21 @@ func TestAutocompleteUsers(t *testing.T) {
th.LoginBasic()
- rusers, resp = Client.AutocompleteUsers(username, "")
+ rusers, resp = Client.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if rusers.Users[0].FirstName != "" || rusers.Users[0].LastName != "" {
t.Fatal("should not show first/last name")
}
- rusers, resp = Client.AutocompleteUsersInChannel(teamId, channelId, username, "")
+ rusers, resp = Client.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if rusers.Users[0].FirstName != "" || rusers.Users[0].LastName != "" {
t.Fatal("should not show first/last name")
}
- rusers, resp = Client.AutocompleteUsersInTeam(teamId, username, "")
+ rusers, resp = Client.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if rusers.Users[0].FirstName != "" || rusers.Users[0].LastName != "" {
@@ -874,7 +874,7 @@ func TestAutocompleteUsers(t *testing.T) {
}
t.Run("user must have access to team id, especially when it does not match channel's team id", func(t *testing.T) {
- rusers, resp = Client.AutocompleteUsersInChannel("otherTeamId", channelId, username, "")
+ rusers, resp = Client.AutocompleteUsersInChannel("otherTeamId", channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckErrorMessage(t, resp, "api.context.permissions.app_error")
})
}
diff --git a/app/user.go b/app/user.go
index e565fea76..b3fd3f5b9 100644
--- a/app/user.go
+++ b/app/user.go
@@ -1469,93 +1469,93 @@ func (a *App) VerifyUserEmail(userId string) *model.AppError {
return (<-a.Srv.Store.User().VerifyEmail(userId)).Err
}
-func (a *App) SearchUsers(props *model.UserSearch, searchOptions map[string]bool, asAdmin bool) ([]*model.User, *model.AppError) {
+func (a *App) SearchUsers(props *model.UserSearch, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
if props.WithoutTeam {
- return a.SearchUsersWithoutTeam(props.Term, searchOptions, asAdmin)
+ return a.SearchUsersWithoutTeam(props.Term, options)
} else if props.InChannelId != "" {
- return a.SearchUsersInChannel(props.InChannelId, props.Term, searchOptions, asAdmin)
+ return a.SearchUsersInChannel(props.InChannelId, props.Term, options)
} else if props.NotInChannelId != "" {
- return a.SearchUsersNotInChannel(props.TeamId, props.NotInChannelId, props.Term, searchOptions, asAdmin)
+ return a.SearchUsersNotInChannel(props.TeamId, props.NotInChannelId, props.Term, options)
} else if props.NotInTeamId != "" {
- return a.SearchUsersNotInTeam(props.NotInTeamId, props.Term, searchOptions, asAdmin)
+ return a.SearchUsersNotInTeam(props.NotInTeamId, props.Term, options)
} else {
- return a.SearchUsersInTeam(props.TeamId, props.Term, searchOptions, asAdmin)
+ return a.SearchUsersInTeam(props.TeamId, props.Term, options)
}
}
-func (a *App) SearchUsersInChannel(channelId string, term string, searchOptions map[string]bool, asAdmin bool) ([]*model.User, *model.AppError) {
- if result := <-a.Srv.Store.User().SearchInChannel(channelId, term, searchOptions); result.Err != nil {
+func (a *App) SearchUsersInChannel(channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
+ if result := <-a.Srv.Store.User().SearchInChannel(channelId, term, options); result.Err != nil {
return nil, result.Err
} else {
users := result.Data.([]*model.User)
for _, user := range users {
- a.SanitizeProfile(user, asAdmin)
+ a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
}
-func (a *App) SearchUsersNotInChannel(teamId string, channelId string, term string, searchOptions map[string]bool, asAdmin bool) ([]*model.User, *model.AppError) {
- if result := <-a.Srv.Store.User().SearchNotInChannel(teamId, channelId, term, searchOptions); result.Err != nil {
+func (a *App) SearchUsersNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
+ if result := <-a.Srv.Store.User().SearchNotInChannel(teamId, channelId, term, options); result.Err != nil {
return nil, result.Err
} else {
users := result.Data.([]*model.User)
for _, user := range users {
- a.SanitizeProfile(user, asAdmin)
+ a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
}
-func (a *App) SearchUsersInTeam(teamId string, term string, searchOptions map[string]bool, asAdmin bool) ([]*model.User, *model.AppError) {
- if result := <-a.Srv.Store.User().Search(teamId, term, searchOptions); result.Err != nil {
+func (a *App) SearchUsersInTeam(teamId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
+ if result := <-a.Srv.Store.User().Search(teamId, term, options); result.Err != nil {
return nil, result.Err
} else {
users := result.Data.([]*model.User)
for _, user := range users {
- a.SanitizeProfile(user, asAdmin)
+ a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
}
-func (a *App) SearchUsersNotInTeam(notInTeamId string, term string, searchOptions map[string]bool, asAdmin bool) ([]*model.User, *model.AppError) {
- if result := <-a.Srv.Store.User().SearchNotInTeam(notInTeamId, term, searchOptions); result.Err != nil {
+func (a *App) SearchUsersNotInTeam(notInTeamId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
+ if result := <-a.Srv.Store.User().SearchNotInTeam(notInTeamId, term, options); result.Err != nil {
return nil, result.Err
} else {
users := result.Data.([]*model.User)
for _, user := range users {
- a.SanitizeProfile(user, asAdmin)
+ a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
}
-func (a *App) SearchUsersWithoutTeam(term string, searchOptions map[string]bool, asAdmin bool) ([]*model.User, *model.AppError) {
- if result := <-a.Srv.Store.User().SearchWithoutTeam(term, searchOptions); result.Err != nil {
+func (a *App) SearchUsersWithoutTeam(term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
+ if result := <-a.Srv.Store.User().SearchWithoutTeam(term, options); result.Err != nil {
return nil, result.Err
} else {
users := result.Data.([]*model.User)
for _, user := range users {
- a.SanitizeProfile(user, asAdmin)
+ a.SanitizeProfile(user, options.IsAdmin)
}
return users, nil
}
}
-func (a *App) AutocompleteUsersInChannel(teamId string, channelId string, term string, searchOptions map[string]bool, asAdmin bool) (*model.UserAutocompleteInChannel, *model.AppError) {
- uchan := a.Srv.Store.User().SearchInChannel(channelId, term, searchOptions)
- nuchan := a.Srv.Store.User().SearchNotInChannel(teamId, channelId, term, searchOptions)
+func (a *App) AutocompleteUsersInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, *model.AppError) {
+ uchan := a.Srv.Store.User().SearchInChannel(channelId, term, options)
+ nuchan := a.Srv.Store.User().SearchNotInChannel(teamId, channelId, term, options)
autocomplete := &model.UserAutocompleteInChannel{}
@@ -1565,7 +1565,7 @@ func (a *App) AutocompleteUsersInChannel(teamId string, channelId string, term s
users := result.Data.([]*model.User)
for _, user := range users {
- a.SanitizeProfile(user, asAdmin)
+ a.SanitizeProfile(user, options.IsAdmin)
}
autocomplete.InChannel = users
@@ -1577,7 +1577,7 @@ func (a *App) AutocompleteUsersInChannel(teamId string, channelId string, term s
users := result.Data.([]*model.User)
for _, user := range users {
- a.SanitizeProfile(user, asAdmin)
+ a.SanitizeProfile(user, options.IsAdmin)
}
autocomplete.OutOfChannel = users
@@ -1586,16 +1586,16 @@ func (a *App) AutocompleteUsersInChannel(teamId string, channelId string, term s
return autocomplete, nil
}
-func (a *App) AutocompleteUsersInTeam(teamId string, term string, searchOptions map[string]bool, asAdmin bool) (*model.UserAutocompleteInTeam, *model.AppError) {
+func (a *App) AutocompleteUsersInTeam(teamId string, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInTeam, *model.AppError) {
autocomplete := &model.UserAutocompleteInTeam{}
- if result := <-a.Srv.Store.User().Search(teamId, term, searchOptions); result.Err != nil {
+ if result := <-a.Srv.Store.User().Search(teamId, term, options); result.Err != nil {
return nil, result.Err
} else {
users := result.Data.([]*model.User)
for _, user := range users {
- a.SanitizeProfile(user, asAdmin)
+ a.SanitizeProfile(user, options.IsAdmin)
}
autocomplete.InTeam = users
diff --git a/model/client4.go b/model/client4.go
index c35dab904..c5001c048 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -417,6 +417,10 @@ func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError)
return c.DoApiRequest(http.MethodPost, c.ApiUrl+url, data, "")
}
+func (c *Client4) doApiPostBytes(url string, data []byte) (*http.Response, *AppError) {
+ return c.doApiRequestBytes(http.MethodPost, c.ApiUrl+url, data, "")
+}
+
func (c *Client4) DoApiPut(url string, data string) (*http.Response, *AppError) {
return c.DoApiRequest(http.MethodPut, c.ApiUrl+url, data, "")
}
@@ -426,7 +430,15 @@ func (c *Client4) DoApiDelete(url string) (*http.Response, *AppError) {
}
func (c *Client4) DoApiRequest(method, url, data, etag string) (*http.Response, *AppError) {
- rq, _ := http.NewRequest(method, url, strings.NewReader(data))
+ return c.doApiRequestReader(method, url, strings.NewReader(data), etag)
+}
+
+func (c *Client4) doApiRequestBytes(method, url string, data []byte, etag string) (*http.Response, *AppError) {
+ return c.doApiRequestReader(method, url, bytes.NewReader(data), etag)
+}
+
+func (c *Client4) doApiRequestReader(method, url string, data io.Reader, etag string) (*http.Response, *AppError) {
+ rq, _ := http.NewRequest(method, url, data)
if len(etag) > 0 {
rq.Header.Set(HEADER_ETAG_CLIENT, etag)
@@ -691,8 +703,8 @@ func (c *Client4) GetUserByEmail(email, etag string) (*User, *Response) {
}
// AutocompleteUsersInTeam returns the users on a team based on search term.
-func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, etag string) (*UserAutocomplete, *Response) {
- query := fmt.Sprintf("?in_team=%v&name=%v", teamId, username)
+func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, limit int, etag string) (*UserAutocomplete, *Response) {
+ query := fmt.Sprintf("?in_team=%v&name=%v&limit=%d", teamId, username, limit)
if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
@@ -702,8 +714,8 @@ func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, etag s
}
// AutocompleteUsersInChannel returns the users in a channel based on search term.
-func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, username string, etag string) (*UserAutocomplete, *Response) {
- query := fmt.Sprintf("?in_team=%v&in_channel=%v&name=%v", teamId, channelId, username)
+func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, username string, limit int, etag string) (*UserAutocomplete, *Response) {
+ query := fmt.Sprintf("?in_team=%v&in_channel=%v&name=%v&limit=%d", teamId, channelId, username, limit)
if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
@@ -713,8 +725,8 @@ func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, us
}
// AutocompleteUsers returns the users in the system based on search term.
-func (c *Client4) AutocompleteUsers(username string, etag string) (*UserAutocomplete, *Response) {
- query := fmt.Sprintf("?name=%v", username)
+func (c *Client4) AutocompleteUsers(username string, limit int, etag string) (*UserAutocomplete, *Response) {
+ query := fmt.Sprintf("?name=%v&limit=%d", username, limit)
if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
@@ -875,7 +887,7 @@ func (c *Client4) GetUsersByUsernames(usernames []string) ([]*User, *Response) {
// SearchUsers returns a list of users based on some search criteria.
func (c *Client4) SearchUsers(search *UserSearch) ([]*User, *Response) {
- if r, err := c.DoApiPost(c.GetUsersRoute()+"/search", search.ToJson()); err != nil {
+ if r, err := c.doApiPostBytes(c.GetUsersRoute()+"/search", search.ToJson()); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
diff --git a/model/user_search.go b/model/user_search.go
index 94596bdcd..68749fbe5 100644
--- a/model/user_search.go
+++ b/model/user_search.go
@@ -8,6 +8,10 @@ import (
"io"
)
+const USER_SEARCH_MAX_LIMIT = 1000
+const USER_SEARCH_DEFAULT_LIMIT = 100
+
+// UserSearch captures the parameters provided by a client for initiating a user search.
type UserSearch struct {
Term string `json:"term"`
TeamId string `json:"team_id"`
@@ -16,17 +20,38 @@ type UserSearch struct {
NotInChannelId string `json:"not_in_channel_id"`
AllowInactive bool `json:"allow_inactive"`
WithoutTeam bool `json:"without_team"`
+ Limit int `json:"limit"`
}
// ToJson convert a User to a json string
-func (u *UserSearch) ToJson() string {
+func (u *UserSearch) ToJson() []byte {
b, _ := json.Marshal(u)
- return string(b)
+ return b
}
// UserSearchFromJson will decode the input and return a User
func UserSearchFromJson(data io.Reader) *UserSearch {
var us *UserSearch
json.NewDecoder(data).Decode(&us)
+
+ if us.Limit == 0 {
+ us.Limit = USER_SEARCH_DEFAULT_LIMIT
+ }
+
return us
}
+
+// UserSearchOptions captures internal parameters derived from the user's permissions and a
+// UserSearch request.
+type UserSearchOptions struct {
+ // IsAdmin tracks whether or not the search is being conducted by an administrator.
+ IsAdmin bool
+ // AllowEmails allows search to examine the emails of users.
+ AllowEmails bool
+ // AllowFullNames allows search to examine the full names of users, vs. just usernames and nicknames.
+ AllowFullNames bool
+ // AllowInactive configures whether or not to return inactive users in the search results.
+ AllowInactive bool
+ // Limit limits the total number of results returned.
+ Limit int
+}
diff --git a/model/user_search_test.go b/model/user_search_test.go
index 0fada7781..89c8b68f7 100644
--- a/model/user_search_test.go
+++ b/model/user_search_test.go
@@ -4,14 +4,14 @@
package model
import (
- "strings"
+ "bytes"
"testing"
)
func TestUserSearchJson(t *testing.T) {
userSearch := UserSearch{Term: NewId(), TeamId: NewId()}
json := userSearch.ToJson()
- ruserSearch := UserSearchFromJson(strings.NewReader(json))
+ ruserSearch := UserSearchFromJson(bytes.NewReader(json))
if userSearch.Term != ruserSearch.Term {
t.Fatal("Terms do not match")
diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go
index f0839c3b7..0e70a87d9 100644
--- a/store/sqlstore/user_store.go
+++ b/store/sqlstore/user_store.go
@@ -970,12 +970,11 @@ func (us SqlUserStore) GetAnyUnreadPostCountForChannel(userId string, channelId
})
}
-func (us SqlUserStore) Search(teamId string, term string, options map[string]bool) store.StoreChannel {
+func (us SqlUserStore) Search(teamId string, term string, options *model.UserSearchOptions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
searchQuery := ""
if teamId == "" {
-
// Id != '' is added because both SEARCH_CLAUSE and INACTIVE_CLAUSE start with an AND
searchQuery = `
SELECT
@@ -986,8 +985,8 @@ func (us SqlUserStore) Search(teamId string, term string, options map[string]boo
Id != ''
SEARCH_CLAUSE
INACTIVE_CLAUSE
- ORDER BY Username ASC
- LIMIT 100`
+ ORDER BY Username ASC
+ LIMIT :Limit`
} else {
searchQuery = `
SELECT
@@ -1000,16 +999,19 @@ func (us SqlUserStore) Search(teamId string, term string, options map[string]boo
AND TeamMembers.DeleteAt = 0
SEARCH_CLAUSE
INACTIVE_CLAUSE
- ORDER BY Users.Username ASC
- LIMIT 100`
+ ORDER BY Users.Username ASC
+ LIMIT :Limit`
}
- *result = us.performSearch(searchQuery, term, options, map[string]interface{}{"TeamId": teamId})
+ *result = us.performSearch(searchQuery, term, options, map[string]interface{}{
+ "TeamId": teamId,
+ "Limit": options.Limit,
+ })
})
}
-func (us SqlUserStore) SearchWithoutTeam(term string, options map[string]bool) store.StoreChannel {
+func (us SqlUserStore) SearchWithoutTeam(term string, options *model.UserSearchOptions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
searchQuery := `
SELECT
@@ -1027,14 +1029,16 @@ func (us SqlUserStore) SearchWithoutTeam(term string, options map[string]bool) s
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Username ASC
- LIMIT 100`
+ LIMIT :Limit`
- *result = us.performSearch(searchQuery, term, options, map[string]interface{}{})
+ *result = us.performSearch(searchQuery, term, options, map[string]interface{}{
+ "Limit": options.Limit,
+ })
})
}
-func (us SqlUserStore) SearchNotInTeam(notInTeamId string, term string, options map[string]bool) store.StoreChannel {
+func (us SqlUserStore) SearchNotInTeam(notInTeamId string, term string, options *model.UserSearchOptions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
searchQuery := `
SELECT
@@ -1048,14 +1052,17 @@ func (us SqlUserStore) SearchNotInTeam(notInTeamId string, term string, options
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC
- LIMIT 100`
+ LIMIT :Limit`
- *result = us.performSearch(searchQuery, term, options, map[string]interface{}{"NotInTeamId": notInTeamId})
+ *result = us.performSearch(searchQuery, term, options, map[string]interface{}{
+ "NotInTeamId": notInTeamId,
+ "Limit": options.Limit,
+ })
})
}
-func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term string, options map[string]bool) store.StoreChannel {
+func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
searchQuery := ""
if teamId == "" {
@@ -1071,7 +1078,7 @@ func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC
- LIMIT 100`
+ LIMIT :Limit`
} else {
searchQuery = `
SELECT
@@ -1089,30 +1096,37 @@ func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term
SEARCH_CLAUSE
INACTIVE_CLAUSE
ORDER BY Users.Username ASC
- LIMIT 100`
+ LIMIT :Limit`
}
- *result = us.performSearch(searchQuery, term, options, map[string]interface{}{"TeamId": teamId, "ChannelId": channelId})
-
+ *result = us.performSearch(searchQuery, term, options, map[string]interface{}{
+ "TeamId": teamId,
+ "ChannelId": channelId,
+ "Limit": options.Limit,
+ })
})
}
-func (us SqlUserStore) SearchInChannel(channelId string, term string, options map[string]bool) store.StoreChannel {
+func (us SqlUserStore) SearchInChannel(channelId string, term string, options *model.UserSearchOptions) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
searchQuery := `
- SELECT
- Users.*
- FROM
- Users, ChannelMembers
- WHERE
- ChannelMembers.ChannelId = :ChannelId
- AND ChannelMembers.UserId = Users.Id
- SEARCH_CLAUSE
- INACTIVE_CLAUSE
- ORDER BY Users.Username ASC
- LIMIT 100`
-
- *result = us.performSearch(searchQuery, term, options, map[string]interface{}{"ChannelId": channelId})
+ SELECT
+ Users.*
+ FROM
+ Users, ChannelMembers
+ WHERE
+ ChannelMembers.ChannelId = :ChannelId
+ AND ChannelMembers.UserId = Users.Id
+ SEARCH_CLAUSE
+ INACTIVE_CLAUSE
+ ORDER BY Users.Username ASC
+ LIMIT :Limit
+ `
+
+ *result = us.performSearch(searchQuery, term, options, map[string]interface{}{
+ "ChannelId": channelId,
+ "Limit": options.Limit,
+ })
})
}
@@ -1160,7 +1174,7 @@ func generateSearchQuery(searchQuery string, terms []string, fields []string, pa
return strings.Replace(searchQuery, "SEARCH_CLAUSE", fmt.Sprintf(" AND %s ", searchClause), 1)
}
-func (us SqlUserStore) performSearch(searchQuery string, term string, options map[string]bool, parameters map[string]interface{}) store.StoreResult {
+func (us SqlUserStore) performSearch(searchQuery string, term string, options *model.UserSearchOptions, parameters map[string]interface{}) store.StoreResult {
result := store.StoreResult{}
// These chars must be removed from the like query.
@@ -1173,16 +1187,22 @@ func (us SqlUserStore) performSearch(searchQuery string, term string, options ma
term = strings.Replace(term, c, "*"+c, -1)
}
- searchType := USER_SEARCH_TYPE_ALL
- if ok := options[store.USER_SEARCH_OPTION_NAMES_ONLY]; ok {
- searchType = USER_SEARCH_TYPE_NAMES
- } else if ok = options[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME]; ok {
- searchType = USER_SEARCH_TYPE_NAMES_NO_FULL_NAME
- } else if ok = options[store.USER_SEARCH_OPTION_ALL_NO_FULL_NAME]; ok {
- searchType = USER_SEARCH_TYPE_ALL_NO_FULL_NAME
+ searchType := USER_SEARCH_TYPE_NAMES_NO_FULL_NAME
+ if options.AllowEmails {
+ if options.AllowFullNames {
+ searchType = USER_SEARCH_TYPE_ALL
+ } else {
+ searchType = USER_SEARCH_TYPE_ALL_NO_FULL_NAME
+ }
+ } else {
+ if options.AllowFullNames {
+ searchType = USER_SEARCH_TYPE_NAMES
+ } else {
+ searchType = USER_SEARCH_TYPE_NAMES_NO_FULL_NAME
+ }
}
- if ok := options[store.USER_SEARCH_OPTION_ALLOW_INACTIVE]; ok {
+ if ok := options.AllowInactive; ok {
searchQuery = strings.Replace(searchQuery, "INACTIVE_CLAUSE", "", 1)
} else {
searchQuery = strings.Replace(searchQuery, "INACTIVE_CLAUSE", "AND Users.DeleteAt = 0", 1)
diff --git a/store/store.go b/store/store.go
index 1e153b422..e554f1486 100644
--- a/store/store.go
+++ b/store/store.go
@@ -272,11 +272,11 @@ type UserStore interface {
GetAnyUnreadPostCountForChannel(userId string, channelId string) StoreChannel
GetRecentlyActiveUsersForTeam(teamId string, offset, limit int) StoreChannel
GetNewUsersForTeam(teamId string, offset, limit int) StoreChannel
- Search(teamId string, term string, options map[string]bool) StoreChannel
- SearchNotInTeam(notInTeamId string, term string, options map[string]bool) StoreChannel
- SearchInChannel(channelId string, term string, options map[string]bool) StoreChannel
- SearchNotInChannel(teamId string, channelId string, term string, options map[string]bool) StoreChannel
- SearchWithoutTeam(term string, options map[string]bool) StoreChannel
+ Search(teamId string, term string, options *model.UserSearchOptions) StoreChannel
+ SearchNotInTeam(notInTeamId string, term string, options *model.UserSearchOptions) StoreChannel
+ SearchInChannel(channelId string, term string, options *model.UserSearchOptions) StoreChannel
+ SearchNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) StoreChannel
+ SearchWithoutTeam(term string, options *model.UserSearchOptions) StoreChannel
AnalyticsGetInactiveUsersCount() StoreChannel
AnalyticsGetSystemAdminCount() StoreChannel
GetProfilesNotInTeam(teamId string, offset int, limit int) StoreChannel
diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go
index 9d42d0b65..7ad8f100d 100644
--- a/store/storetest/mocks/ChannelStore.go
+++ b/store/storetest/mocks/ChannelStore.go
@@ -314,6 +314,22 @@ func (_m *ChannelStore) GetChannelMembersForExport(userId string, teamId string)
return r0
}
+// GetChannelMembersTimezones provides a mock function with given fields: channelId
+func (_m *ChannelStore) GetChannelMembersTimezones(channelId string) store.StoreChannel {
+ ret := _m.Called(channelId)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
+ r0 = rf(channelId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
// GetChannelUnread provides a mock function with given fields: channelId, userId
func (_m *ChannelStore) GetChannelUnread(channelId string, userId string) store.StoreChannel {
ret := _m.Called(channelId, userId)
@@ -442,21 +458,6 @@ func (_m *ChannelStore) GetMember(channelId string, userId string) store.StoreCh
return r0
}
-func (_m *ChannelStore) GetChannelMembersTimezones(channelId string) store.StoreChannel {
- ret := _m.Called(channelId)
-
- var r0 store.StoreChannel
- if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
- r0 = rf(channelId)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(store.StoreChannel)
- }
- }
-
- return r0
-}
-
// GetMemberCount provides a mock function with given fields: channelId, allowFromCache
func (_m *ChannelStore) GetMemberCount(channelId string, allowFromCache bool) store.StoreChannel {
ret := _m.Called(channelId, allowFromCache)
diff --git a/store/storetest/mocks/UserStore.go b/store/storetest/mocks/UserStore.go
index 1e0ce8818..de836e95d 100644
--- a/store/storetest/mocks/UserStore.go
+++ b/store/storetest/mocks/UserStore.go
@@ -626,11 +626,11 @@ func (_m *UserStore) Save(user *model.User) store.StoreChannel {
}
// Search provides a mock function with given fields: teamId, term, options
-func (_m *UserStore) Search(teamId string, term string, options map[string]bool) store.StoreChannel {
+func (_m *UserStore) Search(teamId string, term string, options *model.UserSearchOptions) store.StoreChannel {
ret := _m.Called(teamId, term, options)
var r0 store.StoreChannel
- if rf, ok := ret.Get(0).(func(string, string, map[string]bool) store.StoreChannel); ok {
+ if rf, ok := ret.Get(0).(func(string, string, *model.UserSearchOptions) store.StoreChannel); ok {
r0 = rf(teamId, term, options)
} else {
if ret.Get(0) != nil {
@@ -642,11 +642,11 @@ func (_m *UserStore) Search(teamId string, term string, options map[string]bool)
}
// SearchInChannel provides a mock function with given fields: channelId, term, options
-func (_m *UserStore) SearchInChannel(channelId string, term string, options map[string]bool) store.StoreChannel {
+func (_m *UserStore) SearchInChannel(channelId string, term string, options *model.UserSearchOptions) store.StoreChannel {
ret := _m.Called(channelId, term, options)
var r0 store.StoreChannel
- if rf, ok := ret.Get(0).(func(string, string, map[string]bool) store.StoreChannel); ok {
+ if rf, ok := ret.Get(0).(func(string, string, *model.UserSearchOptions) store.StoreChannel); ok {
r0 = rf(channelId, term, options)
} else {
if ret.Get(0) != nil {
@@ -658,11 +658,11 @@ func (_m *UserStore) SearchInChannel(channelId string, term string, options map[
}
// SearchNotInChannel provides a mock function with given fields: teamId, channelId, term, options
-func (_m *UserStore) SearchNotInChannel(teamId string, channelId string, term string, options map[string]bool) store.StoreChannel {
+func (_m *UserStore) SearchNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) store.StoreChannel {
ret := _m.Called(teamId, channelId, term, options)
var r0 store.StoreChannel
- if rf, ok := ret.Get(0).(func(string, string, string, map[string]bool) store.StoreChannel); ok {
+ if rf, ok := ret.Get(0).(func(string, string, string, *model.UserSearchOptions) store.StoreChannel); ok {
r0 = rf(teamId, channelId, term, options)
} else {
if ret.Get(0) != nil {
@@ -674,11 +674,11 @@ func (_m *UserStore) SearchNotInChannel(teamId string, channelId string, term st
}
// SearchNotInTeam provides a mock function with given fields: notInTeamId, term, options
-func (_m *UserStore) SearchNotInTeam(notInTeamId string, term string, options map[string]bool) store.StoreChannel {
+func (_m *UserStore) SearchNotInTeam(notInTeamId string, term string, options *model.UserSearchOptions) store.StoreChannel {
ret := _m.Called(notInTeamId, term, options)
var r0 store.StoreChannel
- if rf, ok := ret.Get(0).(func(string, string, map[string]bool) store.StoreChannel); ok {
+ if rf, ok := ret.Get(0).(func(string, string, *model.UserSearchOptions) store.StoreChannel); ok {
r0 = rf(notInTeamId, term, options)
} else {
if ret.Get(0) != nil {
@@ -690,11 +690,11 @@ func (_m *UserStore) SearchNotInTeam(notInTeamId string, term string, options ma
}
// SearchWithoutTeam provides a mock function with given fields: term, options
-func (_m *UserStore) SearchWithoutTeam(term string, options map[string]bool) store.StoreChannel {
+func (_m *UserStore) SearchWithoutTeam(term string, options *model.UserSearchOptions) store.StoreChannel {
ret := _m.Called(term, options)
var r0 store.StoreChannel
- if rf, ok := ret.Get(0).(func(string, map[string]bool) store.StoreChannel); ok {
+ if rf, ok := ret.Get(0).(func(string, *model.UserSearchOptions) store.StoreChannel); ok {
r0 = rf(term, options)
} else {
if ret.Get(0) != nil {
diff --git a/store/storetest/user_store.go b/store/storetest/user_store.go
index 533e376b2..a3c1af5a3 100644
--- a/store/storetest/user_store.go
+++ b/store/storetest/user_store.go
@@ -16,6 +16,15 @@ import (
)
func TestUserStore(t *testing.T, ss store.Store) {
+ result := <-ss.User().GetAll()
+ require.Nil(t, result.Err, "failed cleaning up test users")
+ users := result.Data.([]*model.User)
+
+ for _, u := range users {
+ result := <-ss.User().PermanentDelete(u.Id)
+ require.Nil(t, result.Err, "failed cleaning up test user %s", u.Username)
+ }
+
t.Run("Save", func(t *testing.T) { testUserStoreSave(t, ss) })
t.Run("Update", func(t *testing.T) { testUserStoreUpdate(t, ss) })
t.Run("UpdateUpdateAt", func(t *testing.T) { testUserStoreUpdateUpdateAt(t, ss) })
@@ -46,6 +55,9 @@ func TestUserStore(t *testing.T, ss store.Store) {
t.Run("GetRecentlyActiveUsersForTeam", func(t *testing.T) { testUserStoreGetRecentlyActiveUsersForTeam(t, ss) })
t.Run("GetNewUsersForTeam", func(t *testing.T) { testUserStoreGetNewUsersForTeam(t, ss) })
t.Run("Search", func(t *testing.T) { testUserStoreSearch(t, ss) })
+ t.Run("SearchNotInChannel", func(t *testing.T) { testUserStoreSearchNotInChannel(t, ss) })
+ t.Run("SearchInChannel", func(t *testing.T) { testUserStoreSearchInChannel(t, ss) })
+ t.Run("SearchNotInTeam", func(t *testing.T) { testUserStoreSearchNotInTeam(t, ss) })
t.Run("SearchWithoutTeam", func(t *testing.T) { testUserStoreSearchWithoutTeam(t, ss) })
t.Run("AnalyticsGetInactiveUsersCount", func(t *testing.T) { testUserStoreAnalyticsGetInactiveUsersCount(t, ss) })
t.Run("AnalyticsGetSystemAdminCount", func(t *testing.T) { testUserStoreAnalyticsGetSystemAdminCount(t, ss) })
@@ -65,6 +77,7 @@ func testUserStoreSave(t *testing.T, ss store.Store) {
if err := (<-ss.User().Save(&u1)).Err; err != nil {
t.Fatal("couldn't save user", err)
}
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, maxUsersPerTeam))
@@ -95,6 +108,7 @@ func testUserStoreSave(t *testing.T, ss store.Store) {
if err := (<-ss.User().Save(&u1)).Err; err != nil {
t.Fatal("couldn't save item", err)
}
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, maxUsersPerTeam))
}
@@ -105,6 +119,7 @@ func testUserStoreSave(t *testing.T, ss store.Store) {
if err := (<-ss.User().Save(&u1)).Err; err != nil {
t.Fatal("couldn't save item", err)
}
+ defer ss.User().PermanentDelete(u1.Id)
if err := (<-ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, maxUsersPerTeam)).Err; err == nil {
t.Fatal("should be the limit")
@@ -115,12 +130,14 @@ func testUserStoreUpdate(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
u2.AuthService = "ldap"
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1))
time.Sleep(100 * time.Millisecond)
@@ -149,6 +166,7 @@ func testUserStoreUpdate(t *testing.T, ss store.Store) {
oldEmail := u3.Email
u3.AuthService = "gitlab"
store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u3.Id}, -1))
u3.Email = MakeEmail()
@@ -180,6 +198,7 @@ func testUserStoreUpdateUpdateAt(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
time.Sleep(10 * time.Millisecond)
@@ -202,6 +221,7 @@ func testUserStoreUpdateFailedPasswordAttempts(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
if err := (<-ss.User().UpdateFailedPasswordAttempts(u1.Id, 3)).Err; err != nil {
@@ -222,6 +242,7 @@ func testUserStoreGet(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
if r1 := <-ss.User().Get(u1.Id); r1.Err != nil {
@@ -241,6 +262,7 @@ func testUserCount(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
if result := <-ss.User().GetTotalUsersCount(); result.Err != nil {
@@ -256,11 +278,13 @@ func testGetAllUsingAuthService(t *testing.T, ss store.Store) {
u1.Email = MakeEmail()
u1.AuthService = "someservice"
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
u2 := &model.User{}
u2.Email = MakeEmail()
u2.AuthService = "someservice"
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
if r1 := <-ss.User().GetAllUsingAuthService(u1.AuthService); r1.Err != nil {
t.Fatal(r1.Err)
@@ -276,10 +300,12 @@ func testUserStoreGetAllProfiles(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
u2 := &model.User{}
u2.Email = MakeEmail()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
if r1 := <-ss.User().GetAllProfiles(0, 100); r1.Err != nil {
t.Fatal(r1.Err)
@@ -318,6 +344,7 @@ func testUserStoreGetAllProfiles(t *testing.T, ss store.Store) {
u3 := &model.User{}
u3.Email = MakeEmail()
store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
if r2 := <-ss.User().GetEtagForAllProfiles(); r2.Err != nil {
t.Fatal(r2.Err)
@@ -334,11 +361,13 @@ func testUserStoreGetProfiles(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
if r1 := <-ss.User().GetProfiles(teamId, 0, 100); r1.Err != nil {
@@ -379,6 +408,7 @@ func testUserStoreGetProfiles(t *testing.T, ss store.Store) {
u3 := &model.User{}
u3.Email = MakeEmail()
store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
if r2 := <-ss.User().GetEtagForProfiles(teamId); r2.Err != nil {
@@ -396,11 +426,13 @@ func testUserStoreGetProfilesInChannel(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
c1 := model.Channel{}
@@ -472,11 +504,13 @@ func testUserStoreGetProfilesInChannelByStatus(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
c1 := model.Channel{}
@@ -589,11 +623,13 @@ func testUserStoreGetAllProfilesInChannel(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
c1 := model.Channel{}
@@ -677,11 +713,13 @@ func testUserStoreGetProfilesNotInChannel(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
c1 := model.Channel{}
@@ -770,11 +808,13 @@ func testUserStoreGetProfilesByIds(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
if r1 := <-ss.User().GetProfileByIds([]string{u1.Id}, false); r1.Err != nil {
@@ -913,12 +953,14 @@ func testUserStoreGetProfilesByUsernames(t *testing.T, ss store.Store) {
u1.Email = MakeEmail()
u1.Username = "username1" + model.NewId()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
u2.Username = "username2" + model.NewId()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
if r1 := <-ss.User().GetProfilesByUsernames([]string{u1.Username, u2.Username}, teamId); r1.Err != nil {
@@ -957,6 +999,7 @@ func testUserStoreGetProfilesByUsernames(t *testing.T, ss store.Store) {
u3.Email = MakeEmail()
u3.Username = "username3" + model.NewId()
store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: team2Id, UserId: u3.Id}, -1))
if r1 := <-ss.User().GetProfilesByUsernames([]string{u1.Username, u3.Username}, ""); r1.Err != nil {
@@ -997,11 +1040,13 @@ func testUserStoreGetSystemAdminProfiles(t *testing.T, ss store.Store) {
u1.Email = MakeEmail()
u1.Roles = model.SYSTEM_USER_ROLE_ID + " " + model.SYSTEM_ADMIN_ROLE_ID
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
if r1 := <-ss.User().GetSystemAdminProfiles(); r1.Err != nil {
@@ -1020,6 +1065,7 @@ func testUserStoreGetByEmail(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamid, UserId: u1.Id}, -1))
if err := (<-ss.User().GetByEmail(u1.Email)).Err; err != nil {
@@ -1041,6 +1087,7 @@ func testUserStoreGetByAuthData(t *testing.T, ss store.Store) {
u1.AuthData = &auth
u1.AuthService = "service"
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
if err := (<-ss.User().GetByAuth(u1.AuthData, u1.AuthService)).Err; err != nil {
@@ -1060,6 +1107,7 @@ func testUserStoreGetByUsername(t *testing.T, ss store.Store) {
u1.Email = MakeEmail()
u1.Username = model.NewId()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
if err := (<-ss.User().GetByUsername(u1.Username)).Err; err != nil {
@@ -1081,6 +1129,7 @@ func testUserStoreGetForLogin(t *testing.T, ss store.Store) {
AuthData: &auth,
}
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
auth2 := model.NewId()
@@ -1091,6 +1140,7 @@ func testUserStoreGetForLogin(t *testing.T, ss store.Store) {
AuthData: &auth2,
}
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
if result := <-ss.User().GetForLogin(u1.Username, true, true); result.Err != nil {
t.Fatal("Should have gotten user by username", result.Err)
@@ -1120,6 +1170,7 @@ func testUserStoreUpdatePassword(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
hashedPassword := model.HashPassword("newpwd")
@@ -1142,6 +1193,7 @@ func testUserStoreDelete(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
if err := (<-ss.User().PermanentDelete(u1.Id)).Err; err != nil {
@@ -1155,6 +1207,7 @@ func testUserStoreUpdateAuthData(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
service := "someservice"
@@ -1199,12 +1252,14 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
u1.Username = "user1" + model.NewId()
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
u2.Username = "user2" + model.NewId()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1))
if err := (<-ss.Channel().Save(&c1, -1)).Err; err != nil {
@@ -1275,6 +1330,7 @@ func testUserStoreUpdateMfaSecret(t *testing.T, ss store.Store) {
u1 := model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(&u1))
+ defer ss.User().PermanentDelete(u1.Id)
time.Sleep(100 * time.Millisecond)
@@ -1292,6 +1348,7 @@ func testUserStoreUpdateMfaActive(t *testing.T, ss store.Store) {
u1 := model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(&u1))
+ defer ss.User().PermanentDelete(u1.Id)
time.Sleep(100 * time.Millisecond)
@@ -1313,6 +1370,7 @@ func testUserStoreGetRecentlyActiveUsersForTeam(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Status().SaveOrUpdate(&model.Status{UserId: u1.Id, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""}))
tid := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}, -1))
@@ -1326,6 +1384,7 @@ func testUserStoreGetNewUsersForTeam(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Status().SaveOrUpdate(&model.Status{UserId: u1.Id, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""}))
tid := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}, -1))
@@ -1335,41 +1394,67 @@ func testUserStoreGetNewUsersForTeam(t *testing.T, ss store.Store) {
}
}
+func assertUsers(t *testing.T, expected, actual []*model.User) {
+ expectedUsernames := make([]string, 0, len(expected))
+ for _, user := range expected {
+ expectedUsernames = append(expectedUsernames, user.Username)
+ }
+
+ actualUsernames := make([]string, 0, len(actual))
+ for _, user := range actual {
+ actualUsernames = append(actualUsernames, user.Username)
+ }
+
+ if assert.Equal(t, expectedUsernames, actualUsernames) {
+ assert.Equal(t, expected, actual)
+ }
+}
+
func testUserStoreSearch(t *testing.T, ss store.Store) {
- u1 := &model.User{}
- u1.Username = "jimbo" + model.NewId()
- u1.FirstName = "Tim"
- u1.LastName = "Bill"
- u1.Nickname = "Rob"
- u1.Email = "harold" + model.NewId() + "@simulator.amazonses.com"
+ u1 := &model.User{
+ Username: "jimbo1" + model.NewId(),
+ FirstName: "Tim",
+ LastName: "Bill",
+ Nickname: "Rob",
+ Email: "harold" + model.NewId() + "@simulator.amazonses.com",
+ }
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
- u2 := &model.User{}
- u2.Username = "jim-bobby" + model.NewId()
- u2.Email = MakeEmail()
+ u2 := &model.User{
+ Username: "jim-bobby" + model.NewId(),
+ Email: MakeEmail(),
+ }
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
- u3 := &model.User{}
- u3.Username = "jimbo" + model.NewId()
- u3.Email = MakeEmail()
- u3.DeleteAt = 1
+ u3 := &model.User{
+ Username: "jimbo3" + model.NewId(),
+ Email: MakeEmail(),
+ DeleteAt: 1,
+ }
store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
- u5 := &model.User{}
- u5.Username = "yu" + model.NewId()
- u5.FirstName = "En"
- u5.LastName = "Yu"
- u5.Nickname = "enyu"
- u5.Email = MakeEmail()
+ u5 := &model.User{
+ Username: "yu" + model.NewId(),
+ FirstName: "En",
+ LastName: "Yu",
+ Nickname: "enyu",
+ Email: MakeEmail(),
+ }
store.Must(ss.User().Save(u5))
+ defer ss.User().PermanentDelete(u5.Id)
- u6 := &model.User{}
- u6.Username = "underscore" + model.NewId()
- u6.FirstName = "Du_"
- u6.LastName = "_DE"
- u6.Nickname = "lodash"
- u6.Email = MakeEmail()
+ u6 := &model.User{
+ Username: "underscore" + model.NewId(),
+ FirstName: "Du_",
+ LastName: "_DE",
+ Nickname: "lodash",
+ Email: MakeEmail(),
+ }
store.Must(ss.User().Save(u6))
+ defer ss.User().PermanentDelete(u6.Id)
tid := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}, -1))
@@ -1378,550 +1463,855 @@ func testUserStoreSearch(t *testing.T, ss store.Store) {
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u5.Id}, -1))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u6.Id}, -1))
- searchOptions := map[string]bool{}
- searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
-
- if r1 := <-ss.User().Search(tid, "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found1 := false
- found2 := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found1 = true
- }
-
- if profile.Id == u3.Id {
- found2 = true
- }
- }
-
- if !found1 {
- t.Fatal("should have found user")
- }
-
- if found2 {
- t.Fatal("should not have found inactive user")
- }
- }
-
- if r1 := <-ss.User().Search(tid, "en", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found1 := false
- for _, profile := range profiles {
- if profile.Id == u5.Id {
- found1 = true
- }
- }
+ // The users returned from the database will have AuthData as an empty string.
+ nilAuthData := new(string)
+ *nilAuthData = ""
+
+ u1.AuthData = nilAuthData
+ u2.AuthData = nilAuthData
+ u3.AuthData = nilAuthData
+ u5.AuthData = nilAuthData
+ u6.AuthData = nilAuthData
+
+ testCases := []struct {
+ Description string
+ TeamId string
+ Term string
+ Options *model.UserSearchOptions
+ Expected []*model.User
+ }{
+ {
+ "search jimb",
+ tid,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search en",
+ tid,
+ "en",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u5},
+ },
+ {
+ "search email",
+ tid,
+ u1.Email,
+ &model.UserSearchOptions{
+ AllowEmails: true,
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search maps * to space",
+ tid,
+ "jimb*",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "should not return spurious matches",
+ tid,
+ "harol",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "% should be escaped",
+ tid,
+ "h%",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "_ should be escaped",
+ tid,
+ "h_",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "_ should be escaped (2)",
+ tid,
+ "Du_",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u6},
+ },
+ {
+ "_ should be escaped (2)",
+ tid,
+ "_dE",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u6},
+ },
+ {
+ "search jimb, allowing inactive",
+ tid,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowInactive: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1, u3},
+ },
+ {
+ "search jimb, no team id",
+ "",
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search jim-bobb, no team id",
+ "",
+ "jim-bobb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u2},
+ },
+
+ {
+ "search harol, search all fields",
+ tid,
+ "harol",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowEmails: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search Tim, search all fields",
+ tid,
+ "Tim",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowEmails: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search Tim, don't search full names",
+ tid,
+ "Tim",
+ &model.UserSearchOptions{
+ AllowFullNames: false,
+ AllowEmails: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "search Bill, search all fields",
+ tid,
+ "Bill",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowEmails: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search Rob, search all fields",
+ tid,
+ "Rob",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowEmails: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.Description, func(t *testing.T) {
+ result := <-ss.User().Search(testCase.TeamId, testCase.Term, testCase.Options)
+ require.Nil(t, result.Err)
+ assertUsers(t, testCase.Expected, result.Data.([]*model.User))
+ })
+ }
+
+ t.Run("search empty string", func(t *testing.T) {
+ searchOptions := &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ }
+
+ r1 := <-ss.User().Search(tid, "", searchOptions)
+ require.Nil(t, r1.Err)
+ assert.Len(t, r1.Data.([]*model.User), 4)
+ // Don't assert contents, since Postgres' default collation order is left up to
+ // the operating system, and jimbo1 might sort before or after jim-bo.
+ // assertUsers(t, []*model.User{u2, u1, u6, u5}, r1.Data.([]*model.User))
+ })
+
+ t.Run("search empty string, limit 2", func(t *testing.T) {
+ searchOptions := &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: 2,
+ }
+
+ r1 := <-ss.User().Search(tid, "", searchOptions)
+ require.Nil(t, r1.Err)
+ assert.Len(t, r1.Data.([]*model.User), 2)
+ // Don't assert contents, since Postgres' default collation order is left up to
+ // the operating system, and jimbo1 might sort before or after jim-bo.
+ // assertUsers(t, []*model.User{u2, u1, u6, u5}, r1.Data.([]*model.User))
+ })
+}
- if !found1 {
- t.Fatal("should have found user")
- }
+func testUserStoreSearchNotInChannel(t *testing.T, ss store.Store) {
+ u1 := &model.User{
+ Username: "jimbo1" + model.NewId(),
+ FirstName: "Tim",
+ LastName: "Bill",
+ Nickname: "Rob",
+ Email: "harold" + model.NewId() + "@simulator.amazonses.com",
}
+ store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
- searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = false
-
- if r1 := <-ss.User().Search(tid, u1.Email, searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found1 := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found1 = true
- }
- }
-
- if !found1 {
- t.Fatal("should have found user")
- }
+ u2 := &model.User{
+ Username: "jim2-bobby" + model.NewId(),
+ Email: MakeEmail(),
}
+ store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
- searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
-
- // * should be treated as a space
- if r1 := <-ss.User().Search(tid, "jimb*", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found1 := false
- found2 := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found1 = true
- }
-
- if profile.Id == u3.Id {
- found2 = true
- }
- }
-
- if !found1 {
- t.Fatal("should have found user")
- }
-
- if found2 {
- t.Fatal("should not have found inactive user")
- }
+ u3 := &model.User{
+ Username: "jimbo3" + model.NewId(),
+ Email: MakeEmail(),
+ DeleteAt: 1,
}
+ store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
- if r1 := <-ss.User().Search(tid, "harol", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found1 := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found1 = true
- }
- }
+ tid := model.NewId()
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}, -1))
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u2.Id}, -1))
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u3.Id}, -1))
- if found1 {
- t.Fatal("should not have found user")
- }
- }
+ // The users returned from the database will have AuthData as an empty string.
+ nilAuthData := new(string)
+ *nilAuthData = ""
- // % should be escaped and searched for.
- if r1 := <-ss.User().Search(tid, "h%", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- if len(profiles) != 0 {
- t.Fatal("shouldn't have found anything")
- }
- }
+ u1.AuthData = nilAuthData
+ u2.AuthData = nilAuthData
+ u3.AuthData = nilAuthData
- // "_" should be properly escaped and searched for.
- if r1 := <-ss.User().Search(tid, "h_", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- if len(profiles) != 0 {
- t.Fatal("shouldn't have found anything")
- }
+ c1 := model.Channel{
+ TeamId: tid,
+ DisplayName: "NameName",
+ Name: "zz" + model.NewId() + "b",
+ Type: model.CHANNEL_OPEN,
}
- if r1 := <-ss.User().Search(tid, "Du_", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found6 := false
- for _, profile := range profiles {
- if profile.Id == u6.Id {
- found6 = true
- }
- }
+ c1 = *store.Must(ss.Channel().Save(&c1, -1)).(*model.Channel)
- if !found6 {
- t.Fatal("should have found user")
- }
+ c2 := model.Channel{
+ TeamId: tid,
+ DisplayName: "NameName",
+ Name: "zz" + model.NewId() + "b",
+ Type: model.CHANNEL_OPEN,
+ }
+ c2 = *store.Must(ss.Channel().Save(&c2, -1)).(*model.Channel)
+
+ store.Must(ss.Channel().SaveMember(&model.ChannelMember{
+ ChannelId: c2.Id,
+ UserId: u1.Id,
+ NotifyProps: model.GetDefaultChannelNotifyProps(),
+ }))
+ store.Must(ss.Channel().SaveMember(&model.ChannelMember{
+ ChannelId: c1.Id,
+ UserId: u3.Id,
+ NotifyProps: model.GetDefaultChannelNotifyProps(),
+ }))
+ store.Must(ss.Channel().SaveMember(&model.ChannelMember{
+ ChannelId: c2.Id,
+ UserId: u2.Id,
+ NotifyProps: model.GetDefaultChannelNotifyProps(),
+ }))
+
+ testCases := []struct {
+ Description string
+ TeamId string
+ ChannelId string
+ Term string
+ Options *model.UserSearchOptions
+ Expected []*model.User
+ }{
+ {
+ "search jimb, channel 1",
+ tid,
+ c1.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search jimb, allow inactive, channel 1",
+ tid,
+ c1.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowInactive: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search jimb, channel 1, no team id",
+ "",
+ c1.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search jimb, channel 1, junk team id",
+ "junk",
+ c1.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "search jimb, channel 2",
+ tid,
+ c2.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "search jimb, allow inactive, channel 2",
+ tid,
+ c2.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowInactive: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u3},
+ },
+ {
+ "search jimb, channel 2, no team id",
+ "",
+ c2.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "search jimb, channel 2, junk team id",
+ "junk",
+ c2.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "search jim, channel 1",
+ tid,
+ c1.Id,
+ "jim",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u2, u1},
+ },
+ {
+ "search jim, channel 1, limit 1",
+ tid,
+ c1.Id,
+ "jim",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: 1,
+ },
+ []*model.User{u2},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.Description, func(t *testing.T) {
+ result := <-ss.User().SearchNotInChannel(
+ testCase.TeamId,
+ testCase.ChannelId,
+ testCase.Term,
+ testCase.Options,
+ )
+ require.Nil(t, result.Err)
+ assertUsers(t, testCase.Expected, result.Data.([]*model.User))
+ })
}
- if r1 := <-ss.User().Search(tid, "_dE", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found6 := false
- for _, profile := range profiles {
- if profile.Id == u6.Id {
- found6 = true
- }
- }
+}
- if !found6 {
- t.Fatal("should have found user")
- }
+func testUserStoreSearchInChannel(t *testing.T, ss store.Store) {
+ u1 := &model.User{
+ Username: "jimbo1" + model.NewId(),
+ FirstName: "Tim",
+ LastName: "Bill",
+ Nickname: "Rob",
+ Email: "harold" + model.NewId() + "@simulator.amazonses.com",
}
+ store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
- searchOptions[store.USER_SEARCH_OPTION_ALLOW_INACTIVE] = true
-
- if r1 := <-ss.User().Search(tid, "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found1 := false
- found2 := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found1 = true
- }
-
- if profile.Id == u3.Id {
- found2 = true
- }
- }
-
- if !found1 {
- t.Fatal("should have found user")
- }
-
- if !found2 {
- t.Fatal("should have found inactive user")
- }
+ u2 := &model.User{
+ Username: "jim-bobby" + model.NewId(),
+ Email: MakeEmail(),
}
+ store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
- searchOptions[store.USER_SEARCH_OPTION_ALLOW_INACTIVE] = false
-
- if r1 := <-ss.User().Search(tid, "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
+ u3 := &model.User{
+ Username: "jimbo3" + model.NewId(),
+ Email: MakeEmail(),
+ DeleteAt: 1,
}
+ store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
- if r1 := <-ss.User().Search("", "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
- }
+ tid := model.NewId()
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}, -1))
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u2.Id}, -1))
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u3.Id}, -1))
- if r1 := <-ss.User().Search("", "jim-bobb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- t.Log(profile.Username)
- if profile.Id == u2.Id {
- found = true
- break
- }
- }
+ // The users returned from the database will have AuthData as an empty string.
+ nilAuthData := new(string)
+ *nilAuthData = ""
- if !found {
- t.Fatal("should have found user")
- }
- }
+ u1.AuthData = nilAuthData
+ u2.AuthData = nilAuthData
+ u3.AuthData = nilAuthData
- if r1 := <-ss.User().Search(tid, "", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
+ c1 := model.Channel{
+ TeamId: tid,
+ DisplayName: "NameName",
+ Name: "zz" + model.NewId() + "b",
+ Type: model.CHANNEL_OPEN,
}
-
- c1 := model.Channel{}
- c1.TeamId = tid
- c1.DisplayName = "NameName"
- c1.Name = "zz" + model.NewId() + "b"
- c1.Type = model.CHANNEL_OPEN
c1 = *store.Must(ss.Channel().Save(&c1, -1)).(*model.Channel)
- if r1 := <-ss.User().SearchNotInChannel(tid, c1.Id, "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
- }
-
- if r1 := <-ss.User().SearchNotInChannel("", c1.Id, "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
- }
-
- if r1 := <-ss.User().SearchNotInChannel("junk", c1.Id, "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if found {
- t.Fatal("should not have found user")
- }
- }
-
- if r1 := <-ss.User().SearchInChannel(c1.Id, "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if found {
- t.Fatal("should not have found user")
- }
+ c2 := model.Channel{
+ TeamId: tid,
+ DisplayName: "NameName",
+ Name: "zz" + model.NewId() + "b",
+ Type: model.CHANNEL_OPEN,
+ }
+ c2 = *store.Must(ss.Channel().Save(&c2, -1)).(*model.Channel)
+
+ store.Must(ss.Channel().SaveMember(&model.ChannelMember{
+ ChannelId: c1.Id,
+ UserId: u1.Id,
+ NotifyProps: model.GetDefaultChannelNotifyProps(),
+ }))
+ store.Must(ss.Channel().SaveMember(&model.ChannelMember{
+ ChannelId: c2.Id,
+ UserId: u2.Id,
+ NotifyProps: model.GetDefaultChannelNotifyProps(),
+ }))
+ store.Must(ss.Channel().SaveMember(&model.ChannelMember{
+ ChannelId: c1.Id,
+ UserId: u3.Id,
+ NotifyProps: model.GetDefaultChannelNotifyProps(),
+ }))
+
+ testCases := []struct {
+ Description string
+ ChannelId string
+ Term string
+ Options *model.UserSearchOptions
+ Expected []*model.User
+ }{
+ {
+ "search jimb, channel 1",
+ c1.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search jimb, allow inactive, channel 1",
+ c1.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowInactive: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1, u3},
+ },
+ {
+ "search jimb, allow inactive, channel 1, limit 1",
+ c1.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowInactive: true,
+ Limit: 1,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search jimb, channel 2",
+ c2.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "search jimb, allow inactive, channel 2",
+ c2.Id,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowInactive: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.Description, func(t *testing.T) {
+ result := <-ss.User().SearchInChannel(
+ testCase.ChannelId,
+ testCase.Term,
+ testCase.Options,
+ )
+ require.Nil(t, result.Err)
+ assertUsers(t, testCase.Expected, result.Data.([]*model.User))
+ })
}
+}
- store.Must(ss.Channel().SaveMember(&model.ChannelMember{ChannelId: c1.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()}))
-
- if r1 := <-ss.User().SearchInChannel(c1.Id, "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
- }
-
- searchOptions = map[string]bool{}
-
- if r1 := <-ss.User().Search(tid, "harol", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found1 := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found1 = true
- }
- }
-
- if !found1 {
- t.Fatal("should have found user")
- }
+func testUserStoreSearchNotInTeam(t *testing.T, ss store.Store) {
+ u1 := &model.User{
+ Username: "jimbo1" + model.NewId(),
+ FirstName: "Tim",
+ LastName: "Bill",
+ Nickname: "Rob",
+ Email: "harold" + model.NewId() + "@simulator.amazonses.com",
}
+ store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
- if r1 := <-ss.User().Search(tid, "Tim", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
+ u2 := &model.User{
+ Username: "jim-bobby" + model.NewId(),
+ Email: MakeEmail(),
}
+ store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
- if r1 := <-ss.User().Search(tid, "Bill", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
+ u3 := &model.User{
+ Username: "jimbo3" + model.NewId(),
+ Email: MakeEmail(),
+ DeleteAt: 1,
}
+ store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
- if r1 := <-ss.User().Search(tid, "Rob", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
+ u4 := &model.User{
+ Username: "simon" + model.NewId(),
+ Email: MakeEmail(),
+ DeleteAt: 0,
}
-
- // Search Users not in Team.
- u4 := &model.User{}
- u4.Username = "simon" + model.NewId()
- u4.Email = MakeEmail()
- u4.DeleteAt = 0
store.Must(ss.User().Save(u4))
+ defer ss.User().PermanentDelete(u4.Id)
- if r1 := <-ss.User().SearchNotInTeam(tid, "simo", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u4.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
- }
-
- if r1 := <-ss.User().SearchNotInTeam(tid, "jimb", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found = true
- break
- }
- }
-
- if found {
- t.Fatal("should not have found user")
- }
+ u5 := &model.User{
+ Username: "yu" + model.NewId(),
+ FirstName: "En",
+ LastName: "Yu",
+ Nickname: "enyu",
+ Email: MakeEmail(),
}
+ store.Must(ss.User().Save(u5))
+ defer ss.User().PermanentDelete(u5.Id)
- // Check SearchNotInTeam finds previously deleted team members.
- store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u4.Id}, -1))
-
- if r1 := <-ss.User().SearchNotInTeam(tid, "simo", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u4.Id {
- found = true
- break
- }
- }
-
- if found {
- t.Fatal("should not have found user")
- }
+ u6 := &model.User{
+ Username: "underscore" + model.NewId(),
+ FirstName: "Du_",
+ LastName: "_DE",
+ Nickname: "lodash",
+ Email: MakeEmail(),
}
-
- store.Must(ss.Team().UpdateMember(&model.TeamMember{TeamId: tid, UserId: u4.Id, DeleteAt: model.GetMillis() - 1000}))
- if r1 := <-ss.User().SearchNotInTeam(tid, "simo", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
- found := false
- for _, profile := range profiles {
- if profile.Id == u4.Id {
- found = true
- break
- }
- }
-
- if !found {
- t.Fatal("should have found user")
- }
+ store.Must(ss.User().Save(u6))
+ defer ss.User().PermanentDelete(u6.Id)
+
+ teamId1 := model.NewId()
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: u1.Id}, -1))
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: u2.Id}, -1))
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: u3.Id}, -1))
+ // u4 is not in team 1
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: u5.Id}, -1))
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId1, UserId: u6.Id}, -1))
+
+ teamId2 := model.NewId()
+ store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId2, UserId: u4.Id}, -1))
+
+ // The users returned from the database will have AuthData as an empty string.
+ nilAuthData := new(string)
+ *nilAuthData = ""
+
+ u1.AuthData = nilAuthData
+ u2.AuthData = nilAuthData
+ u3.AuthData = nilAuthData
+ u4.AuthData = nilAuthData
+ u5.AuthData = nilAuthData
+ u6.AuthData = nilAuthData
+
+ testCases := []struct {
+ Description string
+ TeamId string
+ Term string
+ Options *model.UserSearchOptions
+ Expected []*model.User
+ }{
+ {
+ "search simo, team 1",
+ teamId1,
+ "simo",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u4},
+ },
+
+ {
+ "search jimb, team 1",
+ teamId1,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "search jimb, allow inactive, team 1",
+ teamId1,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowInactive: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "search simo, team 2",
+ teamId2,
+ "simo",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{},
+ },
+ {
+ "search jimb, team1",
+ teamId2,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1},
+ },
+ {
+ "search jimb, allow inactive, team 2",
+ teamId2,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowInactive: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u1, u3},
+ },
+ {
+ "search jimb, allow inactive, team 2, limit 1",
+ teamId2,
+ "jimb",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ AllowInactive: true,
+ Limit: 1,
+ },
+ []*model.User{u1},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.Description, func(t *testing.T) {
+ result := <-ss.User().SearchNotInTeam(
+ testCase.TeamId,
+ testCase.Term,
+ testCase.Options,
+ )
+ require.Nil(t, result.Err)
+ assertUsers(t, testCase.Expected, result.Data.([]*model.User))
+ })
}
-
- // Check PLT-8354 - search that ends up with just space for terms doesn't error.
- r1 := <-ss.User().SearchWithoutTeam("* ", searchOptions)
- assert.Nil(t, r1.Err)
}
func testUserStoreSearchWithoutTeam(t *testing.T, ss store.Store) {
- u1 := &model.User{}
- u1.Username = "jimbo" + model.NewId()
- u1.FirstName = "Tim"
- u1.LastName = "Bill"
- u1.Nickname = "Rob"
- u1.Email = "harold" + model.NewId() + "@simulator.amazonses.com"
+ u1 := &model.User{
+ Username: "jimbo1" + model.NewId(),
+ FirstName: "Tim",
+ LastName: "Bill",
+ Nickname: "Rob",
+ Email: "harold" + model.NewId() + "@simulator.amazonses.com",
+ }
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
- u2 := &model.User{}
- u2.Username = "jim-bobby" + model.NewId()
- u2.Email = MakeEmail()
+ u2 := &model.User{
+ Username: "jim2-bobby" + model.NewId(),
+ Email: MakeEmail(),
+ }
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
- u3 := &model.User{}
- u3.Username = "jimbo" + model.NewId()
- u3.Email = MakeEmail()
- u3.DeleteAt = 1
+ u3 := &model.User{
+ Username: "jimbo3" + model.NewId(),
+ Email: MakeEmail(),
+ DeleteAt: 1,
+ }
store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
tid := model.NewId()
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u3.Id}, -1))
- searchOptions := map[string]bool{}
- searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
-
- if r1 := <-ss.User().SearchWithoutTeam("", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- }
-
- if r1 := <-ss.User().SearchWithoutTeam("jim", searchOptions); r1.Err != nil {
- t.Fatal(r1.Err)
- } else {
- profiles := r1.Data.([]*model.User)
-
- found1 := false
- found2 := false
- found3 := false
-
- for _, profile := range profiles {
- if profile.Id == u1.Id {
- found1 = true
- } else if profile.Id == u2.Id {
- found2 = true
- } else if profile.Id == u3.Id {
- found3 = true
- }
- }
-
- if !found1 {
- t.Fatal("should have found user1")
- } else if !found2 {
- t.Fatal("should have found user2")
- } else if found3 {
- t.Fatal("should not have found user3")
- }
+ // The users returned from the database will have AuthData as an empty string.
+ nilAuthData := new(string)
+ *nilAuthData = ""
+
+ u1.AuthData = nilAuthData
+ u2.AuthData = nilAuthData
+ u3.AuthData = nilAuthData
+
+ testCases := []struct {
+ Description string
+ Term string
+ Options *model.UserSearchOptions
+ Expected []*model.User
+ }{
+ {
+ "empty string",
+ "",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u2, u1},
+ },
+ {
+ "jim",
+ "jim",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u2, u1},
+ },
+ {
+ "PLT-8354",
+ "* ",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: model.USER_SEARCH_DEFAULT_LIMIT,
+ },
+ []*model.User{u2, u1},
+ },
+ {
+ "jim, limit 1",
+ "jim",
+ &model.UserSearchOptions{
+ AllowFullNames: true,
+ Limit: 1,
+ },
+ []*model.User{u2},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.Description, func(t *testing.T) {
+ result := <-ss.User().SearchWithoutTeam(
+ testCase.Term,
+ testCase.Options,
+ )
+ require.Nil(t, result.Err)
+ assertUsers(t, testCase.Expected, result.Data.([]*model.User))
+ })
}
}
@@ -1929,6 +2319,7 @@ func testUserStoreAnalyticsGetInactiveUsersCount(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
var count int64
@@ -1942,6 +2333,7 @@ func testUserStoreAnalyticsGetInactiveUsersCount(t *testing.T, ss store.Store) {
u2.Email = MakeEmail()
u2.DeleteAt = model.GetMillis()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
if result := <-ss.User().AnalyticsGetInactiveUsersCount(); result.Err != nil {
t.Fatal(result.Err)
@@ -1973,10 +2365,12 @@ func testUserStoreAnalyticsGetSystemAdminCount(t *testing.T, ss store.Store) {
if err := (<-ss.User().Save(&u1)).Err; err != nil {
t.Fatal("couldn't save user", err)
}
+ defer ss.User().PermanentDelete(u1.Id)
if err := (<-ss.User().Save(&u2)).Err; err != nil {
t.Fatal("couldn't save user", err)
}
+ defer ss.User().PermanentDelete(u2.Id)
if result := <-ss.User().AnalyticsGetSystemAdminCount(); result.Err != nil {
t.Fatal(result.Err)
@@ -1994,12 +2388,14 @@ func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()
store.Must(ss.User().Save(u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}, -1))
store.Must(ss.User().UpdateUpdateAt(u1.Id))
u2 := &model.User{}
u2.Email = MakeEmail()
store.Must(ss.User().Save(u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.User().UpdateUpdateAt(u2.Id))
var initialUsersNotInTeam int
@@ -2106,6 +2502,7 @@ func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) {
u3 := &model.User{}
u3.Email = MakeEmail()
store.Must(ss.User().Save(u3))
+ defer ss.User().PermanentDelete(u3.Id)
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1))
store.Must(ss.User().UpdateUpdateAt(u3.Id))
@@ -2143,9 +2540,13 @@ func testUserStoreClearAllCustomRoleAssignments(t *testing.T, ss store.Store) {
}
store.Must(ss.User().Save(&u1))
+ defer ss.User().PermanentDelete(u1.Id)
store.Must(ss.User().Save(&u2))
+ defer ss.User().PermanentDelete(u2.Id)
store.Must(ss.User().Save(&u3))
+ defer ss.User().PermanentDelete(u3.Id)
store.Must(ss.User().Save(&u4))
+ defer ss.User().PermanentDelete(u4.Id)
require.Nil(t, (<-ss.User().ClearAllCustomRoleAssignments()).Err)
@@ -2173,6 +2574,7 @@ func testUserStoreGetAllAfter(t *testing.T, ss store.Store) {
Roles: "system_user system_admin system_post_all",
}
store.Must(ss.User().Save(&u1))
+ defer ss.User().PermanentDelete(u1.Id)
r1 := <-ss.User().GetAllAfter(10000, strings.Repeat("0", 26))
require.Nil(t, r1.Err)