mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-21551] Add search tests structure to test the search engines (#14031)
* WIP * Adding bleve to go modules * WIP * Adding missing files from searchengine implementation * WIP * WIP * WIP * WIP * WIP * WIP * User and channel indexing and searches implemented * Make bleve tests run with in-memory indexes * Implement post index and deletion tests * Initial commits for the search layer * Removing unnecesary indexing * WIP * WIP * More fixes for tests * Adding the search layer * Finishing the migration of searchers to the layer * Removing unnecesary code * Allowing multiple engines active at the same time * WIP * Add simple post search * Print information when using bleve * Adding some debugging to understand better how the searches are working * Making more dynamic config of search engines * Add post search basics * Adding the Purge API endpoint * Fixing bleve config updates * Adding missed file * Regenerating search engine mocks * Adding missed v5 to modules imports * fixing i18n * Fixing some test around search engine * Removing all bleve traces * Cleaning up the vendors directory and go.mod/go.sum files * Regenerating timer layer * Adding properly the license * Fixing govet shadow error * Fixing some tests * Fixing TestSearchPostsFromUser * Fixing another test * Fixing more tests * Fixing more tests * Removing SearchEngine redundant text from searchengine module code * Fixing some reindexing problems in members updates * Fixing tests * Addressing PR comments * Reverting go.mod and go.sum * Addressing PR comments * Fixing tests compilation * Fixing govet * Adding search engine stop method * Being more explicit on where we use includeDeleted * Adding GetSqlSupplier test helper method * Mocking elasticsearch start function * Fixing tests * Search tests * Fix tests * Fix mod * Fixing searchEngine for test helpers with store mocks * Remove loglines * Fix i18n strings * Migrate search posts tests * Fix linter * Do not run search tests if -short flag is enabled * Migrate back store tests that didn't belong to the searchlayer * Fix scopelint issues Co-authored-by: Jesús Espino <jespinog@gmail.com>
This commit is contained in:
committed by
GitHub
parent
2b1b001bcc
commit
4fe25b1cdd
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v5/config"
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/services/searchengine"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
"github.com/mattermost/mattermost-server/v5/store/localcachelayer"
|
||||
"github.com/mattermost/mattermost-server/v5/store/storetest/mocks"
|
||||
@@ -63,7 +64,7 @@ func SetMainHelper(mh *testlib.MainHelper) {
|
||||
mainHelper = mh
|
||||
}
|
||||
|
||||
func setupTestHelper(dbStore store.Store, enterprise bool, includeCache bool, updateConfig func(*model.Config)) *TestHelper {
|
||||
func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, enterprise bool, includeCache bool, updateConfig func(*model.Config)) *TestHelper {
|
||||
tempWorkspace, err := ioutil.TempDir("", "apptest")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -102,6 +103,10 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCache bool, up
|
||||
IncludeCacheLayer: includeCache,
|
||||
}
|
||||
|
||||
if searchEngine != nil {
|
||||
th.App.SetSearchEngine(searchEngine)
|
||||
}
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.TeamSettings.MaxUsersPerTeam = 50
|
||||
*cfg.RateLimitSettings.Enable = false
|
||||
@@ -163,7 +168,8 @@ func SetupEnterprise(tb testing.TB) *TestHelper {
|
||||
dbStore := mainHelper.GetStore()
|
||||
dbStore.DropAllTables()
|
||||
dbStore.MarkSystemRanUnitTests()
|
||||
return setupTestHelper(dbStore, true, true, nil)
|
||||
searchEngine := mainHelper.GetSearchEngine()
|
||||
return setupTestHelper(dbStore, searchEngine, true, true, nil)
|
||||
}
|
||||
|
||||
func Setup(tb testing.TB) *TestHelper {
|
||||
@@ -178,7 +184,8 @@ func Setup(tb testing.TB) *TestHelper {
|
||||
dbStore := mainHelper.GetStore()
|
||||
dbStore.DropAllTables()
|
||||
dbStore.MarkSystemRanUnitTests()
|
||||
return setupTestHelper(dbStore, false, true, nil)
|
||||
searchEngine := mainHelper.GetSearchEngine()
|
||||
return setupTestHelper(dbStore, searchEngine, false, true, nil)
|
||||
}
|
||||
|
||||
func SetupConfig(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
||||
@@ -193,11 +200,12 @@ func SetupConfig(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelpe
|
||||
dbStore := mainHelper.GetStore()
|
||||
dbStore.DropAllTables()
|
||||
dbStore.MarkSystemRanUnitTests()
|
||||
return setupTestHelper(dbStore, false, true, updateConfig)
|
||||
searchEngine := mainHelper.GetSearchEngine()
|
||||
return setupTestHelper(dbStore, searchEngine, false, true, updateConfig)
|
||||
}
|
||||
|
||||
func SetupConfigWithStoreMock(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
||||
th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), false, false, updateConfig)
|
||||
th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, false, false, updateConfig)
|
||||
emptyMockStore := mocks.Store{}
|
||||
emptyMockStore.On("Close").Return(nil)
|
||||
th.App.Srv().Store = &emptyMockStore
|
||||
@@ -205,7 +213,7 @@ func SetupConfigWithStoreMock(tb testing.TB, updateConfig func(cfg *model.Config
|
||||
}
|
||||
|
||||
func SetupWithStoreMock(tb testing.TB) *TestHelper {
|
||||
th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), false, false, nil)
|
||||
th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, false, false, nil)
|
||||
emptyMockStore := mocks.Store{}
|
||||
emptyMockStore.On("Close").Return(nil)
|
||||
th.App.Srv().Store = &emptyMockStore
|
||||
@@ -213,7 +221,7 @@ func SetupWithStoreMock(tb testing.TB) *TestHelper {
|
||||
}
|
||||
|
||||
func SetupEnterpriseWithStoreMock(tb testing.TB) *TestHelper {
|
||||
th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), true, false, nil)
|
||||
th := setupTestHelper(testlib.GetMockStoreForSetupFunctions(), nil, true, false, nil)
|
||||
emptyMockStore := mocks.Store{}
|
||||
emptyMockStore.On("Close").Return(nil)
|
||||
th.App.Srv().Store = &emptyMockStore
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/services/searchengine"
|
||||
)
|
||||
|
||||
func (a *App) TestElasticsearch(cfg *model.Config) *model.AppError {
|
||||
@@ -43,3 +44,7 @@ func (a *App) PurgeElasticsearchIndexes() *model.AppError {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SetSearchEngine(se *searchengine.Broker) {
|
||||
a.searchEngine = se
|
||||
}
|
||||
|
||||
@@ -3970,6 +3970,10 @@
|
||||
"id": "ent.elasticsearch.purge_indexes.delete_failed",
|
||||
"translation": "Failed to delete Elasticsearch index"
|
||||
},
|
||||
{
|
||||
"id": "ent.elasticsearch.refresh_indexes.refresh_failed",
|
||||
"translation": "Failed to refresh Elasticsearch indexes"
|
||||
},
|
||||
{
|
||||
"id": "ent.elasticsearch.search_channels.disabled",
|
||||
"translation": "Elasticsearch searching is disabled on this server"
|
||||
@@ -5682,6 +5686,10 @@
|
||||
"id": "store.insert_error",
|
||||
"translation": "insert error"
|
||||
},
|
||||
{
|
||||
"id": "store.search_user_store.empty_team_id",
|
||||
"translation": "Failed to get list of allowed channels for team: empty teamId"
|
||||
},
|
||||
{
|
||||
"id": "store.select_error",
|
||||
"translation": "select error"
|
||||
|
||||
@@ -19,6 +19,7 @@ type SearchEngineInterface interface {
|
||||
IsIndexingEnabled() bool
|
||||
IsSearchEnabled() bool
|
||||
IsAutocompletionEnabled() bool
|
||||
IsIndexingSync() bool
|
||||
IndexPost(post *model.Post, teamId string) *model.AppError
|
||||
SearchPosts(channels *model.ChannelList, searchParams []*model.SearchParams, page, perPage int) ([]string, model.PostSearchMatches, *model.AppError)
|
||||
DeletePost(post *model.Post) *model.AppError
|
||||
@@ -31,5 +32,6 @@ type SearchEngineInterface interface {
|
||||
DeleteUser(user *model.User) *model.AppError
|
||||
TestConfig(cfg *model.Config) *model.AppError
|
||||
PurgeIndexes() *model.AppError
|
||||
RefreshIndexes() *model.AppError
|
||||
DataRetentionDeleteIndexes(cutoff time.Time) *model.AppError
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
model "github.com/mattermost/mattermost-server/v5/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
time "time"
|
||||
)
|
||||
|
||||
// SearchEngineInterface is an autogenerated mock type for the SearchEngineInterface type
|
||||
@@ -198,6 +198,20 @@ func (_m *SearchEngineInterface) IsIndexingEnabled() bool {
|
||||
return r0
|
||||
}
|
||||
|
||||
// IsIndexingSync provides a mock function with given fields:
|
||||
func (_m *SearchEngineInterface) IsIndexingSync() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// IsSearchEnabled provides a mock function with given fields:
|
||||
func (_m *SearchEngineInterface) IsSearchEnabled() bool {
|
||||
ret := _m.Called()
|
||||
@@ -228,6 +242,22 @@ func (_m *SearchEngineInterface) PurgeIndexes() *model.AppError {
|
||||
return r0
|
||||
}
|
||||
|
||||
// RefreshIndexes provides a mock function with given fields:
|
||||
func (_m *SearchEngineInterface) RefreshIndexes() *model.AppError {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func() *model.AppError); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SearchChannels provides a mock function with given fields: teamId, term
|
||||
func (_m *SearchEngineInterface) SearchChannels(teamId string, term string) ([]string, *model.AppError) {
|
||||
ret := _m.Called(teamId, term)
|
||||
|
||||
@@ -19,12 +19,12 @@ func (c *SearchChannelStore) deleteChannelIndex(channel *model.Channel) {
|
||||
if channel.Type == model.CHANNEL_OPEN {
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteChannel(channel); err != nil {
|
||||
mlog.Error("Encountered error deleting channel", mlog.String("channel_id", channel.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
}
|
||||
mlog.Debug("Removed channel from index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("channel_id", channel.Id))
|
||||
})(engine)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,12 +34,12 @@ func (c *SearchChannelStore) indexChannel(channel *model.Channel) {
|
||||
if channel.Type == model.CHANNEL_OPEN {
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.IndexChannel(channel); err != nil {
|
||||
mlog.Error("Encountered error indexing channel", mlog.String("channel_id", channel.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
}
|
||||
mlog.Debug("Indexed channel in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("channel_id", channel.Id))
|
||||
})(engine)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (s SearchStore) indexUserFromID(userId string) {
|
||||
func (s SearchStore) indexUser(user *model.User) {
|
||||
for _, engine := range s.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
userTeams, err := s.Team().GetTeamsByUserId(user.Id)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error indexing user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
@@ -87,7 +87,21 @@ func (s SearchStore) indexUser(user *model.User) {
|
||||
return
|
||||
}
|
||||
mlog.Debug("Indexed user in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("user_id", user.Id))
|
||||
})(engine)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Runs an indexing function synchronously or asynchronously depending on the engine
|
||||
func runIndexFn(engine searchengine.SearchEngineInterface, indexFn func(searchengine.SearchEngineInterface)) {
|
||||
if engine.IsIndexingSync() {
|
||||
indexFn(engine)
|
||||
if err := engine.RefreshIndexes(); err != nil {
|
||||
mlog.Error("Encountered error refresh the indexes", mlog.Err(err))
|
||||
}
|
||||
} else {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
indexFn(engineCopy)
|
||||
})(engine)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type SearchPostStore struct {
|
||||
func (s SearchPostStore) indexPost(post *model.Post) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
channel, chanErr := s.rootStore.Channel().Get(post.ChannelId, true)
|
||||
if chanErr != nil {
|
||||
mlog.Error("Couldn't get channel for post for SearchEngine indexing.", mlog.String("channel_id", post.ChannelId), mlog.String("search_engine", engineCopy.GetName()), mlog.String("post_id", post.Id), mlog.Err(chanErr))
|
||||
@@ -28,7 +28,7 @@ func (s SearchPostStore) indexPost(post *model.Post) {
|
||||
mlog.Error("Encountered error indexing post", mlog.String("post_id", post.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
}
|
||||
mlog.Debug("Indexed post in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("post_id", post.Id))
|
||||
})(engine)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,12 +36,12 @@ func (s SearchPostStore) indexPost(post *model.Post) {
|
||||
func (s SearchPostStore) deletePostIndex(post *model.Post) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeletePost(post); err != nil {
|
||||
mlog.Error("Encountered error deleting post", mlog.String("post_id", post.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
}
|
||||
mlog.Debug("Removed post from the index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("post_id", post.Id))
|
||||
})(engine)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
@@ -20,13 +21,13 @@ type SearchUserStore struct {
|
||||
func (s *SearchUserStore) deleteUserIndex(user *model.User) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteUser(user); err != nil {
|
||||
mlog.Error("Encountered error deleting user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
mlog.Debug("Removed user from the index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("user_id", user.Id))
|
||||
})(engine)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,11 +46,13 @@ func (s *SearchUserStore) Search(teamId, term string, options *model.UserSearchO
|
||||
|
||||
usersIds, err := engine.SearchUsersInTeam(teamId, listOfAllowedChannels, term, options)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error on Search", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
|
||||
users, err := s.UserStore.GetProfileByIds(usersIds, nil, false)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error on Search", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -58,6 +61,7 @@ func (s *SearchUserStore) Search(teamId, term string, options *model.UserSearchO
|
||||
}
|
||||
}
|
||||
mlog.Debug("Using database search because no other search engine is available")
|
||||
|
||||
return s.UserStore.Search(teamId, term, options)
|
||||
}
|
||||
|
||||
@@ -138,6 +142,10 @@ func (s *SearchUserStore) autocompleteUsersInChannelByEngine(engine searchengine
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) getListOfAllowedChannelsForTeam(teamId string, viewRestrictions *model.ViewUsersRestrictions) ([]string, *model.AppError) {
|
||||
if len(teamId) == 0 {
|
||||
return nil, model.NewAppError("SearchUserStore", "store.search_user_store.empty_team_id", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
var listOfAllowedChannels []string
|
||||
if viewRestrictions == nil || strings.Contains(strings.Join(viewRestrictions.Teams, "."), teamId) {
|
||||
channels, err := s.rootStore.Channel().GetTeamChannels(teamId)
|
||||
@@ -152,7 +160,12 @@ func (s *SearchUserStore) getListOfAllowedChannelsForTeam(teamId string, viewRes
|
||||
return channelIds, nil
|
||||
}
|
||||
|
||||
if len(viewRestrictions.Channels) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
channels, err := s.rootStore.Channel().GetChannelsByIds(viewRestrictions.Channels, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
211
store/searchtest/channel_layer.go
Normal file
211
store/searchtest/channel_layer.go
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchtest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var searchChannelStoreTests = []searchTest{
|
||||
{"Database Channel AutocompleteInTeamForSearch tests", testSearchDatabaseChannelAutocompleteInTeamForSearch, []string{ENGINE_MYSQL, ENGINE_POSTGRES}},
|
||||
{"Elasticsearch search channels", testSearchESSearchChannels, []string{ENGINE_ELASTICSEARCH}},
|
||||
}
|
||||
|
||||
func TestSearchChannelStore(t *testing.T, s store.Store, testEngine *SearchTestEngine) {
|
||||
runTestSearch(t, s, testEngine, searchChannelStoreTests)
|
||||
}
|
||||
|
||||
func testSearchDatabaseChannelAutocompleteInTeamForSearch(t *testing.T, s store.Store) {
|
||||
u1 := &model.User{}
|
||||
u1.Email = makeEmail()
|
||||
u1.Username = "user1" + model.NewId()
|
||||
u1.Nickname = model.NewId()
|
||||
_, err := s.User().Save(u1)
|
||||
require.Nil(t, err)
|
||||
|
||||
u2 := &model.User{}
|
||||
u2.Email = makeEmail()
|
||||
u2.Username = "user2" + model.NewId()
|
||||
u2.Nickname = model.NewId()
|
||||
_, err = s.User().Save(u2)
|
||||
require.Nil(t, err)
|
||||
|
||||
u3 := &model.User{}
|
||||
u3.Email = makeEmail()
|
||||
u3.Username = "user3" + model.NewId()
|
||||
u3.Nickname = model.NewId()
|
||||
_, err = s.User().Save(u3)
|
||||
require.Nil(t, err)
|
||||
|
||||
u4 := &model.User{}
|
||||
u4.Email = makeEmail()
|
||||
u4.Username = "user4" + model.NewId()
|
||||
u4.Nickname = model.NewId()
|
||||
_, err = s.User().Save(u4)
|
||||
require.Nil(t, err)
|
||||
|
||||
o1 := model.Channel{}
|
||||
o1.TeamId = model.NewId()
|
||||
o1.DisplayName = "ChannelA"
|
||||
o1.Name = "zz" + model.NewId() + "b"
|
||||
o1.Type = model.CHANNEL_OPEN
|
||||
_, err = s.Channel().Save(&o1, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = o1.Id
|
||||
m1.UserId = u1.Id
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = s.Channel().SaveMember(&m1)
|
||||
require.Nil(t, err)
|
||||
|
||||
o2 := model.Channel{}
|
||||
o2.TeamId = model.NewId()
|
||||
o2.DisplayName = "Channel2"
|
||||
o2.Name = "zz" + model.NewId() + "b"
|
||||
o2.Type = model.CHANNEL_OPEN
|
||||
_, err = s.Channel().Save(&o2, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
m2 := model.ChannelMember{}
|
||||
m2.ChannelId = o2.Id
|
||||
m2.UserId = m1.UserId
|
||||
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = s.Channel().SaveMember(&m2)
|
||||
require.Nil(t, err)
|
||||
|
||||
o3 := model.Channel{}
|
||||
o3.TeamId = o1.TeamId
|
||||
o3.DisplayName = "ChannelA"
|
||||
o3.Name = "zz" + model.NewId() + "b"
|
||||
o3.Type = model.CHANNEL_OPEN
|
||||
_, err = s.Channel().Save(&o3, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
m3 := model.ChannelMember{}
|
||||
m3.ChannelId = o3.Id
|
||||
m3.UserId = m1.UserId
|
||||
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = s.Channel().SaveMember(&m3)
|
||||
require.Nil(t, err)
|
||||
|
||||
err = s.Channel().SetDeleteAt(o3.Id, 100, 100)
|
||||
require.Nil(t, err, "channel should have been deleted")
|
||||
|
||||
o4 := model.Channel{}
|
||||
o4.TeamId = o1.TeamId
|
||||
o4.DisplayName = "ChannelA"
|
||||
o4.Name = "zz" + model.NewId() + "b"
|
||||
o4.Type = model.CHANNEL_PRIVATE
|
||||
_, err = s.Channel().Save(&o4, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
m4 := model.ChannelMember{}
|
||||
m4.ChannelId = o4.Id
|
||||
m4.UserId = m1.UserId
|
||||
m4.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = s.Channel().SaveMember(&m4)
|
||||
require.Nil(t, err)
|
||||
|
||||
o5 := model.Channel{}
|
||||
o5.TeamId = o1.TeamId
|
||||
o5.DisplayName = "ChannelC"
|
||||
o5.Name = "zz" + model.NewId() + "b"
|
||||
o5.Type = model.CHANNEL_PRIVATE
|
||||
_, err = s.Channel().Save(&o5, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = s.Channel().CreateDirectChannel(u1, u2)
|
||||
require.Nil(t, err)
|
||||
_, err = s.Channel().CreateDirectChannel(u2, u3)
|
||||
require.Nil(t, err)
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
term string
|
||||
includeDeleted bool
|
||||
expectedMatches int
|
||||
}{
|
||||
{"Empty search (list all)", "", false, 4},
|
||||
{"Narrow search", "ChannelA", false, 2},
|
||||
{"Wide search", "Cha", false, 3},
|
||||
{"Direct messages", "user", false, 1},
|
||||
{"Wide search with archived channels", "Cha", true, 4},
|
||||
{"Narrow with archived channels", "ChannelA", true, 3},
|
||||
{"Direct messages with archived channels", "user", true, 1},
|
||||
{"Search without results", "blarg", true, 0},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
channels, err := s.Channel().AutocompleteInTeamForSearch(o1.TeamId, m1.UserId, "ChannelA", false)
|
||||
require.Nil(t, err)
|
||||
require.Len(t, *channels, 2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testSearchESSearchChannels(t *testing.T, s store.Store) {
|
||||
team, err := s.Team().Save(&model.Team{Name: "team", DisplayName: "team", Type: model.TEAM_OPEN})
|
||||
require.Nil(t, err)
|
||||
channel1, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "channel", DisplayName: "Test One", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
channel2, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "channel-second", DisplayName: "Test Two", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
channel3, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "channel_third", DisplayName: "Test Three", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Term string
|
||||
Expected []string
|
||||
}{
|
||||
{
|
||||
Name: "autocomplete search for all channels by name",
|
||||
Term: "cha",
|
||||
Expected: []string{channel1.Id, channel2.Id, channel3.Id},
|
||||
},
|
||||
{
|
||||
Name: "autocomplete search for one channel by display name",
|
||||
Term: "one",
|
||||
Expected: []string{channel1.Id},
|
||||
},
|
||||
{
|
||||
Name: "autocomplete search for one channel split by -",
|
||||
Term: "seco",
|
||||
Expected: []string{channel2.Id},
|
||||
},
|
||||
{
|
||||
Name: "autocomplete search for one channel split by _",
|
||||
Term: "thir",
|
||||
Expected: []string{channel3.Id},
|
||||
},
|
||||
{
|
||||
Name: "autocomplete search that won't match anything",
|
||||
Term: "nothing",
|
||||
Expected: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
res, err := s.Channel().AutocompleteInTeam(team.Id, tc.Term, false)
|
||||
require.Nil(t, err)
|
||||
require.Len(t, tc.Expected, len(*res))
|
||||
|
||||
resIds := make([]string, len(*res))
|
||||
for i, channel := range *res {
|
||||
resIds[i] = channel.Id
|
||||
}
|
||||
require.ElementsMatch(t, tc.Expected, resIds)
|
||||
})
|
||||
}
|
||||
}
|
||||
1971
store/searchtest/post_layer.go
Normal file
1971
store/searchtest/post_layer.go
Normal file
File diff suppressed because it is too large
Load Diff
180
store/searchtest/testlib.go
Normal file
180
store/searchtest/testlib.go
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchtest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
"github.com/mattermost/mattermost-server/v5/utils"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
ENGINE_ALL = "all"
|
||||
ENGINE_MYSQL = "mysql"
|
||||
ENGINE_POSTGRES = "postgres"
|
||||
ENGINE_ELASTICSEARCH = "elasticsearch"
|
||||
)
|
||||
|
||||
type SearchTestEngine struct {
|
||||
Driver string
|
||||
BeforeTest func(*testing.T, store.Store)
|
||||
AfterTest func(*testing.T, store.Store)
|
||||
}
|
||||
|
||||
type searchTest struct {
|
||||
Name string
|
||||
Fn func(*testing.T, store.Store)
|
||||
Tags []string
|
||||
}
|
||||
|
||||
func filterTestsByTag(tests []searchTest, tags ...string) []searchTest {
|
||||
filteredTests := []searchTest{}
|
||||
for _, test := range tests {
|
||||
if utils.StringInSlice(ENGINE_ALL, test.Tags) {
|
||||
filteredTests = append(filteredTests, test)
|
||||
continue
|
||||
}
|
||||
for _, tag := range tags {
|
||||
if utils.StringInSlice(tag, test.Tags) {
|
||||
filteredTests = append(filteredTests, test)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredTests
|
||||
}
|
||||
|
||||
func runTestSearch(t *testing.T, s store.Store, testEngine *SearchTestEngine, tests []searchTest) {
|
||||
filteredTests := filterTestsByTag(tests, testEngine.Driver)
|
||||
|
||||
for _, test := range filteredTests {
|
||||
test := test
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping advanced search test")
|
||||
continue
|
||||
}
|
||||
|
||||
if testEngine.BeforeTest != nil {
|
||||
testEngine.BeforeTest(t, s)
|
||||
}
|
||||
t.Run(test.Name, func(t *testing.T) { test.Fn(t, s) })
|
||||
if testEngine.AfterTest != nil {
|
||||
testEngine.AfterTest(t, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeEmail() string {
|
||||
return "success_" + model.NewId() + "@simulator.amazonses.com"
|
||||
}
|
||||
|
||||
func assertUsersMatchInAnyOrder(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.ElementsMatch(t, expectedUsernames, actualUsernames) {
|
||||
assert.ElementsMatch(t, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func createUser(username, nickname, firstName, lastName string) *model.User {
|
||||
user := &model.User{
|
||||
Username: username,
|
||||
Password: username,
|
||||
Nickname: nickname,
|
||||
FirstName: firstName,
|
||||
LastName: lastName,
|
||||
Email: makeEmail(),
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func createPost(userId string, channelId string, message string) *model.Post {
|
||||
post := &model.Post{
|
||||
Message: message,
|
||||
ChannelId: channelId,
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: userId,
|
||||
CreateAt: 1000000,
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
func addUserToTeamsAndChannels(s store.Store, user *model.User, teamIds []string, channelIds []string) error {
|
||||
for _, teamId := range teamIds {
|
||||
_, err := s.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user.Id}, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, channelId := range channelIds {
|
||||
_, err := s.Channel().SaveMember(&model.ChannelMember{ChannelId: channelId, UserId: user.Id, NotifyProps: model.GetDefaultChannelNotifyProps()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPostInSearchResults(t *testing.T, postId string, searchResults []string) {
|
||||
t.Helper()
|
||||
assert.Contains(t, searchResults, postId, "Did not find expected post in search results.")
|
||||
}
|
||||
|
||||
func checkPostNotInSearchResults(t *testing.T, postId string, searchResults []string) {
|
||||
t.Helper()
|
||||
assert.NotContains(t, searchResults, postId, "Found post in search results that should not be there.")
|
||||
}
|
||||
|
||||
func checkMatchesEqual(t *testing.T, expected model.PostSearchMatches, actual map[string][]string) {
|
||||
a := assert.New(t)
|
||||
|
||||
a.Len(actual, len(expected), "Received matches for a different number of posts")
|
||||
|
||||
for postId, expectedMatches := range expected {
|
||||
a.ElementsMatch(expectedMatches, actual[postId], fmt.Sprintf("%v: expected %v, got %v", postId, expectedMatches, actual[postId]))
|
||||
}
|
||||
}
|
||||
|
||||
func createPostWithHashtags(userId string, channelId string, message string, hashtags string) *model.Post {
|
||||
post := &model.Post{
|
||||
Message: message,
|
||||
ChannelId: channelId,
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: userId,
|
||||
CreateAt: 1000000,
|
||||
Hashtags: hashtags,
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
func createPostAtTime(userId string, channelId string, message string, createAt int64) *model.Post {
|
||||
post := &model.Post{
|
||||
Message: message,
|
||||
ChannelId: channelId,
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: userId,
|
||||
CreateAt: createAt,
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
679
store/searchtest/user_layer.go
Normal file
679
store/searchtest/user_layer.go
Normal file
@@ -0,0 +1,679 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchtest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
)
|
||||
|
||||
var searchUserStoreTests = []searchTest{
|
||||
{"Database User Search tests", testSearchDatabaseUserSearch, []string{ENGINE_MYSQL, ENGINE_POSTGRES}},
|
||||
{"Elasticsearch search users in channel", testSearchESSearchUsersInChannel, []string{ENGINE_ELASTICSEARCH}},
|
||||
{"Elasticsearch search users in team", testSearchESSearchUsersInTeam, []string{ENGINE_ELASTICSEARCH}},
|
||||
}
|
||||
|
||||
func TestSearchUserStore(t *testing.T, s store.Store, testEngine *SearchTestEngine) {
|
||||
runTestSearch(t, s, testEngine, searchUserStoreTests)
|
||||
}
|
||||
|
||||
func testSearchDatabaseUserSearch(t *testing.T, s store.Store) {
|
||||
u1 := &model.User{
|
||||
Username: "jimbo1" + model.NewId(),
|
||||
FirstName: "Tim",
|
||||
LastName: "Bill",
|
||||
Nickname: "Rob",
|
||||
Email: "harold" + model.NewId() + "@simulator.amazonses.com",
|
||||
Roles: "system_user system_admin",
|
||||
}
|
||||
_, err := s.User().Save(u1)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(u1.Id)) }()
|
||||
|
||||
u2 := &model.User{
|
||||
Username: "jim-bobby" + model.NewId(),
|
||||
Email: makeEmail(),
|
||||
Roles: "system_user",
|
||||
}
|
||||
_, err = s.User().Save(u2)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(u2.Id)) }()
|
||||
|
||||
u3 := &model.User{
|
||||
Username: "jimbo3" + model.NewId(),
|
||||
Email: makeEmail(),
|
||||
DeleteAt: 1,
|
||||
Roles: "system_admin",
|
||||
}
|
||||
_, err = s.User().Save(u3)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(u3.Id)) }()
|
||||
_, err = s.Bot().Save(&model.Bot{
|
||||
UserId: u3.Id,
|
||||
Username: u3.Username,
|
||||
OwnerId: u1.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
u3.IsBot = true
|
||||
defer func() { require.Nil(t, s.Bot().PermanentDelete(u3.Id)) }()
|
||||
|
||||
u5 := &model.User{
|
||||
Username: "yu" + model.NewId(),
|
||||
FirstName: "En",
|
||||
LastName: "Yu",
|
||||
Nickname: "enyu",
|
||||
Email: makeEmail(),
|
||||
}
|
||||
_, err = s.User().Save(u5)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(u5.Id)) }()
|
||||
|
||||
u6 := &model.User{
|
||||
Username: "underscore" + model.NewId(),
|
||||
FirstName: "Du_",
|
||||
LastName: "_DE",
|
||||
Nickname: "lodash",
|
||||
Email: makeEmail(),
|
||||
}
|
||||
_, err = s.User().Save(u6)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(u6.Id)) }()
|
||||
|
||||
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
|
||||
require.Nil(t, err)
|
||||
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = s.Team().SaveMember(&model.TeamMember{TeamId: team.Id, UserId: u1.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = s.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()})
|
||||
require.Nil(t, err)
|
||||
_, err = s.Team().SaveMember(&model.TeamMember{TeamId: team.Id, UserId: u2.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = s.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: u2.Id, NotifyProps: model.GetDefaultChannelNotifyProps()})
|
||||
require.Nil(t, err)
|
||||
_, err = s.Team().SaveMember(&model.TeamMember{TeamId: team.Id, UserId: u3.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = s.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: u3.Id, NotifyProps: model.GetDefaultChannelNotifyProps()})
|
||||
require.Nil(t, err)
|
||||
_, err = s.Team().SaveMember(&model.TeamMember{TeamId: team.Id, UserId: u5.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = s.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: u5.Id, NotifyProps: model.GetDefaultChannelNotifyProps()})
|
||||
require.Nil(t, err)
|
||||
_, err = s.Team().SaveMember(&model.TeamMember{TeamId: team.Id, UserId: u6.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = s.Channel().SaveMember(&model.ChannelMember{ChannelId: channel.Id, UserId: u6.Id, NotifyProps: model.GetDefaultChannelNotifyProps()})
|
||||
require.Nil(t, err)
|
||||
|
||||
// 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",
|
||||
team.Id,
|
||||
"jimb",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search en",
|
||||
team.Id,
|
||||
"en",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u5},
|
||||
},
|
||||
{
|
||||
"search email",
|
||||
team.Id,
|
||||
u1.Email,
|
||||
&model.UserSearchOptions{
|
||||
AllowEmails: true,
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search maps * to space",
|
||||
team.Id,
|
||||
"jimb*",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"should not return spurious matches",
|
||||
team.Id,
|
||||
"harol",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{},
|
||||
},
|
||||
{
|
||||
"% should be escaped",
|
||||
team.Id,
|
||||
"h%",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{},
|
||||
},
|
||||
{
|
||||
"_ should be escaped",
|
||||
team.Id,
|
||||
"h_",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{},
|
||||
},
|
||||
{
|
||||
"_ should be escaped (2)",
|
||||
team.Id,
|
||||
"Du_",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u6},
|
||||
},
|
||||
{
|
||||
"_ should be escaped (2)",
|
||||
team.Id,
|
||||
"_dE",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u6},
|
||||
},
|
||||
{
|
||||
"search jimb, allowing inactive",
|
||||
team.Id,
|
||||
"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",
|
||||
team.Id,
|
||||
"harol",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
AllowEmails: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search Tim, search all fields",
|
||||
team.Id,
|
||||
"Tim",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
AllowEmails: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search Tim, don't search full names",
|
||||
team.Id,
|
||||
"Tim",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: false,
|
||||
AllowEmails: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{},
|
||||
},
|
||||
{
|
||||
"search Bill, search all fields",
|
||||
team.Id,
|
||||
"Bill",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
AllowEmails: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search Rob, search all fields",
|
||||
team.Id,
|
||||
"Rob",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
AllowEmails: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"leading @ should be ignored",
|
||||
team.Id,
|
||||
"@jimb",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search jim-bobby with system_user roles",
|
||||
team.Id,
|
||||
"jim-bobby",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
Role: "system_user",
|
||||
},
|
||||
[]*model.User{u2},
|
||||
},
|
||||
{
|
||||
"search jim with system_admin roles",
|
||||
team.Id,
|
||||
"jim",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
Role: "system_admin",
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search ji with system_user roles",
|
||||
team.Id,
|
||||
"ji",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
Role: "system_user",
|
||||
},
|
||||
[]*model.User{u1, u2},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
users, err := s.User().Search(testCase.TeamId, testCase.Term, testCase.Options)
|
||||
require.Nil(t, err)
|
||||
assertUsersMatchInAnyOrder(t, testCase.Expected, users)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("search empty string", func(t *testing.T) {
|
||||
searchOptions := &model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
}
|
||||
|
||||
users, err := s.User().Search(team.Id, "", searchOptions)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, users, 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,
|
||||
}
|
||||
|
||||
users, err := s.User().Search(team.Id, "", searchOptions)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, users, 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))
|
||||
})
|
||||
}
|
||||
|
||||
func testSearchESSearchUsersInChannel(t *testing.T, s store.Store) {
|
||||
// Create and index some users
|
||||
// Channels for team 1
|
||||
team1, err := s.Team().Save(&model.Team{Name: "team1", DisplayName: "team1", Type: model.TEAM_OPEN})
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, s.Team().PermanentDelete(team1.Id)) }()
|
||||
channel1, err := s.Channel().Save(&model.Channel{TeamId: team1.Id, Name: "channel", DisplayName: "Test One", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
channel2, err := s.Channel().Save(&model.Channel{TeamId: team1.Id, Name: "channel-second", DisplayName: "Test Two", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Channels for team 2
|
||||
team2, err := s.Team().Save(&model.Team{Name: "team2", DisplayName: "team2", Type: model.TEAM_OPEN})
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, s.Team().PermanentDelete(team2.Id)) }()
|
||||
channel3, err := s.Channel().Save(&model.Channel{TeamId: team2.Id, Name: "channel_third", DisplayName: "Test Three", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Users in team 1
|
||||
user1, err := s.User().Save(createUser("test.one", "userone", "User", "One"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user1, []string{team1.Id}, []string{channel1.Id, channel2.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user1.Id)) }()
|
||||
|
||||
user2, err := s.User().Save(createUser("test.two", "usertwo", "User", "Special Two"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user2, []string{team1.Id}, []string{channel1.Id, channel2.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user2.Id)) }()
|
||||
|
||||
user3, err := s.User().Save(createUser("test.three", "userthree", "User", "Special Three"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user3, []string{team1.Id}, []string{channel2.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user3.Id)) }()
|
||||
|
||||
// Users in team 2
|
||||
user4, err := s.User().Save(createUser("test.four", "userfour", "User", "Four"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user4, []string{team2.Id}, []string{channel3.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user4.Id)) }()
|
||||
|
||||
user5, err := s.User().Save(createUser("test.five_split", "userfive", "User", "Five"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user5, []string{team2.Id}, []string{channel3.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user5.Id)) }()
|
||||
|
||||
// Given the default search options
|
||||
options := &model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: 100,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Team string
|
||||
Channel string
|
||||
ViewRestrictions *model.ViewUsersRestrictions
|
||||
Term string
|
||||
InChannel []string
|
||||
OutOfChannel []string
|
||||
}{
|
||||
{
|
||||
Name: "All users in channel1",
|
||||
Team: team1.Id,
|
||||
Channel: channel1.Id,
|
||||
ViewRestrictions: nil,
|
||||
Term: "",
|
||||
InChannel: []string{user1.Id, user2.Id},
|
||||
OutOfChannel: []string{user3.Id},
|
||||
},
|
||||
{
|
||||
Name: "All users in channel1 with channel restrictions",
|
||||
Team: team1.Id,
|
||||
Channel: channel1.Id,
|
||||
ViewRestrictions: &model.ViewUsersRestrictions{Channels: []string{channel1.Id}},
|
||||
Term: "",
|
||||
InChannel: []string{user1.Id, user2.Id},
|
||||
OutOfChannel: []string{},
|
||||
},
|
||||
{
|
||||
Name: "All users in channel1 with channel all channels restricted",
|
||||
Team: team1.Id,
|
||||
Channel: channel1.Id,
|
||||
ViewRestrictions: &model.ViewUsersRestrictions{Channels: []string{}},
|
||||
Term: "",
|
||||
InChannel: []string{},
|
||||
OutOfChannel: []string{},
|
||||
},
|
||||
{
|
||||
Name: "All users in channel2",
|
||||
Team: team1.Id,
|
||||
Channel: channel2.Id,
|
||||
ViewRestrictions: nil,
|
||||
Term: "",
|
||||
InChannel: []string{user1.Id, user2.Id, user3.Id},
|
||||
OutOfChannel: []string{},
|
||||
},
|
||||
{
|
||||
Name: "All users in channel3",
|
||||
Team: team2.Id,
|
||||
Channel: channel3.Id,
|
||||
ViewRestrictions: nil,
|
||||
Term: "",
|
||||
InChannel: []string{user4.Id, user5.Id},
|
||||
OutOfChannel: []string{},
|
||||
},
|
||||
{
|
||||
Name: "All users in channel1 with term \"spe\"",
|
||||
Team: team1.Id,
|
||||
Channel: channel1.Id,
|
||||
ViewRestrictions: nil,
|
||||
Term: "spe",
|
||||
InChannel: []string{user2.Id},
|
||||
OutOfChannel: []string{user3.Id},
|
||||
},
|
||||
{
|
||||
Name: "All users in channel1 with term \"spe\" with channel restrictions",
|
||||
Team: team1.Id,
|
||||
Channel: channel1.Id,
|
||||
ViewRestrictions: &model.ViewUsersRestrictions{Channels: []string{channel1.Id}},
|
||||
Term: "spe",
|
||||
InChannel: []string{user2.Id},
|
||||
OutOfChannel: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
options.ViewRestrictions = tc.ViewRestrictions
|
||||
res, err := s.User().AutocompleteUsersInChannel(tc.Team, tc.Channel, tc.Term, options)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Len(t, res.InChannel, len(tc.InChannel))
|
||||
inChannelIds := make([]string, len(res.InChannel))
|
||||
for i, user := range res.InChannel {
|
||||
inChannelIds[i] = user.Id
|
||||
}
|
||||
assert.ElementsMatch(t, tc.InChannel, inChannelIds)
|
||||
|
||||
require.Len(t, res.OutOfChannel, len(tc.OutOfChannel))
|
||||
outOfChannelIds := make([]string, len(res.OutOfChannel))
|
||||
for i, user := range res.OutOfChannel {
|
||||
outOfChannelIds[i] = user.Id
|
||||
}
|
||||
assert.ElementsMatch(t, tc.OutOfChannel, outOfChannelIds)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testSearchESSearchUsersInTeam(t *testing.T, s store.Store) {
|
||||
// Create and index some users
|
||||
// Channels for team 1
|
||||
team1, err := s.Team().Save(&model.Team{Name: "team1", DisplayName: "team1", Type: model.TEAM_OPEN})
|
||||
require.Nil(t, err)
|
||||
defer require.Nil(t, s.Team().PermanentDelete(team1.Id))
|
||||
channel1, err := s.Channel().Save(&model.Channel{TeamId: team1.Id, Name: "channel", DisplayName: "Test One", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
channel2, err := s.Channel().Save(&model.Channel{TeamId: team1.Id, Name: "channel-second", DisplayName: "Test Two", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Channels for team 2
|
||||
team2, err := s.Team().Save(&model.Team{Name: "team2", DisplayName: "team2", Type: model.TEAM_OPEN})
|
||||
require.Nil(t, err)
|
||||
defer require.Nil(t, s.Team().PermanentDelete(team2.Id))
|
||||
channel3, err := s.Channel().Save(&model.Channel{TeamId: team2.Id, Name: "channel_third", DisplayName: "Test Three", Type: model.CHANNEL_OPEN}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Users in team 1
|
||||
user1, err := s.User().Save(createUser("test.one.split", "userone", "User", "One"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user1, []string{team1.Id}, []string{channel1.Id, channel2.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user1.Id)) }()
|
||||
|
||||
user2, err := s.User().Save(createUser("test.two", "usertwo", "User", "Special Two"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user2, []string{team1.Id}, []string{channel1.Id, channel2.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user2.Id)) }()
|
||||
|
||||
user3, err := s.User().Save(createUser("test.three", "userthree", "User", "Special Three"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user3, []string{team1.Id}, []string{channel2.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user3.Id)) }()
|
||||
|
||||
// Users in team 2
|
||||
user4, err := s.User().Save(createUser("test.four-slash", "userfour", "User", "Four"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user4, []string{team2.Id}, []string{channel3.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user4.Id)) }()
|
||||
|
||||
user5, err := s.User().Save(createUser("test.five.split", "userfive", "User", "Five"))
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, addUserToTeamsAndChannels(s, user5, []string{team2.Id}, []string{channel3.Id}))
|
||||
defer func() { require.Nil(t, s.User().PermanentDelete(user5.Id)) }()
|
||||
|
||||
// Given the default search options
|
||||
options := &model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: 100,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Team string
|
||||
ViewRestrictions *model.ViewUsersRestrictions
|
||||
Term string
|
||||
Result []string
|
||||
}{
|
||||
{
|
||||
Name: "All users in team1",
|
||||
Team: team1.Id,
|
||||
ViewRestrictions: nil,
|
||||
Term: "",
|
||||
Result: []string{user1.Id, user2.Id, user3.Id},
|
||||
},
|
||||
{
|
||||
Name: "All users in team1 with term \"spe\"",
|
||||
Team: team1.Id,
|
||||
ViewRestrictions: nil,
|
||||
Term: "spe",
|
||||
Result: []string{user2.Id, user3.Id},
|
||||
},
|
||||
{
|
||||
Name: "All users in team1 with term \"spe\" and channel restrictions",
|
||||
Team: team1.Id,
|
||||
ViewRestrictions: &model.ViewUsersRestrictions{Channels: []string{channel1.Id}},
|
||||
Term: "spe",
|
||||
Result: []string{user2.Id},
|
||||
},
|
||||
{
|
||||
Name: "All users in team1 with term \"spe\" and all channels restricted",
|
||||
Team: team1.Id,
|
||||
ViewRestrictions: &model.ViewUsersRestrictions{Channels: []string{}},
|
||||
Term: "spe",
|
||||
Result: []string{},
|
||||
},
|
||||
{
|
||||
Name: "All users in team2",
|
||||
Team: team2.Id,
|
||||
ViewRestrictions: nil,
|
||||
Term: "",
|
||||
Result: []string{user4.Id, user5.Id},
|
||||
},
|
||||
{
|
||||
Name: "All users in team2 with term FiV",
|
||||
Team: team2.Id,
|
||||
ViewRestrictions: nil,
|
||||
Term: "FiV",
|
||||
Result: []string{user5.Id},
|
||||
},
|
||||
{
|
||||
Name: "All users in team2 by split part of the username with a dot",
|
||||
Team: team2.Id,
|
||||
ViewRestrictions: &model.ViewUsersRestrictions{Channels: []string{channel3.Id}},
|
||||
Term: "split",
|
||||
Result: []string{user5.Id},
|
||||
},
|
||||
{
|
||||
Name: "All users in team2 by split part of the username with a slash",
|
||||
Team: team2.Id,
|
||||
ViewRestrictions: &model.ViewUsersRestrictions{Channels: []string{channel3.Id}},
|
||||
Term: "slash",
|
||||
Result: []string{user4.Id},
|
||||
},
|
||||
{
|
||||
Name: "All users in team2 by split part of the username with a -slash",
|
||||
Team: team2.Id,
|
||||
ViewRestrictions: &model.ViewUsersRestrictions{Channels: []string{channel3.Id}},
|
||||
Term: "-slash",
|
||||
Result: []string{user4.Id},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
options.ViewRestrictions = tc.ViewRestrictions
|
||||
res, err := s.User().Search(tc.Team, tc.Term, options)
|
||||
require.Nil(t, err)
|
||||
require.Len(t, tc.Result, len(res))
|
||||
|
||||
userIds := make([]string, len(res))
|
||||
for i, user := range res {
|
||||
userIds[i] = user.Id
|
||||
}
|
||||
assert.ElementsMatch(t, userIds, tc.Result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
"github.com/mattermost/mattermost-server/v5/store/searchtest"
|
||||
"github.com/mattermost/mattermost-server/v5/store/storetest"
|
||||
)
|
||||
|
||||
@@ -19,6 +20,10 @@ func TestChannelStore(t *testing.T) {
|
||||
StoreTestWithSqlSupplier(t, storetest.TestChannelStore)
|
||||
}
|
||||
|
||||
func TestSearchChannelStore(t *testing.T) {
|
||||
StoreTestWithSearchTestEngine(t, searchtest.TestSearchChannelStore)
|
||||
}
|
||||
|
||||
func TestChannelSearchQuerySQLInjection(t *testing.T) {
|
||||
for _, st := range storeTypes {
|
||||
t.Run(st.Name, func(t *testing.T) {
|
||||
|
||||
@@ -6,9 +6,14 @@ package sqlstore
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/store/searchtest"
|
||||
"github.com/mattermost/mattermost-server/v5/store/storetest"
|
||||
)
|
||||
|
||||
func TestPostStore(t *testing.T) {
|
||||
StoreTestWithSqlSupplier(t, storetest.TestPostStore)
|
||||
}
|
||||
|
||||
func TestSearchPostStore(t *testing.T) {
|
||||
StoreTestWithSearchTestEngine(t, searchtest.TestSearchPostStore)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
"github.com/mattermost/mattermost-server/v5/store/searchtest"
|
||||
"github.com/mattermost/mattermost-server/v5/store/storetest"
|
||||
)
|
||||
|
||||
@@ -39,6 +40,31 @@ func StoreTest(t *testing.T, f func(*testing.T, store.Store)) {
|
||||
}
|
||||
}
|
||||
|
||||
func StoreTestWithSearchTestEngine(t *testing.T, f func(*testing.T, store.Store, *searchtest.SearchTestEngine)) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
tearDownStores()
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, st := range storeTypes {
|
||||
st := st
|
||||
searchTestEngine := &searchtest.SearchTestEngine{
|
||||
Driver: *st.SqlSettings.DriverName,
|
||||
AfterTest: func(t *testing.T, s store.Store) {
|
||||
t.Helper()
|
||||
|
||||
st.SqlSupplier.GetMaster().Exec("TRUNCATE Teams")
|
||||
st.SqlSupplier.GetMaster().Exec("TRUNCATE Channels")
|
||||
st.SqlSupplier.GetMaster().Exec("TRUNCATE Users")
|
||||
},
|
||||
}
|
||||
|
||||
t.Run(st.Name, func(t *testing.T) { f(t, st.Store, searchTestEngine) })
|
||||
}
|
||||
}
|
||||
|
||||
func StoreTestWithSqlSupplier(t *testing.T, f func(*testing.T, store.Store, storetest.SqlSupplier)) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
|
||||
@@ -6,9 +6,14 @@ package sqlstore
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/store/searchtest"
|
||||
"github.com/mattermost/mattermost-server/v5/store/storetest"
|
||||
)
|
||||
|
||||
func TestUserStore(t *testing.T) {
|
||||
StoreTestWithSqlSupplier(t, storetest.TestUserStore)
|
||||
}
|
||||
|
||||
func TestSearchUserStore(t *testing.T) {
|
||||
StoreTestWithSearchTestEngine(t, searchtest.TestSearchUserStore)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
t.Run("SearchInTeam", func(t *testing.T) { testChannelStoreSearchInTeam(t, ss) })
|
||||
t.Run("SearchForUserInTeam", func(t *testing.T) { testChannelStoreSearchForUserInTeam(t, ss) })
|
||||
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) })
|
||||
@@ -89,7 +88,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
t.Run("MaterializedPublicChannels", func(t *testing.T) { testMaterializedPublicChannels(t, ss, s) })
|
||||
t.Run("GetAllChannelsForExportAfter", func(t *testing.T) { testChannelStoreGetAllChannelsForExportAfter(t, ss) })
|
||||
t.Run("GetChannelMembersForExport", func(t *testing.T) { testChannelStoreGetChannelMembersForExport(t, ss) })
|
||||
t.Run("RemoveAllDeactivatedMembers", func(t *testing.T) { testChannelStoreRemoveAllDeactivatedMembers(t, ss) })
|
||||
t.Run("RemoveAllDeactivatedMembers", func(t *testing.T) { testChannelStoreRemoveAllDeactivatedMembers(t, ss, s) })
|
||||
t.Run("ExportAllDirectChannels", func(t *testing.T) { testChannelStoreExportAllDirectChannels(t, ss, s) })
|
||||
t.Run("ExportAllDirectChannelsExcludePrivateAndPublic", func(t *testing.T) { testChannelStoreExportAllDirectChannelsExcludePrivateAndPublic(t, ss, s) })
|
||||
t.Run("ExportAllDirectChannelsDeletedChannel", func(t *testing.T) { testChannelStoreExportAllDirectChannelsDeletedChannel(t, ss, s) })
|
||||
@@ -5074,139 +5073,6 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) {
|
||||
}
|
||||
}
|
||||
|
||||
func testChannelStoreAutocompleteInTeamForSearch(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
u1 := &model.User{}
|
||||
u1.Email = MakeEmail()
|
||||
u1.Username = "user1" + model.NewId()
|
||||
u1.Nickname = model.NewId()
|
||||
_, err := ss.User().Save(u1)
|
||||
require.Nil(t, err)
|
||||
|
||||
u2 := &model.User{}
|
||||
u2.Email = MakeEmail()
|
||||
u2.Username = "user2" + model.NewId()
|
||||
u2.Nickname = model.NewId()
|
||||
_, err = ss.User().Save(u2)
|
||||
require.Nil(t, err)
|
||||
|
||||
u3 := &model.User{}
|
||||
u3.Email = MakeEmail()
|
||||
u3.Username = "user3" + model.NewId()
|
||||
u3.Nickname = model.NewId()
|
||||
_, err = ss.User().Save(u3)
|
||||
require.Nil(t, err)
|
||||
|
||||
u4 := &model.User{}
|
||||
u4.Email = MakeEmail()
|
||||
u4.Username = "user4" + model.NewId()
|
||||
u4.Nickname = model.NewId()
|
||||
_, err = ss.User().Save(u4)
|
||||
require.Nil(t, err)
|
||||
|
||||
o1 := model.Channel{}
|
||||
o1.TeamId = model.NewId()
|
||||
o1.DisplayName = "ChannelA"
|
||||
o1.Name = "zz" + model.NewId() + "b"
|
||||
o1.Type = model.CHANNEL_OPEN
|
||||
_, err = ss.Channel().Save(&o1, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = o1.Id
|
||||
m1.UserId = u1.Id
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = ss.Channel().SaveMember(&m1)
|
||||
require.Nil(t, err)
|
||||
|
||||
o2 := model.Channel{}
|
||||
o2.TeamId = model.NewId()
|
||||
o2.DisplayName = "Channel2"
|
||||
o2.Name = "zz" + model.NewId() + "b"
|
||||
o2.Type = model.CHANNEL_OPEN
|
||||
_, err = ss.Channel().Save(&o2, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
m2 := model.ChannelMember{}
|
||||
m2.ChannelId = o2.Id
|
||||
m2.UserId = m1.UserId
|
||||
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = ss.Channel().SaveMember(&m2)
|
||||
require.Nil(t, err)
|
||||
|
||||
o3 := model.Channel{}
|
||||
o3.TeamId = o1.TeamId
|
||||
o3.DisplayName = "ChannelA"
|
||||
o3.Name = "zz" + model.NewId() + "b"
|
||||
o3.Type = model.CHANNEL_OPEN
|
||||
_, err = ss.Channel().Save(&o3, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
m3 := model.ChannelMember{}
|
||||
m3.ChannelId = o3.Id
|
||||
m3.UserId = m1.UserId
|
||||
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = ss.Channel().SaveMember(&m3)
|
||||
require.Nil(t, err)
|
||||
|
||||
err = ss.Channel().SetDeleteAt(o3.Id, 100, 100)
|
||||
require.Nil(t, err, "channel should have been deleted")
|
||||
|
||||
o4 := model.Channel{}
|
||||
o4.TeamId = o1.TeamId
|
||||
o4.DisplayName = "ChannelA"
|
||||
o4.Name = "zz" + model.NewId() + "b"
|
||||
o4.Type = model.CHANNEL_PRIVATE
|
||||
_, err = ss.Channel().Save(&o4, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
m4 := model.ChannelMember{}
|
||||
m4.ChannelId = o4.Id
|
||||
m4.UserId = m1.UserId
|
||||
m4.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = ss.Channel().SaveMember(&m4)
|
||||
require.Nil(t, err)
|
||||
|
||||
o5 := model.Channel{}
|
||||
o5.TeamId = o1.TeamId
|
||||
o5.DisplayName = "ChannelC"
|
||||
o5.Name = "zz" + model.NewId() + "b"
|
||||
o5.Type = model.CHANNEL_PRIVATE
|
||||
_, err = ss.Channel().Save(&o5, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Channel().CreateDirectChannel(u1, u2)
|
||||
require.Nil(t, err)
|
||||
_, err = ss.Channel().CreateDirectChannel(u2, u3)
|
||||
require.Nil(t, err)
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
term string
|
||||
includeDeleted bool
|
||||
expectedMatches int
|
||||
}{
|
||||
{"Empty search (list all)", "", false, 4},
|
||||
{"Narrow search", "ChannelA", false, 2},
|
||||
{"Wide search", "Cha", false, 3},
|
||||
{"Direct messages", "user", false, 1},
|
||||
{"Wide search with archived channels", "Cha", true, 4},
|
||||
{"Narrow with archived channels", "ChannelA", true, 3},
|
||||
{"Direct messages with archived channels", "user", true, 1},
|
||||
{"Search without results", "blarg", true, 0},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
channels, err := ss.Channel().AutocompleteInTeamForSearch(o1.TeamId, m1.UserId, "ChannelA", false)
|
||||
require.Nil(t, err)
|
||||
require.Len(t, *channels, 2)
|
||||
})
|
||||
}
|
||||
|
||||
// Manually truncate Channels table until testlib can handle cleanups
|
||||
s.GetMaster().Exec("TRUNCATE Channels")
|
||||
}
|
||||
|
||||
func testChannelStoreGetMembersByIds(t *testing.T, ss store.Store) {
|
||||
o1 := model.Channel{}
|
||||
o1.TeamId = model.NewId()
|
||||
@@ -6084,7 +5950,7 @@ func testChannelStoreGetChannelMembersForExport(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, u1.Id, cmfe1.UserId)
|
||||
}
|
||||
|
||||
func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, ss store.Store) {
|
||||
func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
// Set up all the objects needed in the store.
|
||||
t1 := model.Team{}
|
||||
t1.DisplayName = "Name"
|
||||
@@ -6162,6 +6028,9 @@ func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, ss store.Store) {
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, *d2, 1)
|
||||
assert.Equal(t, u3.Id, (*d2)[0].UserId)
|
||||
|
||||
// Manually truncate Channels table until testlib can handle cleanups
|
||||
s.GetMaster().Exec("TRUNCATE Channels")
|
||||
}
|
||||
|
||||
func testChannelStoreExportAllDirectChannels(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
|
||||
@@ -34,7 +34,6 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
t.Run("GetPostsBeforeAfter", func(t *testing.T) { testPostStoreGetPostsBeforeAfter(t, ss) })
|
||||
t.Run("GetPostsSince", func(t *testing.T) { testPostStoreGetPostsSince(t, ss) })
|
||||
t.Run("GetPostBeforeAfter", func(t *testing.T) { testPostStoreGetPostBeforeAfter(t, ss) })
|
||||
t.Run("Search", func(t *testing.T) { testPostStoreSearch(t, ss) })
|
||||
t.Run("UserCountsWithPostsByDay", func(t *testing.T) { testUserCountsWithPostsByDay(t, ss) })
|
||||
t.Run("PostCountsByDay", func(t *testing.T) { testPostCountsByDay(t, ss) })
|
||||
t.Run("GetFlaggedPostsForTeam", func(t *testing.T) { testPostStoreGetFlaggedPostsForTeam(t, ss, s) })
|
||||
@@ -1319,347 +1318,6 @@ func testPostStoreGetPostBeforeAfter(t *testing.T, ss store.Store) {
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func testPostStoreSearch(t *testing.T, ss store.Store) {
|
||||
teamId := model.NewId()
|
||||
userId := model.NewId()
|
||||
|
||||
u1 := &model.User{}
|
||||
u1.Username = "usera1"
|
||||
u1.Email = MakeEmail()
|
||||
u1, err := ss.User().Save(u1)
|
||||
require.Nil(t, err)
|
||||
|
||||
t1 := &model.TeamMember{}
|
||||
t1.TeamId = teamId
|
||||
t1.UserId = u1.Id
|
||||
_, err = ss.Team().SaveMember(t1, 1000)
|
||||
require.Nil(t, err)
|
||||
|
||||
u2 := &model.User{}
|
||||
u2.Username = "userb2"
|
||||
u2.Email = MakeEmail()
|
||||
u2, err = ss.User().Save(u2)
|
||||
require.Nil(t, err)
|
||||
|
||||
t2 := &model.TeamMember{}
|
||||
t2.TeamId = teamId
|
||||
t2.UserId = u2.Id
|
||||
_, err = ss.Team().SaveMember(t2, 1000)
|
||||
require.Nil(t, err)
|
||||
|
||||
u3 := &model.User{}
|
||||
u3.Username = "userc3"
|
||||
u3.Email = MakeEmail()
|
||||
u3, err = ss.User().Save(u3)
|
||||
require.Nil(t, err)
|
||||
|
||||
t3 := &model.TeamMember{}
|
||||
t3.TeamId = teamId
|
||||
t3.UserId = u3.Id
|
||||
_, err = ss.Team().SaveMember(t3, 1000)
|
||||
require.Nil(t, err)
|
||||
|
||||
c1 := &model.Channel{}
|
||||
c1.TeamId = teamId
|
||||
c1.DisplayName = "Channel1"
|
||||
c1.Name = "channel-x"
|
||||
c1.Type = model.CHANNEL_OPEN
|
||||
c1, _ = ss.Channel().Save(c1, -1)
|
||||
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = c1.Id
|
||||
m1.UserId = userId
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = ss.Channel().SaveMember(&m1)
|
||||
require.Nil(t, err)
|
||||
|
||||
c2 := &model.Channel{}
|
||||
c2.TeamId = teamId
|
||||
c2.DisplayName = "Channel2"
|
||||
c2.Name = "channel-y"
|
||||
c2.Type = model.CHANNEL_OPEN
|
||||
c2, _ = ss.Channel().Save(c2, -1)
|
||||
|
||||
c3 := &model.Channel{}
|
||||
c3.TeamId = teamId
|
||||
c3.DisplayName = "Channel3"
|
||||
c3.Name = "channel-z"
|
||||
c3.Type = model.CHANNEL_OPEN
|
||||
c3, _ = ss.Channel().Save(c3, -1)
|
||||
|
||||
ss.Channel().Delete(c3.Id, model.GetMillis())
|
||||
|
||||
m3 := model.ChannelMember{}
|
||||
m3.ChannelId = c3.Id
|
||||
m3.UserId = userId
|
||||
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
_, err = ss.Channel().SaveMember(&m3)
|
||||
require.Nil(t, err)
|
||||
|
||||
o1 := &model.Post{}
|
||||
o1.ChannelId = c1.Id
|
||||
o1.UserId = u1.Id
|
||||
o1.Message = "corey mattermost new york United States"
|
||||
o1, err = ss.Post().Save(o1)
|
||||
require.Nil(t, err)
|
||||
|
||||
o1a := &model.Post{}
|
||||
o1a.ChannelId = c1.Id
|
||||
o1a.UserId = model.NewId()
|
||||
o1a.Message = "corey mattermost new york United States"
|
||||
o1a.Type = model.POST_JOIN_CHANNEL
|
||||
_, err = ss.Post().Save(o1a)
|
||||
require.Nil(t, err)
|
||||
|
||||
o2 := &model.Post{}
|
||||
o2.ChannelId = c1.Id
|
||||
o2.UserId = u2.Id
|
||||
o2.Message = "New Jersey United States is where John is from"
|
||||
o2, err = ss.Post().Save(o2)
|
||||
require.Nil(t, err)
|
||||
|
||||
o3 := &model.Post{}
|
||||
o3.ChannelId = c2.Id
|
||||
o3.UserId = model.NewId()
|
||||
o3.Message = "New Jersey United States is where John is from corey new york"
|
||||
_, err = ss.Post().Save(o3)
|
||||
require.Nil(t, err)
|
||||
|
||||
o4 := &model.Post{}
|
||||
o4.ChannelId = c1.Id
|
||||
o4.UserId = model.NewId()
|
||||
o4.Hashtags = "#hashtag #tagme"
|
||||
o4.Message = "(message)blargh"
|
||||
o4, err = ss.Post().Save(o4)
|
||||
require.Nil(t, err)
|
||||
|
||||
o5 := &model.Post{}
|
||||
o5.ChannelId = c1.Id
|
||||
o5.UserId = model.NewId()
|
||||
o5.Hashtags = "#secret #howdy #tagme"
|
||||
o5, err = ss.Post().Save(o5)
|
||||
require.Nil(t, err)
|
||||
|
||||
o6 := &model.Post{}
|
||||
o6.ChannelId = c3.Id
|
||||
o6.UserId = model.NewId()
|
||||
o6.Hashtags = "#hashtag"
|
||||
o6, err = ss.Post().Save(o6)
|
||||
require.Nil(t, err)
|
||||
|
||||
o7 := &model.Post{}
|
||||
o7.ChannelId = c3.Id
|
||||
o7.UserId = u3.Id
|
||||
o7.Message = "New Jersey United States is where John is from corey new york"
|
||||
o7, err = ss.Post().Save(o7)
|
||||
require.Nil(t, err)
|
||||
|
||||
o8 := &model.Post{}
|
||||
o8.ChannelId = c3.Id
|
||||
o8.UserId = model.NewId()
|
||||
o8.Message = "Deleted"
|
||||
o8, err = ss.Post().Save(o8)
|
||||
require.Nil(t, err)
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
searchParams *model.SearchParams
|
||||
expectedResultsCount int
|
||||
expectedMessageResultIds []string
|
||||
}{
|
||||
{
|
||||
"normal-search-1",
|
||||
&model.SearchParams{Terms: "corey"},
|
||||
1,
|
||||
[]string{o1.Id},
|
||||
},
|
||||
{
|
||||
"normal-search-2",
|
||||
&model.SearchParams{Terms: "new"},
|
||||
2,
|
||||
[]string{o1.Id, o2.Id},
|
||||
},
|
||||
{
|
||||
"normal-search-3",
|
||||
&model.SearchParams{Terms: "john"},
|
||||
1,
|
||||
[]string{o2.Id},
|
||||
},
|
||||
{
|
||||
"wildcard-search",
|
||||
&model.SearchParams{Terms: "matter*"},
|
||||
1,
|
||||
[]string{o1.Id},
|
||||
},
|
||||
{
|
||||
"hashtag-search",
|
||||
&model.SearchParams{Terms: "#hashtag", IsHashtag: true},
|
||||
1,
|
||||
[]string{o4.Id},
|
||||
},
|
||||
{
|
||||
"hashtag-search-2",
|
||||
&model.SearchParams{Terms: "#secret", IsHashtag: true},
|
||||
1,
|
||||
[]string{o5.Id},
|
||||
},
|
||||
{
|
||||
"hashtag-search-with-exclusion",
|
||||
&model.SearchParams{Terms: "#tagme", ExcludedTerms: "#hashtag", IsHashtag: true},
|
||||
1,
|
||||
[]string{o5.Id},
|
||||
},
|
||||
{
|
||||
"no-match-mention",
|
||||
&model.SearchParams{Terms: "@thisshouldmatchnothing", IsHashtag: true},
|
||||
0,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"no-results-search",
|
||||
&model.SearchParams{Terms: "mattermost jersey"},
|
||||
0,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"exclude-search",
|
||||
&model.SearchParams{Terms: "united", ExcludedTerms: "jersey"},
|
||||
1,
|
||||
[]string{o1.Id},
|
||||
},
|
||||
{
|
||||
"multiple-words-search",
|
||||
&model.SearchParams{Terms: "corey new york"},
|
||||
1,
|
||||
[]string{o1.Id},
|
||||
},
|
||||
{
|
||||
"multiple-words-with-exclusion-search",
|
||||
&model.SearchParams{Terms: "united states", ExcludedTerms: "jersey"},
|
||||
1,
|
||||
[]string{o1.Id},
|
||||
},
|
||||
{
|
||||
"multiple-excluded-words-search",
|
||||
&model.SearchParams{Terms: "united", ExcludedTerms: "corey john"},
|
||||
0,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"multiple-wildcard-search",
|
||||
&model.SearchParams{Terms: "matter* jer*"},
|
||||
0,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"multiple-wildcard-with-exclusion-search",
|
||||
&model.SearchParams{Terms: "unite* state*", ExcludedTerms: "jers*"},
|
||||
1,
|
||||
[]string{o1.Id},
|
||||
},
|
||||
{
|
||||
"multiple-wildcard-excluded-words-search",
|
||||
&model.SearchParams{Terms: "united states", ExcludedTerms: "jers* yor*"},
|
||||
0,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"search-with-work-next-to-a-symbol",
|
||||
&model.SearchParams{Terms: "message blargh"},
|
||||
1,
|
||||
[]string{o4.Id},
|
||||
},
|
||||
{
|
||||
"search-with-or",
|
||||
&model.SearchParams{Terms: "Jersey corey", OrTerms: true},
|
||||
2,
|
||||
[]string{o1.Id, o2.Id},
|
||||
},
|
||||
{
|
||||
"exclude-search-with-or",
|
||||
&model.SearchParams{Terms: "york jersey", ExcludedTerms: "john", OrTerms: true},
|
||||
1,
|
||||
[]string{o1.Id},
|
||||
},
|
||||
{
|
||||
"search-with-from-user",
|
||||
&model.SearchParams{Terms: "united states", FromUsers: []string{"usera1"}, IncludeDeletedChannels: true},
|
||||
1,
|
||||
[]string{o1.Id},
|
||||
},
|
||||
{
|
||||
"search-with-multiple-from-user",
|
||||
&model.SearchParams{Terms: "united states", FromUsers: []string{"usera1", "userc3"}, IncludeDeletedChannels: true},
|
||||
2,
|
||||
[]string{o1.Id, o7.Id},
|
||||
},
|
||||
{
|
||||
"search-with-excluded-user",
|
||||
&model.SearchParams{Terms: "united states", ExcludedUsers: []string{"usera1"}, IncludeDeletedChannels: true},
|
||||
2,
|
||||
[]string{o2.Id, o7.Id},
|
||||
},
|
||||
{
|
||||
"search-with-multiple-excluded-user",
|
||||
&model.SearchParams{Terms: "united states", ExcludedUsers: []string{"usera1", "userb2"}, IncludeDeletedChannels: true},
|
||||
1,
|
||||
[]string{o7.Id},
|
||||
},
|
||||
{
|
||||
"search-with-deleted-and-channel-filter",
|
||||
&model.SearchParams{Terms: "Jersey corey", InChannels: []string{"channel-x"}, IncludeDeletedChannels: true, OrTerms: true},
|
||||
2,
|
||||
[]string{o1.Id, o2.Id},
|
||||
},
|
||||
{
|
||||
"search-with-deleted-and-multiple-channel-filter",
|
||||
&model.SearchParams{Terms: "Jersey corey", InChannels: []string{"channel-x", "channel-z"}, IncludeDeletedChannels: true, OrTerms: true},
|
||||
3,
|
||||
[]string{o1.Id, o2.Id, o7.Id},
|
||||
},
|
||||
{
|
||||
"search-with-deleted-and-excluded-channel-filter",
|
||||
&model.SearchParams{Terms: "Jersey corey", ExcludedChannels: []string{"channel-x"}, IncludeDeletedChannels: true, OrTerms: true},
|
||||
1,
|
||||
[]string{o7.Id},
|
||||
},
|
||||
{
|
||||
"search-with-deleted-and-multiple-excluded-channel-filter",
|
||||
&model.SearchParams{Terms: "Jersey corey", ExcludedChannels: []string{"channel-x", "channel-z"}, IncludeDeletedChannels: true, OrTerms: true},
|
||||
0,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"search-with-or-and-deleted",
|
||||
&model.SearchParams{Terms: "Jersey corey", OrTerms: true, IncludeDeletedChannels: true},
|
||||
3,
|
||||
[]string{o1.Id, o2.Id, o7.Id},
|
||||
},
|
||||
{
|
||||
"search-hashtag-deleted",
|
||||
&model.SearchParams{Terms: "#hashtag", IsHashtag: true, IncludeDeletedChannels: true},
|
||||
2,
|
||||
[]string{o4.Id, o6.Id},
|
||||
},
|
||||
{
|
||||
"search-deleted-only",
|
||||
&model.SearchParams{Terms: "Deleted", IncludeDeletedChannels: true},
|
||||
1,
|
||||
[]string{o8.Id},
|
||||
},
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := ss.Post().Search(teamId, userId, tc.searchParams)
|
||||
require.Nil(t, err)
|
||||
require.Len(t, result.Order, tc.expectedResultsCount)
|
||||
for _, expectedMessageResultId := range tc.expectedMessageResultIds {
|
||||
assert.Contains(t, result.Order, expectedMessageResultId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testUserCountsWithPostsByDay(t *testing.T, ss store.Store) {
|
||||
t1 := &model.Team{}
|
||||
t1.DisplayName = "DisplayName"
|
||||
|
||||
@@ -67,7 +67,6 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
t.Run("UpdateMfaActive", func(t *testing.T) { testUserStoreUpdateMfaActive(t, ss) })
|
||||
t.Run("GetRecentlyActiveUsersForTeam", func(t *testing.T) { testUserStoreGetRecentlyActiveUsersForTeam(t, ss, s) })
|
||||
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) })
|
||||
@@ -2231,373 +2230,6 @@ func assertUsers(t *testing.T, expected, actual []*model.User) {
|
||||
}
|
||||
}
|
||||
|
||||
func assertUsersMatchInAnyOrder(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.ElementsMatch(t, expectedUsernames, actualUsernames) {
|
||||
assert.ElementsMatch(t, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func testUserStoreSearch(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",
|
||||
Roles: "system_user system_admin",
|
||||
}
|
||||
_, err := ss.User().Save(u1)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u1.Id)) }()
|
||||
|
||||
u2 := &model.User{
|
||||
Username: "jim-bobby" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
Roles: "system_user",
|
||||
}
|
||||
_, err = ss.User().Save(u2)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u2.Id)) }()
|
||||
|
||||
u3 := &model.User{
|
||||
Username: "jimbo3" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
DeleteAt: 1,
|
||||
Roles: "system_admin",
|
||||
}
|
||||
_, err = ss.User().Save(u3)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u3.Id)) }()
|
||||
_, err = ss.Bot().Save(&model.Bot{
|
||||
UserId: u3.Id,
|
||||
Username: u3.Username,
|
||||
OwnerId: u1.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
u3.IsBot = true
|
||||
defer func() { require.Nil(t, ss.Bot().PermanentDelete(u3.Id)) }()
|
||||
|
||||
u5 := &model.User{
|
||||
Username: "yu" + model.NewId(),
|
||||
FirstName: "En",
|
||||
LastName: "Yu",
|
||||
Nickname: "enyu",
|
||||
Email: MakeEmail(),
|
||||
}
|
||||
_, err = ss.User().Save(u5)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u5.Id)) }()
|
||||
|
||||
u6 := &model.User{
|
||||
Username: "underscore" + model.NewId(),
|
||||
FirstName: "Du_",
|
||||
LastName: "_DE",
|
||||
Nickname: "lodash",
|
||||
Email: MakeEmail(),
|
||||
}
|
||||
_, err = ss.User().Save(u6)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u6.Id)) }()
|
||||
|
||||
tid := model.NewId()
|
||||
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u2.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u3.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u5.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
_, err = ss.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u6.Id}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
// 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},
|
||||
},
|
||||
{
|
||||
"leading @ should be ignored",
|
||||
tid,
|
||||
"@jimb",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search jim-bobby with system_user roles",
|
||||
tid,
|
||||
"jim-bobby",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
Role: "system_user",
|
||||
},
|
||||
[]*model.User{u2},
|
||||
},
|
||||
{
|
||||
"search jim with system_admin roles",
|
||||
tid,
|
||||
"jim",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
Role: "system_admin",
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search ji with system_user roles",
|
||||
tid,
|
||||
"ji",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
Role: "system_user",
|
||||
},
|
||||
[]*model.User{u1, u2},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
users, err := ss.User().Search(testCase.TeamId, testCase.Term, testCase.Options)
|
||||
require.Nil(t, err)
|
||||
assertUsersMatchInAnyOrder(t, testCase.Expected, users)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("search empty string", func(t *testing.T) {
|
||||
searchOptions := &model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
}
|
||||
|
||||
users, err := ss.User().Search(tid, "", searchOptions)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, users, 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,
|
||||
}
|
||||
|
||||
users, err := ss.User().Search(tid, "", searchOptions)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, users, 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))
|
||||
})
|
||||
}
|
||||
|
||||
func testUserStoreSearchNotInChannel(t *testing.T, ss store.Store) {
|
||||
u1 := &model.User{
|
||||
Username: "jimbo1" + model.NewId(),
|
||||
|
||||
@@ -168,8 +168,16 @@ func (h *MainHelper) GetSQLSupplier() *sqlstore.SqlSupplier {
|
||||
|
||||
func (h *MainHelper) GetClusterInterface() *FakeClusterInterface {
|
||||
if h.ClusterInterface == nil {
|
||||
panic("MainHelper not initialized with sql supplier.")
|
||||
panic("MainHelper not initialized with cluster interface.")
|
||||
}
|
||||
|
||||
return h.ClusterInterface
|
||||
}
|
||||
|
||||
func (h *MainHelper) GetSearchEngine() *searchengine.Broker {
|
||||
if h.SearchEngine == nil {
|
||||
panic("MainHelper not initialized with search engine")
|
||||
}
|
||||
|
||||
return h.SearchEngine
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user