Files
mostlymatter/server/channels/store/searchtest/post_layer.go
Alejandro García Montoro fddd4a70bc Avoid simple config when doing FTS in Postgres (#35063) (#35179)
This commit reverts PR #30214, which addressed bug MM-60790 but caused a
performance regression tracked by MM-66782.

This revert has two implications:

1. The performance issue is solved.
2. The original bug is re-introduced.

Re-introducing the original bug seems not to be ideal, but I argue that
the original PR did not actually fix the bug:

- Before that PR, looking for a quoted string would return additional
  results: the UX was slightly confusing, because when the user looked
  for the word "stateful", the results would contain matches like
  "states" (see MM-60790).
- After that PR, looking for a quoted string can timeout, so that the
  list of results becomes empty. The UX here may be less confusing,
  since the user simply doesn't find what they're looking for, and they
  may assume that string is not present in any post, but it's completely
  wrong: the result list is empty because the SQL query timed out and
  thus the endpoint returned 0 results.

The solution to the original issue should be addressed via
Elasticsearch, which should provide a more refined and precise search
results.

For more information on the investigation on this issue and the
motivation behind the revert, see
https://mattermost.atlassian.net/wiki/x/IYAk_w

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-02-04 13:00:44 +01:00

2050 строки
81 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package searchtest
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
)
var searchPostStoreTests = []searchTest{
{
Name: "Should be able to search posts including results from DMs",
Fn: testSearchPostsIncludingDMs,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search posts using pagination",
Fn: testSearchPostsWithPagination,
Tags: []string{EngineElasticSearch, EngineBleve},
},
{
Name: "Should return pinned and unpinned posts",
Fn: testSearchReturnPinnedAndUnpinned,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search for quoted patterns with AND OR combinations",
Fn: testSearchANDORQuotesCombinations,
Tags: []string{EnginePostgres, EngineMySQL, EngineElasticSearch},
},
{
// Postgres supports search with and without quotes
Name: "Should be able to search for email addresses with or without quotes",
Fn: testSearchEmailAddresses,
Tags: []string{EnginePostgres, EngineElasticSearch},
},
{
// MySql supports search with quotes only
Name: "Should be able to search for email addresses with quotes",
Fn: testSearchEmailAddressesWithQuotes,
Tags: []string{EngineMySQL},
},
{
Name: "Should be able to search when markdown underscores are applied",
Fn: testSearchMarkdownUnderscores,
Tags: []string{EnginePostgres, EngineElasticSearch},
},
{
Name: "Should be able to search for non-latin words",
Fn: testSearchNonLatinWords,
Tags: []string{EngineElasticSearch},
},
{
Name: "Should be able to search for alternative spellings of words",
Fn: testSearchAlternativeSpellings,
Tags: []string{EngineElasticSearch},
},
{
Name: "Should be able to search for alternative spellings of words with and without accents",
Fn: testSearchAlternativeSpellingsAccents,
Tags: []string{EngineElasticSearch},
},
{
Name: "Should be able to search or exclude messages written by a specific user",
Fn: testSearchOrExcludePostsBySpecificUser,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search or exclude messages written in a specific channel",
Fn: testSearchOrExcludePostsInChannel,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search or exclude messages written in a DM or GM",
Fn: testSearchOrExcludePostsInDMGM,
Tags: []string{EngineAll},
},
{
Name: "Should be able to filter messages written after a specific date",
Fn: testFilterMessagesAfterSpecificDate,
Tags: []string{EngineAll},
},
{
Name: "Should be able to filter messages written before a specific date",
Fn: testFilterMessagesBeforeSpecificDate,
Tags: []string{EngineAll},
},
{
Name: "Should be able to filter messages written on a specific date",
Fn: testFilterMessagesInSpecificDate,
Tags: []string{EngineAll},
},
{
Name: "Should be able to exclude messages that contain a search term",
Fn: testFilterMessagesWithATerm,
Tags: []string{EngineMySQL, EnginePostgres},
},
{
Name: "Should be able to search using boolean operators",
Fn: testSearchUsingBooleanOperators,
Tags: []string{EngineMySQL, EnginePostgres, EngineElasticSearch},
},
{
Name: "Should be able to search with combined filters",
Fn: testSearchUsingCombinedFilters,
Tags: []string{EngineAll},
},
{
Name: "Should be able to ignore stop words",
Fn: testSearchIgnoringStopWords,
Tags: []string{EngineMySQL, EngineElasticSearch},
},
{
Name: "Should support search stemming",
Fn: testSupportStemming,
Tags: []string{EnginePostgres, EngineElasticSearch},
},
{
Name: "Should support search with wildcards",
Fn: testSupportWildcards,
Tags: []string{EngineAll},
},
{
Name: "Should not support search with preceding wildcards",
Fn: testNotSupportPrecedingWildcards,
Tags: []string{EngineAll},
},
{
Name: "Should discard a wildcard if it's not placed immediately by text",
Fn: testSearchDiscardWildcardAlone,
Tags: []string{EngineAll},
},
{
Name: "Should support terms with dash",
Fn: testSupportTermsWithDash,
Tags: []string{EngineAll},
Skip: true,
},
{
Name: "Should support terms with underscore",
Fn: testSupportTermsWithUnderscore,
Tags: []string{EngineMySQL, EngineElasticSearch},
},
{
Name: "Should search or exclude post using hashtags",
Fn: testSearchOrExcludePostsWithHashtags,
Tags: []string{EngineAll},
},
{
Name: "Should support searching for hashtags surrounded by markdown",
Fn: testSearchHashtagWithMarkdown,
Tags: []string{EngineAll},
},
{
Name: "Should support searching for multiple hashtags",
Fn: testSearchWithMultipleHashtags,
Tags: []string{EngineElasticSearch},
},
{
Name: "Should support searching hashtags with dots",
Fn: testSearchPostsWithDotsInHashtags,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search or exclude messages with hashtags in a case insensitive manner",
Fn: testSearchHashtagCaseInsensitive,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search by hashtags with dashes",
Fn: testSearchHashtagWithDash,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search by hashtags with numbers",
Fn: testSearchHashtagWithNumbers,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search by hashtags with dots",
Fn: testSearchHashtagWithDots,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search by hashtags with underscores",
Fn: testSearchHashtagWithUnderscores,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search by hashtags with dashes and numbers",
Fn: testSearchHashtagWithDashAndNumbers,
Tags: []string{EngineAll},
},
{
Name: "Should not return system messages",
Fn: testSearchShouldExcludeSystemMessages,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search matching by mentions",
Fn: testSearchShouldBeAbleToMatchByMentions,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search in deleted/archived channels",
Fn: testSearchInDeletedOrArchivedChannels,
Tags: []string{EngineMySQL, EnginePostgres},
},
{
Name: "Should be able to search terms with dashes",
Fn: testSearchTermsWithDashes,
Tags: []string{EngineAll},
Skip: true,
SkipMessage: "Not working",
},
{
Name: "Should be able to search terms with dots",
Fn: testSearchTermsWithDots,
Tags: []string{EnginePostgres, EngineElasticSearch},
},
{
Name: "Should be able to search terms with underscores",
Fn: testSearchTermsWithUnderscores,
Tags: []string{EngineMySQL, EngineElasticSearch},
},
{
Name: "Should be able to search posts made by bot accounts",
Fn: testSearchBotAccountsPosts,
Tags: []string{EngineAll},
},
{
Name: "Should be able to combine stemming and wildcards",
Fn: testSupportStemmingAndWildcards,
Tags: []string{EngineElasticSearch},
},
{
Name: "Should support wildcard outside quotes",
Fn: testSupportWildcardOutsideQuotes,
Tags: []string{EngineElasticSearch},
},
{
Name: "Should support hashtags with 3 or more characters",
Fn: testHashtagSearchShouldSupportThreeOrMoreCharacters,
Tags: []string{EngineAll},
},
{
Name: "Should not support slash as character separator",
Fn: testSlashShouldNotBeCharSeparator,
Tags: []string{EngineMySQL, EngineElasticSearch},
},
{
Name: "Should be able to search in comments",
Fn: testSupportSearchInComments,
Tags: []string{EngineAll},
},
{
Name: "Should be able to search terms within links",
Fn: testSupportSearchTermsWithinLinks,
Tags: []string{EngineMySQL, EngineElasticSearch},
},
{
Name: "Should not return links that are embedded in markdown",
Fn: testShouldNotReturnLinksEmbeddedInMarkdown,
Tags: []string{EnginePostgres, EngineElasticSearch},
},
{
Name: "Should search across teams",
Fn: testSearchAcrossTeams,
Tags: []string{EngineAll},
},
{
Name: "Should search across teams with from filter",
Fn: testSearchAcrossTeamsWithFromFilter,
Tags: []string{EngineAll},
},
{
Name: "Should be removed from search index when deleted",
Fn: testSearchPostDeleted,
Tags: []string{EngineAll},
},
}
func TestSearchPostStore(t *testing.T, s store.Store, testEngine *SearchTestEngine) {
th := &SearchTestHelper{
Context: request.TestContext(t),
Store: s,
}
err := th.SetupBasicFixtures()
require.NoError(t, err)
defer th.CleanFixtures()
runTestSearch(t, testEngine, searchPostStoreTests, th)
}
func testSearchPostsIncludingDMs(t *testing.T, th *SearchTestHelper) {
direct, err := th.createDirectChannel(th.Team.Id, "direct", []*model.User{th.User, th.User2})
require.NoError(t, err)
defer th.deleteChannel(direct)
p1, err := th.createPost(th.User.Id, direct.Id, "dm test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, direct.Id, "dm other", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "channel test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "test"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
}
func testSearchPostsWithPagination(t *testing.T, th *SearchTestHelper) {
direct, err := th.createDirectChannel(th.Team.Id, "direct", []*model.User{th.User, th.User2})
require.NoError(t, err)
defer th.deleteChannel(direct)
p1, err := th.createPost(th.User.Id, direct.Id, "dm test", "", model.PostTypeDefault, 10000, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, direct.Id, "dm other", "", model.PostTypeDefault, 20000, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "channel test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "test"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 1)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
results, err = th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 1, 1)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSearchReturnPinnedAndUnpinned(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "channel test unpinned", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "channel test pinned", "", model.PostTypeDefault, 0, true)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "test"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
}
func testSearchANDORQuotesCombinations(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "one two three four", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "one two five", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "one five six", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
testCases := []struct {
name string
terms string
orTerms bool
expectedLen int
expectedIDs []string
}{
{
name: "AND operator, No Quotes, Matches 1",
terms: `two four`,
orTerms: false,
expectedLen: 1,
expectedIDs: []string{p1.Id},
},
{
name: "AND operator, No Quotes, Matches 0",
terms: `two six`,
orTerms: false,
expectedLen: 0,
expectedIDs: []string{},
},
{
name: "AND operator, With Full Quotes, Matches 0",
terms: `"two four"`,
orTerms: false,
expectedLen: 0,
expectedIDs: []string{},
},
{
name: "AND operator, With Full Quotes, Matches 1",
terms: `"two three four"`,
orTerms: false,
expectedLen: 1,
expectedIDs: []string{p1.Id},
},
{
name: "AND operator, With Part Quotes, Matches 1",
terms: `two "three four"`,
orTerms: false,
expectedLen: 1,
expectedIDs: []string{p1.Id},
},
{
name: "OR operator, No Quotes, Matches 2",
terms: `two four`,
orTerms: true,
expectedLen: 2,
expectedIDs: []string{p1.Id, p2.Id},
},
{
name: "OR operator, No Quotes, Matches 3",
terms: `two six`,
orTerms: true,
expectedLen: 3,
expectedIDs: []string{p1.Id, p2.Id, p3.Id},
},
{
name: "OR operator, With Full Quotes, Matches 0",
terms: `"two four"`,
orTerms: true,
expectedLen: 0,
expectedIDs: []string{},
},
{
name: "OR operator, With Full Quotes, Matches 1",
terms: `"two three four"`,
orTerms: true,
expectedLen: 1,
expectedIDs: []string{p1.Id},
},
{
name: "OR operator, With Part Quotes, Matches 2",
terms: `two "three four"`,
orTerms: true,
expectedLen: 2,
expectedIDs: []string{p1.Id, p2.Id},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
params := &model.SearchParams{Terms: tc.terms, OrTerms: tc.orTerms}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, tc.expectedLen)
for _, id := range tc.expectedIDs {
th.checkPostInSearchResults(t, id, results.Posts)
}
})
}
}
func testSearchEmailAddresses(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "email test@test.com", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "email test2@test.com", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search email addresses enclosed by quotes", func(t *testing.T) {
params := &model.SearchParams{Terms: "\"test@test.com\""}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search email addresses without quotes", func(t *testing.T) {
params := &model.SearchParams{Terms: "test@test.com"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
}
func testSearchEmailAddressesWithQuotes(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "email test@test.com", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "email test2@test.com", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "\"test@test.com\""}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSearchMarkdownUnderscores(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "_start middle end_ _another_", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search the start inside the markdown underscore", func(t *testing.T) {
params := &model.SearchParams{Terms: "start"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search a word in the middle of the markdown underscore", func(t *testing.T) {
params := &model.SearchParams{Terms: "middle"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search in the end of the markdown underscore", func(t *testing.T) {
params := &model.SearchParams{Terms: "end"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search inside markdown underscore", func(t *testing.T) {
params := &model.SearchParams{Terms: "another"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
}
func testSearchNonLatinWords(t *testing.T, th *SearchTestHelper) {
t.Run("Should be able to search chinese words", func(t *testing.T) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "你好", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "你", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search one word", func(t *testing.T) {
params := &model.SearchParams{Terms: "你"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should search two words", func(t *testing.T) {
params := &model.SearchParams{Terms: "你好"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search with wildcard", func(t *testing.T) {
params := &model.SearchParams{Terms: "你*"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
})
t.Run("Should be able to search cyrillic words", func(t *testing.T) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "слово test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search one word", func(t *testing.T) {
params := &model.SearchParams{Terms: "слово"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search using wildcard", func(t *testing.T) {
params := &model.SearchParams{Terms: "слов*"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
})
t.Run("Should be able to search japanese words", func(t *testing.T) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "本", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "本木", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search one word", func(t *testing.T) {
params := &model.SearchParams{Terms: "本"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should search two words", func(t *testing.T) {
params := &model.SearchParams{Terms: "本木"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should search with wildcard", func(t *testing.T) {
params := &model.SearchParams{Terms: "本*"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
})
t.Run("Should be able to search korean words", func(t *testing.T) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "불", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "불다", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search one word", func(t *testing.T) {
params := &model.SearchParams{Terms: "불"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search two words", func(t *testing.T) {
params := &model.SearchParams{Terms: "불다"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should search with wildcard", func(t *testing.T) {
params := &model.SearchParams{Terms: "불*"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
})
}
func testSearchAlternativeSpellings(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "Straße test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "Strasse test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "Straße"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
params = &model.SearchParams{Terms: "Strasse"}
results, err = th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
}
func testSearchAlternativeSpellingsAccents(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "café", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "café", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "café"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
params = &model.SearchParams{Terms: "café"}
results, err = th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
params = &model.SearchParams{Terms: "cafe"}
results, err = th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 0)
}
func testSearchOrExcludePostsBySpecificUser(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "test fromuser", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User2.Id, th.ChannelPrivate.Id, "test fromuser 2", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
defer th.deleteUserPosts(th.User2.Id)
params := &model.SearchParams{
Terms: "fromuser",
FromUsers: []string{th.User.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSearchOrExcludePostsInChannel(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test fromuser", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User2.Id, th.ChannelPrivate.Id, "test fromuser 2", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
defer th.deleteUserPosts(th.User2.Id)
params := &model.SearchParams{
Terms: "fromuser",
InChannels: []string{th.ChannelBasic.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSearchOrExcludePostsInDMGM(t *testing.T, th *SearchTestHelper) {
direct, err := th.createDirectChannel(th.Team.Id, "direct", []*model.User{th.User, th.User2})
require.NoError(t, err)
defer th.deleteChannel(direct)
group, err := th.createGroupChannel(th.Team.Id, "test group", []*model.User{th.User, th.User2})
require.NoError(t, err)
defer th.deleteChannel(group)
p1, err := th.createPost(th.User.Id, direct.Id, "test fromuser", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User2.Id, group.Id, "test fromuser 2", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
defer th.deleteUserPosts(th.User2.Id)
t.Run("Should be able to search in both DM and GM channels", func(t *testing.T) {
params := &model.SearchParams{
Terms: "fromuser",
InChannels: []string{direct.Id, group.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should be able to search only in DM channel", func(t *testing.T) {
params := &model.SearchParams{
Terms: "fromuser",
InChannels: []string{direct.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should be able to search only in GM channel", func(t *testing.T) {
params := &model.SearchParams{
Terms: "fromuser",
InChannels: []string{group.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
}
func testFilterMessagesInSpecificDate(t *testing.T, th *SearchTestHelper) {
creationDate := model.GetMillisForTime(time.Date(2020, 03, 22, 12, 0, 0, 0, time.UTC))
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test in specific date", "", model.PostTypeDefault, creationDate, false)
require.NoError(t, err)
creationDate2 := model.GetMillisForTime(time.Date(2020, 03, 23, 0, 0, 0, 0, time.UTC))
p2, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "test in the present", "", model.PostTypeDefault, creationDate2, false)
require.NoError(t, err)
creationDate3 := model.GetMillisForTime(time.Date(2020, 03, 21, 23, 59, 59, 0, time.UTC))
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test in the present", "", model.PostTypeDefault, creationDate3, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should be able to search posts on date", func(t *testing.T) {
params := &model.SearchParams{
Terms: "test",
OnDate: "2020-03-22",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should be able to exclude posts on date", func(t *testing.T) {
params := &model.SearchParams{
Terms: "test",
ExcludedDate: "2020-03-22",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
}
func testFilterMessagesBeforeSpecificDate(t *testing.T, th *SearchTestHelper) {
creationDate := model.GetMillisForTime(time.Date(2020, 03, 01, 12, 0, 0, 0, time.UTC))
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test in specific date", "", model.PostTypeDefault, creationDate, false)
require.NoError(t, err)
creationDate2 := model.GetMillisForTime(time.Date(2020, 03, 22, 23, 59, 59, 0, time.UTC))
p2, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "test in specific date 2", "", model.PostTypeDefault, creationDate2, false)
require.NoError(t, err)
creationDate3 := model.GetMillisForTime(time.Date(2020, 03, 26, 16, 55, 0, 0, time.UTC))
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test in the present", "", model.PostTypeDefault, creationDate3, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should be able to search posts before a date", func(t *testing.T) {
params := &model.SearchParams{
Terms: "test",
BeforeDate: "2020-03-23",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should be able to exclude posts before a date", func(t *testing.T) {
params := &model.SearchParams{
Terms: "test",
ExcludedBeforeDate: "2020-03-23",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
}
func testFilterMessagesAfterSpecificDate(t *testing.T, th *SearchTestHelper) {
creationDate := model.GetMillisForTime(time.Date(2020, 03, 01, 12, 0, 0, 0, time.UTC))
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test in specific date", "", model.PostTypeDefault, creationDate, false)
require.NoError(t, err)
creationDate2 := model.GetMillisForTime(time.Date(2020, 03, 22, 23, 59, 59, 0, time.UTC))
p2, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "test in specific date 2", "", model.PostTypeDefault, creationDate2, false)
require.NoError(t, err)
creationDate3 := model.GetMillisForTime(time.Date(2020, 03, 26, 16, 55, 0, 0, time.UTC))
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test in the present", "", model.PostTypeDefault, creationDate3, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should be able to search posts after a date", func(t *testing.T) {
params := &model.SearchParams{
Terms: "test",
AfterDate: "2020-03-23",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Should be able to exclude posts after a date", func(t *testing.T) {
params := &model.SearchParams{
Terms: "test",
ExcludedAfterDate: "2020-03-23",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
}
func testFilterMessagesWithATerm(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "one two three", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "one four five six", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "one seven eight nine", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should exclude terms", func(t *testing.T) {
params := &model.SearchParams{
Terms: "one",
ExcludedTerms: "five eight",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should exclude quoted terms", func(t *testing.T) {
params := &model.SearchParams{
Terms: "one",
ExcludedTerms: "\"eight nine\"",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
}
func testSearchUsingBooleanOperators(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "one two three message", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "two messages", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "another message", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search posts using OR operator", func(t *testing.T) {
params := &model.SearchParams{
Terms: "one two",
OrTerms: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should search posts using AND operator", func(t *testing.T) {
params := &model.SearchParams{
Terms: "one two",
OrTerms: false,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
}
func testSearchUsingCombinedFilters(t *testing.T, th *SearchTestHelper) {
creationDate := model.GetMillisForTime(time.Date(2020, 03, 01, 12, 0, 0, 0, time.UTC))
p1, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "one two three message", "", model.PostTypeDefault, creationDate, false)
require.NoError(t, err)
creationDate2 := model.GetMillisForTime(time.Date(2020, 03, 10, 12, 0, 0, 0, time.UTC))
p2, err := th.createPost(th.User2.Id, th.ChannelPrivate.Id, "two messages", "", model.PostTypeDefault, creationDate2, false)
require.NoError(t, err)
creationDate3 := model.GetMillisForTime(time.Date(2020, 03, 20, 12, 0, 0, 0, time.UTC))
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "two another message", "", model.PostTypeDefault, creationDate3, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
defer th.deleteUserPosts(th.User2.Id)
t.Run("Should search combining from user and in channel filters", func(t *testing.T) {
params := &model.SearchParams{
Terms: "two",
FromUsers: []string{th.User2.Id},
InChannels: []string{th.ChannelPrivate.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should search combining excluding users and in channel filters", func(t *testing.T) {
params := &model.SearchParams{
Terms: "two",
ExcludedUsers: []string{th.User2.Id},
InChannels: []string{th.ChannelPrivate.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search combining excluding dates and in channel filters", func(t *testing.T) {
params := &model.SearchParams{
Terms: "two",
ExcludedBeforeDate: "2020-03-09",
ExcludedAfterDate: "2020-03-11",
InChannels: []string{th.ChannelPrivate.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should search combining excluding dates and in channel filters", func(t *testing.T) {
params := &model.SearchParams{
Terms: "two",
AfterDate: "2020-03-11",
ExcludedChannels: []string{th.ChannelPrivate.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
}
func testSearchIgnoringStopWords(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "the search for a bunch of stop words", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "the objective is to avoid a bunch of stop words", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "in the a on to where you", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p4, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "where is the car?", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should avoid stop word 'the'", func(t *testing.T) {
params := &model.SearchParams{
Terms: "the search",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should avoid stop word 'a'", func(t *testing.T) {
params := &model.SearchParams{
Terms: "a avoid",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should avoid stop word 'in'", func(t *testing.T) {
params := &model.SearchParams{
Terms: "in where you",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Should avoid stop words 'where', 'is' and 'the'", func(t *testing.T) {
results, err := th.Store.Post().Search(th.Team.Id, th.User.Id, &model.SearchParams{Terms: "where is the car"})
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p4.Id, results.Posts)
})
t.Run("Should remove all terms and return empty list", func(t *testing.T) {
results, err := th.Store.Post().Search(th.Team.Id, th.User.Id, &model.SearchParams{Terms: "where is the"})
require.NoError(t, err)
require.Empty(t, results.Posts)
})
}
func testSupportStemming(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "search post", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching post", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "another post", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "search",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
}
func testSupportWildcards(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "search post", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "another post", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Simple wildcard-only search", func(t *testing.T) {
params := &model.SearchParams{
Terms: "search*",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Wildcard search with another term placed after", func(t *testing.T) {
params := &model.SearchParams{
Terms: "sear* post",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
}
func testNotSupportPrecedingWildcards(t *testing.T, th *SearchTestHelper) {
_, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "search post", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "searching post", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "another post", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "*earch",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 0)
}
func testSearchDiscardWildcardAlone(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "qwerty", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "qwertyjkl", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "qwerty *",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSupportTermsWithDash(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "search term-with-dash", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "searching term with dash", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search terms with dash", func(t *testing.T) {
params := &model.SearchParams{
Terms: "term-with-dash",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search terms with dash using quotes", func(t *testing.T) {
params := &model.SearchParams{
Terms: "\"term-with-dash\"",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
}
func testSupportTermsWithUnderscore(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "search term_with_underscore", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "searching term with underscore", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search terms with underscore", func(t *testing.T) {
params := &model.SearchParams{
Terms: "term_with_underscore",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search terms with underscore using quotes", func(t *testing.T) {
params := &model.SearchParams{
Terms: "\"term_with_underscore\"",
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
}
func testSearchOrExcludePostsWithHashtags(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "search post with #hashtag", "#hashtag", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching term with hashtag", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching term with", "#hashtag", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search terms with hashtags", func(t *testing.T) {
params := &model.SearchParams{
Terms: "#hashtag",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Should search hashtag terms without hashtag option", func(t *testing.T) {
params := &model.SearchParams{
Terms: "#hashtag",
IsHashtag: false,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
}
func testSearchHashtagWithMarkdown(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag", "#hashtag", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching term with `#hashtag`", "#hashtag", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching term with **#hashtag**", "#hashtag", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p4, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching term with ~~#hashtag~~", "#hashtag", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p5, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching term with _#hashtag_", "#hashtag", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "#hashtag",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 5)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
th.checkPostInSearchResults(t, p4.Id, results.Posts)
th.checkPostInSearchResults(t, p5.Id, results.Posts)
}
func testSearchWithMultipleHashtags(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag", "#hashtwo #hashone", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching term with `#hashtag`", "#hashtwo #hashthree", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should search posts with multiple hashtags", func(t *testing.T) {
params := &model.SearchParams{
Terms: "#hashone #hashtwo",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Should search posts with multiple hashtags using OR", func(t *testing.T) {
params := &model.SearchParams{
Terms: "#hashone #hashtwo",
IsHashtag: true,
OrTerms: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
}
func testSearchPostsWithDotsInHashtags(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag.dot", "#hashtag.dot", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "#hashtag.dot",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSearchHashtagCaseInsensitive(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #HaShTaG", "#HaShTaG", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag", "#hashtag", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #HASHTAG", "#HASHTAG", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Lower case hashtag", func(t *testing.T) {
params := &model.SearchParams{
Terms: "#hashtag",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 3)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Upper case hashtag", func(t *testing.T) {
params := &model.SearchParams{
Terms: "#HASHTAG",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 3)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Mixed case hashtag", func(t *testing.T) {
params := &model.SearchParams{
Terms: "#HaShTaG",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 3)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
}
func testSearchHashtagWithDash(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag-test", "#hashtag-test", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtagtest", "#hashtagtest", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "#hashtag-test",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSearchHashtagWithNumbers(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #h4sht4g", "#h4sht4g", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag", "#hashtag", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "#h4sht4g",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSearchHashtagWithDots(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag.test", "#hashtag.test", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtagtest", "#hashtagtest", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "#hashtag.test",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSearchHashtagWithUnderscores(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag_test", "#hashtag_test", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtagtest", "#hashtagtest", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "#hashtag_test",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSearchHashtagWithDashAndNumbers(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag-1-finals23", "#hashtag-1-finals23", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag-1-finals23", "#hashtag-1-finals23", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "searching hashtag #hashtag-finals20", "#hashtag-finals20", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{
Terms: "#hashtag-1-finals23",
IsHashtag: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
}
func testSearchShouldExcludeSystemMessages(t *testing.T, th *SearchTestHelper) {
_, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test system message one", "", model.PostTypeJoinChannel, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "test system message two", "", model.PostTypeLeaveChannel, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "test system message three", "", model.PostTypeLeaveTeam, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "test system message four", "", model.PostTypeAddToChannel, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "test system message five", "", model.PostTypeAddToTeam, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "test system message six", "", model.PostTypeHeaderChange, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "test system"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 0)
}
func testSearchShouldBeAbleToMatchByMentions(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test system @testuser", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test system testuser", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "test system #testuser", "#testuser", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "@testuser"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 3)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
}
func testSearchInDeletedOrArchivedChannels(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelDeleted.Id, "message in deleted channel", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message in regular channel", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p3, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "message in private channel", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Doesn't include posts in deleted channels", func(t *testing.T) {
params := &model.SearchParams{Terms: "message", IncludeDeletedChannels: false}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Include posts in deleted channels", func(t *testing.T) {
params := &model.SearchParams{Terms: "message", IncludeDeletedChannels: true}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 3)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Include posts in deleted channels using multiple terms", func(t *testing.T) {
params := &model.SearchParams{Terms: "message channel", IncludeDeletedChannels: true}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 3)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Include posts in deleted channels using multiple OR terms", func(t *testing.T) {
params := &model.SearchParams{
Terms: "message channel",
IncludeDeletedChannels: true,
OrTerms: true,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 3)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("All IncludeDeletedChannels params should have same value if multiple SearchParams provided", func(t *testing.T) {
params1 := &model.SearchParams{
Terms: "message channel",
IncludeDeletedChannels: true,
}
params2 := &model.SearchParams{
Terms: "#hashtag",
IncludeDeletedChannels: false,
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params1, params2}, th.User.Id, th.Team.Id, 0, 20)
require.Nil(t, results)
require.Error(t, err)
})
}
func testSearchTermsWithDashes(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message with-dash-term", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message with dash term", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Search for terms with dash", func(t *testing.T) {
params := &model.SearchParams{Terms: "with-dash-term"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Search for terms with quoted dash", func(t *testing.T) {
params := &model.SearchParams{Terms: "\"with-dash-term\""}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Search for multiple terms with one having dash", func(t *testing.T) {
params := &model.SearchParams{Terms: "with-dash-term message"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Search for multiple OR terms with one having dash", func(t *testing.T) {
params := &model.SearchParams{Terms: "with-dash-term message", OrTerms: true}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
}
func testSearchTermsWithDots(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message with.dots.term", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message with dots term", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Search for terms with dots", func(t *testing.T) {
params := &model.SearchParams{Terms: "with.dots.term"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Search for terms with quoted dots", func(t *testing.T) {
params := &model.SearchParams{Terms: "\"with.dots.term\""}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Search for multiple terms with one having dots", func(t *testing.T) {
params := &model.SearchParams{Terms: "with.dots.term message"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Search for multiple OR terms with one having dots", func(t *testing.T) {
params := &model.SearchParams{Terms: "with.dots.term message", OrTerms: true}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
}
func testSearchTermsWithUnderscores(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message with_underscores_term", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message with underscores term", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Search for terms with underscores", func(t *testing.T) {
params := &model.SearchParams{Terms: "with_underscores_term"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Search for terms with quoted underscores", func(t *testing.T) {
params := &model.SearchParams{Terms: "\"with_underscores_term\""}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Search for multiple terms with one having underscores", func(t *testing.T) {
params := &model.SearchParams{Terms: "with_underscores_term message"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
})
t.Run("Search for multiple OR terms with one having underscores", func(t *testing.T) {
params := &model.SearchParams{Terms: "with_underscores_term message", OrTerms: true}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
}
func testSearchBotAccountsPosts(t *testing.T, th *SearchTestHelper) {
bot, err := th.createBot("testbot", "Test Bot", th.User.Id)
require.NoError(t, err)
defer th.deleteBotUser(bot.UserId)
err = th.addUserToTeams(model.UserFromBot(bot), []string{th.Team.Id})
require.NoError(t, err)
p1, err := th.createPost(bot.UserId, th.ChannelBasic.Id, "bot test message", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(bot.UserId, th.ChannelPrivate.Id, "bot test message in private", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(bot.UserId)
params := &model.SearchParams{Terms: "bot"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
}
func testSupportStemmingAndWildcards(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "approve", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "approved", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p3, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "approvedz", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should stem appr", func(t *testing.T) {
params := &model.SearchParams{Terms: "appr*"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 3)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Should stem approve", func(t *testing.T) {
params := &model.SearchParams{Terms: "approve*"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
}
func testSupportWildcardOutsideQuotes(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "hello world", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p2, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "hell or heaven", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
t.Run("Should return results without quotes", func(t *testing.T) {
params := &model.SearchParams{Terms: "hell*"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
t.Run("Should return just one result with quotes", func(t *testing.T) {
params := &model.SearchParams{Terms: "\"hell\"*"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p2.Id, results.Posts)
})
}
func testHashtagSearchShouldSupportThreeOrMoreCharacters(t *testing.T, th *SearchTestHelper) {
_, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "one char hashtag #1", "#1", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User.Id, th.ChannelPrivate.Id, "two chars hashtag #12", "#12", model.PostTypeDefault, 0, false)
require.NoError(t, err)
p3, err := th.createPost(th.User.Id, th.ChannelPrivate.Id, "three chars hashtag #123", "#123", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "#123", IsHashtag: true}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
}
func testSlashShouldNotBeCharSeparator(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "alpha/beta gamma, theta", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "gamma"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
params = &model.SearchParams{Terms: "beta"}
results, err = th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
params = &model.SearchParams{Terms: "alpha"}
results, err = th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testSupportSearchInComments(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message test@test.com", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
r1, err := th.createReply(th.User.Id, "reply check", "", p1, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "reply"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, r1.Id, results.Posts)
}
func testSupportSearchTermsWithinLinks(t *testing.T, th *SearchTestHelper) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message with link http://www.wikipedia.org/dolphins", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "wikipedia"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
}
func testShouldNotReturnLinksEmbeddedInMarkdown(t *testing.T, th *SearchTestHelper) {
_, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message with link [here](http://www.wikipedia.org/dolphins)", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
params := &model.SearchParams{Terms: "wikipedia"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 0)
}
func testSearchAcrossTeams(t *testing.T, th *SearchTestHelper) {
err := th.addUserToChannels(th.User, []string{th.ChannelAnotherTeam.Id})
require.NoError(t, err)
defer th.Store.Channel().RemoveMember(th.Context, th.ChannelAnotherTeam.Id, th.User.Id)
_, err = th.createPost(th.User.Id, th.ChannelAnotherTeam.Id, "text to search", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
_, err = th.createPost(th.User.Id, th.ChannelBasic.Id, "text to search", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
params := &model.SearchParams{Terms: "search"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, "", 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 2)
}
func testSearchAcrossTeamsWithFromFilter(t *testing.T, th *SearchTestHelper) {
err := th.addUserToChannels(th.User, []string{th.ChannelAnotherTeam.Id})
require.NoError(t, err)
defer th.Store.Channel().RemoveMember(th.Context, th.ChannelAnotherTeam.Id, th.User.Id)
err = th.addUserToChannels(th.User2, []string{th.ChannelAnotherTeam.Id})
require.NoError(t, err)
defer th.Store.Channel().RemoveMember(th.Context, th.ChannelAnotherTeam.Id, th.User2.Id)
// Create posts from different users in different teams
p1, err := th.createPost(th.User.Id, th.ChannelAnotherTeam.Id, "cross team search test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User.Id)
_, err = th.createPost(th.User2.Id, th.ChannelAnotherTeam.Id, "cross team search test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
defer th.deleteUserPosts(th.User2.Id)
p3, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "cross team search test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createPost(th.User2.Id, th.ChannelBasic.Id, "cross team search test", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
t.Run("Cross-team search with from filter should work", func(t *testing.T) {
// Cross-team search with from filter (empty teamId)
params := &model.SearchParams{
Terms: "search test",
FromUsers: []string{th.User.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, "", 0, 20)
require.NoError(t, err)
// Should find both posts from th.User across teams
require.Len(t, results.Posts, 2)
th.checkPostInSearchResults(t, p1.Id, results.Posts)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
t.Run("Team-scoped search with from filter should still work", func(t *testing.T) {
// Team-scoped search with from filter (specific teamId)
params := &model.SearchParams{
Terms: "search test",
FromUsers: []string{th.User.Id},
}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
// Should find only the post from th.User in th.Team
require.Len(t, results.Posts, 1)
th.checkPostInSearchResults(t, p3.Id, results.Posts)
})
}
func testSearchPostDeleted(t *testing.T, th *SearchTestHelper) {
t.Run("Search for soft deleted post", func(t *testing.T) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message to delete", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
err = th.Store.Post().Delete(th.Context, p1.Id, p1.UpdateAt, th.User.Id)
require.NoError(t, err)
params := &model.SearchParams{Terms: "message to delete"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 0)
})
t.Run("Search for hard deleted post", func(t *testing.T) {
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message to delete", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
err = th.Store.Post().PermanentDelete(th.Context, p2.Id)
require.NoError(t, err)
params := &model.SearchParams{Terms: "message to delete"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 0)
})
}