[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>
Этот коммит содержится в:
Miguel de la Cruz
2020-03-30 19:17:40 +02:00
коммит произвёл GitHub
родитель 2b1b001bcc
Коммит 4fe25b1cdd
21 изменённых файлов: 3197 добавлений и 868 удалений

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

@@ -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(),