Files
mostlymatter/store/searchtest/post_layer.go
Miguel de la Cruz 4fe25b1cdd [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>
2020-03-30 19:17:40 +02:00

1972 строки
54 KiB
Go

// 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/assert"
"github.com/stretchr/testify/require"
)
var searchPostStoreTests = []searchTest{
{"Database Post tests", testSearchDatabasePostSearch, []string{ENGINE_ALL}},
{"Elasticsearch search posts: Markdown Underscores", testSearchESSearchPosts_MarkdownUnderscores, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Email Addresses", testSearchESSearchPosts_EmailAddresses, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Chinese Words", testSearchESSearchPosts_ChineseWords, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Alternative Spellings", testSearchESSearchPosts_AlternativeSpellings, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: And Or Terms", testSearchESSearchPosts_AndOrTerms, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Paging", testSearchESSearchPosts_Paging, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Quoted Terms", testSearchESSearchPosts_QuotedTerms, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Stop Words", testSearchESSearchPosts_StopWords, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Stemming", testSearchESSearchPosts_Stemming, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Wildcard", testSearchESSearchPosts_Wildcard, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Alternative Unicode Forms", testSearchESSearchPosts_AlternativeUnicodeForms, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: From and In", testSearchESSearchPosts_FromAndIn, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: After Before On", testSearchESSearchPosts_AfterBeforeOn, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Hashtags", testSearchESSearchPosts_Hashtags, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Hashtags and Or words", testSearchESSearchPosts_HashtagsAndOrWords, []string{ENGINE_ELASTICSEARCH}},
{"Elasticsearch search posts: Hashtags case insensitive", testSearchESSearchPosts_HashtagsCaseInsensitive, []string{ENGINE_ELASTICSEARCH}},
}
func TestSearchPostStore(t *testing.T, s store.Store, testEngine *SearchTestEngine) {
runTestSearch(t, s, testEngine, searchPostStoreTests)
}
func testSearchDatabasePostSearch(t *testing.T, s store.Store) {
teamId := model.NewId()
userId := model.NewId()
u1 := &model.User{}
u1.Username = "usera1"
u1.Email = makeEmail()
u1, err := s.User().Save(u1)
require.Nil(t, err)
t1 := &model.TeamMember{}
t1.TeamId = teamId
t1.UserId = u1.Id
_, err = s.Team().SaveMember(t1, 1000)
require.Nil(t, err)
u2 := &model.User{}
u2.Username = "userb2"
u2.Email = makeEmail()
u2, err = s.User().Save(u2)
require.Nil(t, err)
t2 := &model.TeamMember{}
t2.TeamId = teamId
t2.UserId = u2.Id
_, err = s.Team().SaveMember(t2, 1000)
require.Nil(t, err)
u3 := &model.User{}
u3.Username = "userc3"
u3.Email = makeEmail()
u3, err = s.User().Save(u3)
require.Nil(t, err)
t3 := &model.TeamMember{}
t3.TeamId = teamId
t3.UserId = u3.Id
_, err = s.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, _ = s.Channel().Save(c1, -1)
m1 := model.ChannelMember{}
m1.ChannelId = c1.Id
m1.UserId = userId
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err = s.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, _ = s.Channel().Save(c2, -1)
c3 := &model.Channel{}
c3.TeamId = teamId
c3.DisplayName = "Channel3"
c3.Name = "channel-z"
c3.Type = model.CHANNEL_OPEN
c3, _ = s.Channel().Save(c3, -1)
s.Channel().Delete(c3.Id, model.GetMillis())
m3 := model.ChannelMember{}
m3.ChannelId = c3.Id
m3.UserId = userId
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err = s.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 = s.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 = s.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 = s.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 = s.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 = s.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 = s.Post().Save(o5)
require.Nil(t, err)
o6 := &model.Post{}
o6.ChannelId = c3.Id
o6.UserId = model.NewId()
o6.Hashtags = "#hashtag"
o6, err = s.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 = s.Post().Save(o7)
require.Nil(t, err)
o8 := &model.Post{}
o8.ChannelId = c3.Id
o8.UserId = model.NewId()
o8.Message = "Deleted"
o8, err = s.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 {
tc := tc
t.Run(tc.name, func(t *testing.T) {
result, err := s.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 testSearchESSearchPosts_MarkdownUnderscores(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
// Test searching for posts with Markdown underscores in them.
post, err := s.Post().Save(createPost(user.Id, channel.Id, "_start middle end_ _both_"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
IsHashtag: false,
OrTerms: false,
},
}
testCases := []struct {
Name string
Terms string
}{
{
Name: "It should match the post by start",
Terms: "start",
},
{
Name: "It should match the post by middle",
Terms: "middle",
},
{
Name: "It should match the post by end",
Terms: "end",
},
{
Name: "It should match the post by both",
Terms: "both",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
searchParams[0].Terms = tc.Terms
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
checkPostInSearchResults(t, post.Id, ids)
checkMatchesEqual(t, map[string][]string{
post.Id: {tc.Terms},
}, res.Matches)
})
}
}
func testSearchESSearchPosts_EmailAddresses(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
// Test searching for posts with Markdown underscores in them.
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "something test something example.com something"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "test@example.com"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
Terms: "test@example.com",
IsHashtag: false,
OrTerms: false,
},
}
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
checkPostNotInSearchResults(t, post1.Id, ids)
checkPostInSearchResults(t, post2.Id, ids)
checkMatchesEqual(t, map[string][]string{
post2.Id: {"test", "example.com"},
}, res.Matches)
}
func testSearchESSearchPosts_ChineseWords(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "你好"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "你"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
IsHashtag: false,
OrTerms: false,
},
}
testCases := []struct {
Name string
Terms string
Matches map[string][]string
}{
{
Name: "Test cjk 2 character word match",
Terms: "你好",
Matches: map[string][]string{
post1.Id: {"你好"},
},
},
{
Name: "Test cjk 1 character word match",
Terms: "你",
Matches: map[string][]string{
post2.Id: {"你"},
},
},
{
Name: "Test cjk wildcard word match",
Terms: "你*",
Matches: map[string][]string{
post1.Id: {"你好"},
post2.Id: {"你"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
searchParams[0].Terms = tc.Terms
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_AlternativeSpellings(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "Straße"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "Strasse"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
IsHashtag: false,
OrTerms: false,
},
}
testCases := []struct {
Name string
Terms string
Matches map[string][]string
}{
{
Name: "Search for Straße",
Terms: "Straße",
Matches: map[string][]string{
post1.Id: {"Straße"},
post2.Id: {"Strasse"},
},
},
{
Name: "Search for Strasse",
Terms: "Strasse",
Matches: map[string][]string{
post1.Id: {"Straße"},
post2.Id: {"Strasse"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
searchParams[0].Terms = tc.Terms
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_AndOrTerms(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "things and stuff"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "things"))
require.Nil(t, err)
post3, err := s.Post().Save(createPost(user.Id, channel.Id, "stuff"))
require.Nil(t, err)
t.Run("Searches for things stuff", func(t *testing.T) {
searchParams := []*model.SearchParams{
{
Terms: "things stuff",
IsHashtag: false,
OrTerms: false,
},
}
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
require.Len(t, res.Posts, 1)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
checkPostInSearchResults(t, post1.Id, ids)
checkPostNotInSearchResults(t, post2.Id, ids)
checkPostNotInSearchResults(t, post3.Id, ids)
checkMatchesEqual(t, map[string][]string{
post1.Id: {"things", "stuff"},
}, res.Matches)
})
t.Run("Searches for things", func(t *testing.T) {
searchParams := []*model.SearchParams{
{
Terms: "things",
IsHashtag: false,
OrTerms: false,
},
}
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
require.Len(t, res.Posts, 2)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
checkPostInSearchResults(t, post1.Id, ids)
checkPostInSearchResults(t, post2.Id, ids)
checkPostNotInSearchResults(t, post3.Id, ids)
checkMatchesEqual(t, map[string][]string{
post1.Id: {"things"},
post2.Id: {"things"},
}, res.Matches)
})
t.Run("Searches for stuff", func(t *testing.T) {
searchParams := []*model.SearchParams{
{
Terms: "stuff",
IsHashtag: false,
OrTerms: false,
},
}
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
require.Len(t, res.Posts, 2)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
checkPostInSearchResults(t, post1.Id, ids)
checkPostNotInSearchResults(t, post2.Id, ids)
checkPostInSearchResults(t, post3.Id, ids)
checkMatchesEqual(t, map[string][]string{
post1.Id: {"stuff"},
post3.Id: {"stuff"},
}, res.Matches)
})
t.Run("Searches for things and not stuff", func(t *testing.T) {
searchParams := []*model.SearchParams{
{
Terms: "things",
ExcludedTerms: "stuff",
IsHashtag: false,
OrTerms: false,
},
}
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
require.Len(t, res.Posts, 1)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
checkPostInSearchResults(t, post2.Id, ids)
checkMatchesEqual(t, map[string][]string{
post2.Id: {"things"},
}, res.Matches)
})
t.Run("Searches for not things", func(t *testing.T) {
searchParams := []*model.SearchParams{
{
ExcludedTerms: "things",
IsHashtag: false,
OrTerms: false,
},
}
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
require.Len(t, res.Posts, 1)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
checkPostInSearchResults(t, post3.Id, ids)
checkMatchesEqual(t, map[string][]string{
post3.Id: {},
}, res.Matches)
})
t.Run("Searches for things stuff with an or search", func(t *testing.T) {
searchParams := []*model.SearchParams{
{
Terms: "things stuff",
IsHashtag: false,
OrTerms: true,
},
}
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
require.Len(t, res.Posts, 3)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
checkPostInSearchResults(t, post1.Id, ids)
checkPostInSearchResults(t, post2.Id, ids)
checkPostInSearchResults(t, post3.Id, ids)
checkMatchesEqual(t, map[string][]string{
post1.Id: {"things", "stuff"},
post2.Id: {"things"},
post3.Id: {"stuff"},
}, res.Matches)
})
}
func testSearchESSearchPosts_Paging(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "things and stuff"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "things"))
require.Nil(t, err)
post3, err := s.Post().Save(createPost(user.Id, channel.Id, "stuff"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
Terms: "things stuff",
OrTerms: true,
IsHashtag: false,
},
}
testCases := []struct {
Name string
Page int
PerPage int
Matches map[string][]string
}{
{
Name: "The full page",
Page: 0,
PerPage: 20,
Matches: map[string][]string{
post1.Id: {"things", "stuff"},
post2.Id: {"things"},
post3.Id: {"stuff"},
},
},
{
Name: "The first page with two results",
Page: 0,
PerPage: 2,
Matches: map[string][]string{
post1.Id: {"things", "stuff"},
post2.Id: {"things"},
},
},
{
Name: "The second page with two results",
Page: 1,
PerPage: 2,
Matches: map[string][]string{
post3.Id: {"stuff"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, tc.Page, tc.PerPage)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_QuotedTerms(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "large dog"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "large scary dog"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
IsHashtag: false,
OrTerms: false,
},
}
testCases := []struct {
Name string
Terms string
ExcludedTerms string
Matches map[string][]string
}{
{
Name: "Search for large dog",
Terms: "large dog",
Matches: map[string][]string{
post1.Id: {"large", "dog"},
post2.Id: {"large", "dog"},
},
},
{
Name: "Search for \"large dog\"",
Terms: "\"large dog\"",
Matches: map[string][]string{
post1.Id: {"large", "dog"}, // Note that we don't return the whole string highlighted
},
},
{
Name: "Search for dog -\"large scary\"",
Terms: "dog",
ExcludedTerms: "\"large scary\"",
Matches: map[string][]string{
post1.Id: {"dog"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
searchParams[0].Terms = tc.Terms
searchParams[0].ExcludedTerms = tc.ExcludedTerms
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_StopWords(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "I enjoyed eating the house"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "I enjoyed eating house"))
require.Nil(t, err)
post3, err := s.Post().Save(createPost(user.Id, channel.Id, "I enjoyed eating a house"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
IsHashtag: false,
OrTerms: false,
},
}
testCases := []struct {
Name string
Terms string
Matches map[string][]string
}{
{
Name: "Search for the house",
Terms: "the house",
Matches: map[string][]string{
post1.Id: {"house"},
post2.Id: {"house"},
post3.Id: {"house"},
},
},
{
Name: "Search for eating the house",
Terms: "eating the house",
Matches: map[string][]string{
post1.Id: {"eating", "house"},
post2.Id: {"eating", "house"},
post3.Id: {"eating", "house"},
},
},
{
Name: "Search for quoted \"eating the house\"",
Terms: "\"eating the house\"",
Matches: map[string][]string{
post1.Id: {"eating", "house"},
post3.Id: {"eating", "house"},
},
},
{
Name: "Search for quoted \"eating house\"",
Terms: "\"eating house\"",
Matches: map[string][]string{
post2.Id: {"eating", "house"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
searchParams[0].Terms = tc.Terms
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_Stemming(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "sail"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "sailing"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
IsHashtag: false,
OrTerms: false,
},
}
testCases := []struct {
Name string
Terms string
Matches map[string][]string
}{
{
Name: "Search for sail",
Terms: "sail",
Matches: map[string][]string{
post1.Id: {"sail"},
post2.Id: {"sailing"},
},
},
{
Name: "Search for sailing",
Terms: "sailing",
Matches: map[string][]string{
post1.Id: {"sail"},
post2.Id: {"sailing"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
searchParams[0].Terms = tc.Terms
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_Wildcard(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "insensitive"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "insensible"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
IsHashtag: false,
OrTerms: false,
},
}
testCases := []struct {
Name string
Terms string
Matches map[string][]string
}{
{
Name: "Search for inse*",
Terms: "inse*",
Matches: map[string][]string{
post1.Id: {"insensitive"},
post2.Id: {"insensible"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
searchParams[0].Terms = tc.Terms
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_AlternativeUnicodeForms(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPost(user.Id, channel.Id, "café"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "café"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
IsHashtag: false,
OrTerms: false,
},
}
testCases := []struct {
Name string
Terms string
Matches map[string][]string
}{
{
Name: "Search for café",
Terms: "café",
Matches: map[string][]string{
post1.Id: {"café"},
post2.Id: {"café"},
},
},
{
Name: "Search for café",
Terms: "café",
Matches: map[string][]string{
post1.Id: {"café"},
post2.Id: {"café"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
searchParams[0].Terms = tc.Terms
res, err := s.Post().SearchPostsInTeamForUser(searchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_FromAndIn(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel1, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
channel2, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c2", DisplayName: "c2", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user1, err := s.User().Save(createUser("testuserone", "testuserone", "Test", "One"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user1, []string{team.Id}, []string{channel1.Id, channel2.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user1.Id)) }()
user2, err := s.User().Save(createUser("testusertwo", "testusertwo", "Test", "Two"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user2, []string{team.Id}, []string{channel1.Id, channel2.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user2.Id)) }()
post1, err := s.Post().Save(createPost(user1.Id, channel1.Id, "post in channel 1 by user 1"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user1.Id, channel2.Id, "post in channel 2 by user 1"))
require.Nil(t, err)
post3, err := s.Post().Save(createPost(user2.Id, channel1.Id, "post in channel 1 by user 2"))
require.Nil(t, err)
post4, err := s.Post().Save(createPost(user2.Id, channel2.Id, "post in channel 2 by user 2"))
require.Nil(t, err)
searchParams := []*model.SearchParams{
{
Terms: "post",
IsHashtag: false,
OrTerms: false,
},
}
testCases := []struct {
Name string
User *model.User
FromUsers []string
InChannels []string
ExcludedUsers []string
ExcludedChannels []string
Matches map[string][]string
}{
{
Name: "Search for post from the user1 in the channel1",
User: user1,
InChannels: []string{channel1.Id},
FromUsers: []string{user1.Id},
Matches: map[string][]string{
post1.Id: {"post"},
},
},
{
Name: "Search for post from the user1 in channel1 and channel2",
User: user1,
InChannels: []string{channel1.Id, channel2.Id},
FromUsers: []string{user1.Id},
Matches: map[string][]string{
post1.Id: {"post"},
post2.Id: {"post"},
},
},
{
Name: "Search for post from user1 and user2 in channel1 and channel2",
User: user1,
InChannels: []string{channel1.Id, channel2.Id},
FromUsers: []string{user1.Id, user2.Id},
Matches: map[string][]string{
post1.Id: {"post"},
post2.Id: {"post"},
post3.Id: {"post"},
post4.Id: {"post"},
},
},
{
Name: "Search for post not from the user1 in the channel1",
User: user1,
InChannels: []string{channel1.Id},
ExcludedUsers: []string{user1.Id},
Matches: map[string][]string{
post3.Id: {"post"},
},
},
{
Name: "Search for post not in the channel1",
User: user1,
ExcludedChannels: []string{channel1.Id},
Matches: map[string][]string{
post2.Id: {"post"},
post4.Id: {"post"},
},
},
{
Name: "Search for post not from user1 and not in the channel1",
User: user1,
ExcludedUsers: []string{user1.Id},
ExcludedChannels: []string{channel1.Id},
Matches: map[string][]string{
post4.Id: {"post"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
searchParams[0].FromUsers = tc.FromUsers
searchParams[0].InChannels = tc.InChannels
searchParams[0].ExcludedUsers = tc.ExcludedUsers
searchParams[0].ExcludedChannels = tc.ExcludedChannels
res, err := s.Post().SearchPostsInTeamForUser(searchParams, tc.User.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_AfterBeforeOn(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPostAtTime(user.Id, channel.Id, "post on 2018-08-28 at 23:59 GMT", 1535500740000))
require.Nil(t, err)
post2, err := s.Post().Save(createPostAtTime(user.Id, channel.Id, "post on 2018-08-29 at 00:01 GMT", 1535500860000))
require.Nil(t, err)
post3, err := s.Post().Save(createPostAtTime(user.Id, channel.Id, "post on 2018-08-29 at 23:59 GMT", 1535587140000))
require.Nil(t, err)
post4, err := s.Post().Save(createPostAtTime(user.Id, channel.Id, "post on 2018-08-30 at 00:01 GMT", 1535587260000))
require.Nil(t, err)
earlierTimeZoneOffset := 1 * 60 * 60
laterTimeZoneOffset := -1 * 60 * 60
testCases := []struct {
Name string
SearchParams []*model.SearchParams
ExpectedIds []string
}{
{
Name: "after: should return posts on 2018-08-30 or later",
SearchParams: []*model.SearchParams{
{
AfterDate: "2018-08-28",
},
},
ExpectedIds: []string{post2.Id, post3.Id, post4.Id},
},
{
Name: "after: should return posts on 2018-09-01 or later",
SearchParams: []*model.SearchParams{
{
AfterDate: "2018-08-30",
},
},
ExpectedIds: []string{},
},
{
Name: "not before: should return posts on 2018-08-28 or later",
SearchParams: []*model.SearchParams{
{
ExcludedBeforeDate: "2018-08-28",
},
},
ExpectedIds: []string{post1.Id, post2.Id, post3.Id, post4.Id},
},
{
Name: "not before: should return posts on 2018-08-29 or later",
SearchParams: []*model.SearchParams{
{
ExcludedBeforeDate: "2018-08-29",
},
},
ExpectedIds: []string{post2.Id, post3.Id, post4.Id},
},
{
Name: "not before: should return posts on 2018-08-30 or later",
SearchParams: []*model.SearchParams{
{
ExcludedBeforeDate: "2018-08-30",
},
},
ExpectedIds: []string{post4.Id},
},
{
Name: "not before: should return posts on 2018-09-01 or later",
SearchParams: []*model.SearchParams{
{
ExcludedBeforeDate: "2018-09-01",
},
},
ExpectedIds: []string{},
},
{
Name: "before: should return posts on 2018-08-29 or earlier",
SearchParams: []*model.SearchParams{
{
BeforeDate: "2018-08-30",
},
},
ExpectedIds: []string{post1.Id, post2.Id, post3.Id},
},
{
Name: "before: should return posts on 2018-08-28 or earlier",
SearchParams: []*model.SearchParams{
{
BeforeDate: "2018-08-29",
},
},
ExpectedIds: []string{post1.Id},
},
{
Name: "before: should return posts on 2018-08-27 or earlier",
SearchParams: []*model.SearchParams{
{
BeforeDate: "2018-08-28",
},
},
ExpectedIds: []string{},
},
{
Name: "after and before: should return posts on 2018-08-29",
SearchParams: []*model.SearchParams{
{
AfterDate: "2018-08-28",
BeforeDate: "2018-08-30",
},
},
ExpectedIds: []string{post2.Id, post3.Id},
},
{
Name: "not after: should return posts on 2018-08-30 or earlier",
SearchParams: []*model.SearchParams{
{
ExcludedAfterDate: "2018-09-01",
},
},
ExpectedIds: []string{post1.Id, post2.Id, post3.Id, post4.Id},
},
{
Name: "not after: should return posts on 2018-08-29 or earlier",
SearchParams: []*model.SearchParams{
{
ExcludedAfterDate: "2018-08-29",
},
},
ExpectedIds: []string{post1.Id, post2.Id, post3.Id},
},
{
Name: "not after: should return posts on 2018-08-28 or earlier",
SearchParams: []*model.SearchParams{
{
ExcludedAfterDate: "2018-08-28",
},
},
ExpectedIds: []string{post1.Id},
},
{
Name: "not after: should return posts on 2018-08-27 or earlier",
SearchParams: []*model.SearchParams{
{
ExcludedAfterDate: "2018-08-27",
},
},
ExpectedIds: []string{},
},
{
Name: "not after and not before: should return posts on 2018-08-29",
SearchParams: []*model.SearchParams{
{
ExcludedAfterDate: "2018-08-29",
ExcludedBeforeDate: "2018-08-29",
},
},
ExpectedIds: []string{post2.Id, post3.Id},
},
{
Name: "on: should return posts on 2018-08-28",
SearchParams: []*model.SearchParams{
{
OnDate: "2018-08-28",
},
},
ExpectedIds: []string{post1.Id},
},
{
Name: "on: should return posts on 2018-08-29",
SearchParams: []*model.SearchParams{
{
OnDate: "2018-08-29",
},
},
ExpectedIds: []string{post2.Id, post3.Id},
},
{
Name: "on: should return posts on 2018-08-30",
SearchParams: []*model.SearchParams{
{
OnDate: "2018-08-30",
},
},
ExpectedIds: []string{post4.Id},
},
{
Name: "not on: should return all posts but the ones on 2018-08-28",
SearchParams: []*model.SearchParams{
{
ExcludedDate: "2018-08-28",
},
},
ExpectedIds: []string{post2.Id, post3.Id, post4.Id},
},
{
Name: "not on: should return all posts but the ones on 2018-08-29",
SearchParams: []*model.SearchParams{
{
ExcludedDate: "2018-08-29",
},
},
ExpectedIds: []string{post1.Id, post4.Id},
},
{
Name: "not on: should return all posts but the ones on 2018-08-30",
SearchParams: []*model.SearchParams{
{
ExcludedDate: "2018-08-30",
},
},
ExpectedIds: []string{post1.Id, post2.Id, post3.Id},
},
{
Name: "not on and after: should return posts after 2018-08-30",
SearchParams: []*model.SearchParams{
{
AfterDate: "2018-08-28",
ExcludedDate: "2018-08-29",
},
},
ExpectedIds: []string{post4.Id},
},
{
Name: "after with different time zones: should return posts after 2018-08-27 23:00",
SearchParams: []*model.SearchParams{
{
AfterDate: "2018-08-28",
TimeZoneOffset: earlierTimeZoneOffset,
},
},
ExpectedIds: []string{post1.Id, post2.Id, post3.Id, post4.Id},
},
{
Name: "after with different time zones: should return posts after 2018-08-28 01:00",
SearchParams: []*model.SearchParams{
{
AfterDate: "2018-08-28",
TimeZoneOffset: laterTimeZoneOffset,
},
},
ExpectedIds: []string{post3.Id, post4.Id},
},
{
Name: "before with different time zones: should return posts before 2018-08-27 23:00",
SearchParams: []*model.SearchParams{
{
BeforeDate: "2018-08-30",
TimeZoneOffset: earlierTimeZoneOffset,
},
},
ExpectedIds: []string{post1.Id, post2.Id},
},
{
Name: "before with different time zones: should return posts before 2018-08-28 01:00",
SearchParams: []*model.SearchParams{
{
BeforeDate: "2018-08-30",
TimeZoneOffset: laterTimeZoneOffset,
},
},
ExpectedIds: []string{post1.Id, post2.Id, post3.Id, post4.Id},
},
{
Name: "on with different time zones: should return posts after 2018-08-28 23:00 and before 2018-08-29 23:00",
SearchParams: []*model.SearchParams{
{
OnDate: "2018-08-29",
TimeZoneOffset: earlierTimeZoneOffset,
},
},
ExpectedIds: []string{post1.Id, post2.Id},
},
{
Name: "on with different time zones: should return posts after 2018-08-29 01:00 and before 2018-08-30 01:00",
SearchParams: []*model.SearchParams{
{
OnDate: "2018-08-29",
TimeZoneOffset: laterTimeZoneOffset,
},
},
ExpectedIds: []string{post3.Id, post4.Id},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
res, err := s.Post().SearchPostsInTeamForUser(tc.SearchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, tc.ExpectedIds, ids)
})
}
}
func testSearchESSearchPosts_Hashtags(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
post1, err := s.Post().Save(createPostWithHashtags(user.Id, channel.Id, "From bean to cup, you #hashtag up", "#hashtag"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "I liek hashtags"))
require.Nil(t, err)
post3, err := s.Post().Save(createPostWithHashtags(user.Id, channel.Id, "moar #hashtag please", "#hashtag"))
require.Nil(t, err)
testCases := []struct {
Name string
SearchParams []*model.SearchParams
Matches map[string][]string
}{
{
Name: "Search for plain term hashtag",
SearchParams: []*model.SearchParams{
{
Terms: "hashtag",
IsHashtag: false,
OrTerms: false,
},
},
Matches: map[string][]string{
post1.Id: {"hashtag", "#hashtag"},
post2.Id: {"hashtags"},
post3.Id: {"hashtag", "#hashtag"},
},
},
{
Name: "Search for #hashtag",
SearchParams: []*model.SearchParams{
{
Terms: "#hashtag",
IsHashtag: true,
OrTerms: false,
},
},
Matches: map[string][]string{
post1.Id: {"#hashtag"},
post3.Id: {"#hashtag"},
},
},
{
Name: "Search for #hashtag and bean",
SearchParams: []*model.SearchParams{
{
Terms: "#hashtag",
IsHashtag: true,
OrTerms: false,
},
{
Terms: "bean",
IsHashtag: false,
OrTerms: false,
},
},
Matches: map[string][]string{
post1.Id: {"bean", "#hashtag"},
},
},
{
Name: "Search for not #hashtag",
SearchParams: []*model.SearchParams{
{
ExcludedTerms: "#hashtag",
IsHashtag: true,
OrTerms: false,
},
},
Matches: map[string][]string{
post2.Id: {},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
res, err := s.Post().SearchPostsInTeamForUser(tc.SearchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}
func testSearchESSearchPosts_HashtagsAndOrWords(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
// Test searching for posts with Markdown underscores in them.
post1, err := s.Post().Save(createPostWithHashtags(user.Id, channel.Id, "From bean to cup, you #hashtag up", "#hashtag"))
require.Nil(t, err)
post2, err := s.Post().Save(createPostWithHashtags(user.Id, channel.Id, "#hashtag mashup city", "#hashtag"))
require.Nil(t, err)
post3, err := s.Post().Save(createPost(user.Id, channel.Id, "has bean"))
require.Nil(t, err)
testCases := []struct {
Name string
SearchParams []*model.SearchParams
ExpectedIds []string
}{
{
Name: "Search for bean or #hashtag",
SearchParams: []*model.SearchParams{
{
Terms: "bean",
IsHashtag: false,
OrTerms: false,
},
{
Terms: "#hashtag",
IsHashtag: true,
OrTerms: false,
},
},
ExpectedIds: []string{post1.Id},
},
{
Name: "Search for bean and #hashtag",
SearchParams: []*model.SearchParams{
{
Terms: "bean",
IsHashtag: false,
OrTerms: true,
},
{
Terms: "#hashtag",
IsHashtag: true,
OrTerms: true,
},
},
ExpectedIds: []string{post1.Id, post2.Id, post3.Id},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
res, err := s.Post().SearchPostsInTeamForUser(tc.SearchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, tc.ExpectedIds, ids)
})
}
}
func testSearchESSearchPosts_HashtagsCaseInsensitive(t *testing.T, s store.Store) {
team, err := s.Team().Save(&model.Team{Name: "t1", DisplayName: "t1", Type: model.TEAM_OPEN})
require.Nil(t, err)
defer func() { require.Nil(t, s.Team().PermanentDelete(team.Id)) }()
channel, err := s.Channel().Save(&model.Channel{TeamId: team.Id, Name: "c1", DisplayName: "c1", Type: model.CHANNEL_OPEN}, -1)
require.Nil(t, err)
user, err := s.User().Save(createUser("testuser", "testuser", "Test", "User"))
require.Nil(t, err)
require.Nil(t, addUserToTeamsAndChannels(s, user, []string{team.Id}, []string{channel.Id}))
defer func() { require.Nil(t, s.User().PermanentDelete(user.Id)) }()
// Test searching for posts with Markdown underscores in them.
post1, err := s.Post().Save(createPostWithHashtags(user.Id, channel.Id, "From bean to cup, you #hashtag up", "#hashtag"))
require.Nil(t, err)
post2, err := s.Post().Save(createPost(user.Id, channel.Id, "I liek hashtags"))
require.Nil(t, err)
post3, err := s.Post().Save(createPostWithHashtags(user.Id, channel.Id, "moar #Hashtag please", "#Hashtag"))
require.Nil(t, err)
testCases := []struct {
Name string
SearchParams []*model.SearchParams
Matches map[string][]string
}{
{
Name: "Search for hashtag",
SearchParams: []*model.SearchParams{
{
Terms: "hashtag",
IsHashtag: false,
OrTerms: false,
},
},
Matches: map[string][]string{
post1.Id: {"hashtag", "#hashtag"},
post2.Id: {"hashtags"},
post3.Id: {"Hashtag", "#hashtag"},
},
},
{
Name: "Search for #hashtag",
SearchParams: []*model.SearchParams{
{
Terms: "#hashtag",
IsHashtag: true,
OrTerms: false,
},
},
Matches: map[string][]string{
post1.Id: {"#hashtag"},
post3.Id: {"#hashtag"},
},
},
{
Name: "Search for #HashTag",
SearchParams: []*model.SearchParams{
{
Terms: "#HashTag",
IsHashtag: true,
OrTerms: false,
},
},
Matches: map[string][]string{
post1.Id: {"#hashtag"},
post3.Id: {"#hashtag"},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
res, err := s.Post().SearchPostsInTeamForUser(tc.SearchParams, user.Id, team.Id, false, false, 0, 20)
require.Nil(t, err)
expectedIds := []string{}
for id := range tc.Matches {
expectedIds = append(expectedIds, id)
}
ids := []string{}
for id := range res.Posts {
ids = append(ids, id)
}
require.ElementsMatch(t, expectedIds, ids)
checkMatchesEqual(t, tc.Matches, res.Matches)
})
}
}