From ed34468996e6906003c9b3cab0d21ac121ec553f Mon Sep 17 00:00:00 2001 From: Farhan Munshi <3207297+fmunshi@users.noreply.github.com> Date: Thu, 23 Jul 2020 10:46:33 -0400 Subject: [PATCH] [MM-25990] Add filters to search all channels (#15009) * MM-25990 Add filters to search all channels endpoint and clean up tests * Add deleted filter * Remove redundant private public query --- api4/channel.go | 20 ++- api4/channel_test.go | 249 +++++++++++++++++++------------ app/channel.go | 16 +- model/channel.go | 18 ++- model/channel_search.go | 17 ++- store/sqlstore/channel_store.go | 32 +++- store/store.go | 16 +- store/storetest/channel_store.go | 61 +++++--- 8 files changed, 278 insertions(+), 151 deletions(-) diff --git a/api4/channel.go b/api4/channel.go index d4efc4c62b..02a0d90021 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -999,12 +999,20 @@ func searchAllChannels(c *Context, w http.ResponseWriter, r *http.Request) { return } includeDeleted, _ := strconv.ParseBool(r.URL.Query().Get("include_deleted")) + includeDeleted = includeDeleted || props.IncludeDeleted + opts := model.ChannelSearchOpts{ - NotAssociatedToGroup: props.NotAssociatedToGroup, - ExcludeDefaultChannels: props.ExcludeDefaultChannels, - IncludeDeleted: includeDeleted, - Page: props.Page, - PerPage: props.PerPage, + NotAssociatedToGroup: props.NotAssociatedToGroup, + ExcludeDefaultChannels: props.ExcludeDefaultChannels, + TeamIds: props.TeamIds, + GroupConstrained: props.GroupConstrained, + ExcludeGroupConstrained: props.ExcludeGroupConstrained, + Public: props.Public, + Private: props.Private, + IncludeDeleted: includeDeleted, + Deleted: props.Deleted, + Page: props.Page, + PerPage: props.PerPage, } channels, totalCount, appErr := c.App.SearchAllChannels(props.Term, opts) @@ -1014,9 +1022,7 @@ func searchAllChannels(c *Context, w http.ResponseWriter, r *http.Request) { } // Don't fill in channels props, since unused by client and potentially expensive. - var payload []byte - if props.Page != nil && props.PerPage != nil { data := model.ChannelsWithCount{Channels: channels, TotalCount: totalCount} payload = data.ToJson() diff --git a/api4/channel_test.go b/api4/channel_test.go index 4a2ab35504..3a31e84880 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -1227,109 +1227,166 @@ func TestSearchAllChannels(t *testing.T) { defer th.TearDown() Client := th.Client - channel := &model.Channel{ - DisplayName: "FOOBARDISPLAYNAME", + openChannel, chanErr := th.SystemAdminClient.CreateChannel(&model.Channel{ + DisplayName: "SearchAllChannels-FOOBARDISPLAYNAME", Name: "whatever", Type: model.CHANNEL_OPEN, TeamId: th.BasicTeam.Id, + }) + CheckNoError(t, chanErr) + + privateChannel, privErr := th.SystemAdminClient.CreateChannel(&model.Channel{ + DisplayName: "SearchAllChannels-private1", + Name: "private1", + Type: model.CHANNEL_PRIVATE, + TeamId: th.BasicTeam.Id, + }) + CheckNoError(t, privErr) + + team := th.CreateTeam() + groupConstrainedChannel, groupErr := th.SystemAdminClient.CreateChannel(&model.Channel{ + DisplayName: "SearchAllChannels-groupConstrained-1", + Name: "groupconstrained1", + Type: model.CHANNEL_PRIVATE, + GroupConstrained: model.NewBool(true), + TeamId: team.Id, + }) + CheckNoError(t, groupErr) + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.TeamSettings.ExperimentalViewArchivedChannels = true + }) + + testCases := []struct { + Description string + Search *model.ChannelSearch + ExpectedChannelIds []string + }{ + { + "Middle of word search", + &model.ChannelSearch{Term: "bardisplay"}, + []string{openChannel.Id}, + }, + { + "Prefix search", + &model.ChannelSearch{Term: "SearchAllChannels-foobar"}, + []string{openChannel.Id}, + }, + { + "Suffix search", + &model.ChannelSearch{Term: "displayname"}, + []string{openChannel.Id}, + }, + { + "Name search", + &model.ChannelSearch{Term: "what"}, + []string{openChannel.Id}, + }, + { + "Name suffix search", + &model.ChannelSearch{Term: "ever"}, + []string{openChannel.Id}, + }, + { + "Basic channel name middle of word search", + &model.ChannelSearch{Term: th.BasicChannel.Name[2:14]}, + []string{th.BasicChannel.Id}, + }, + { + "Upper case search", + &model.ChannelSearch{Term: strings.ToUpper(th.BasicChannel.Name)}, + []string{th.BasicChannel.Id}, + }, + { + "Mixed case search", + &model.ChannelSearch{Term: th.BasicChannel.Name[0:2] + strings.ToUpper(th.BasicChannel.Name[2:5]) + th.BasicChannel.Name[5:]}, + []string{th.BasicChannel.Id}, + }, + { + "Non mixed case search", + &model.ChannelSearch{Term: th.BasicChannel.Name}, + []string{th.BasicChannel.Id}, + }, + { + "Search private channel name", + &model.ChannelSearch{Term: th.BasicPrivateChannel.Name}, + []string{th.BasicPrivateChannel.Id}, + }, + { + "Search with private channel filter", + &model.ChannelSearch{Private: true}, + []string{th.BasicPrivateChannel.Id, th.BasicPrivateChannel2.Id, privateChannel.Id, groupConstrainedChannel.Id}, + }, + { + "Search with public channel filter", + &model.ChannelSearch{Term: "SearchAllChannels", Public: true}, + []string{openChannel.Id}, + }, + { + "Search with private channel filter", + &model.ChannelSearch{Term: "SearchAllChannels", Private: true}, + []string{privateChannel.Id, groupConstrainedChannel.Id}, + }, + { + "Search with teamIds channel filter", + &model.ChannelSearch{Term: "SearchAllChannels", TeamIds: []string{th.BasicTeam.Id}}, + []string{openChannel.Id, privateChannel.Id}, + }, + { + "Search with deleted without IncludeDeleted filter", + &model.ChannelSearch{Term: th.BasicDeletedChannel.Name}, + []string{}, + }, + { + "Search with deleted IncludeDeleted filter", + &model.ChannelSearch{Term: th.BasicDeletedChannel.Name, IncludeDeleted: true}, + []string{th.BasicDeletedChannel.Id}, + }, + { + "Search with deleted IncludeDeleted filter", + &model.ChannelSearch{Term: th.BasicDeletedChannel.Name, IncludeDeleted: true}, + []string{th.BasicDeletedChannel.Id}, + }, + { + "Search with deleted Deleted filter and empty term", + &model.ChannelSearch{Term: "", Deleted: true}, + []string{th.BasicDeletedChannel.Id}, + }, + { + "Search for group constrained", + &model.ChannelSearch{Term: "SearchAllChannels", GroupConstrained: true}, + []string{groupConstrainedChannel.Id}, + }, + { + "Search for group constrained and public", + &model.ChannelSearch{Term: "SearchAllChannels", GroupConstrained: true, Public: true}, + []string{}, + }, + { + "Search for exclude group constrained", + &model.ChannelSearch{Term: "SearchAllChannels", ExcludeGroupConstrained: true}, + []string{openChannel.Id, privateChannel.Id}, + }, + } + for _, testCase := range testCases { + t.Run(testCase.Description, func(t *testing.T) { + channels, resp := th.SystemAdminClient.SearchAllChannels(testCase.Search) + CheckNoError(t, resp) + assert.Equal(t, len(testCase.ExpectedChannelIds), len(*channels)) + actualChannelIds := []string{} + for _, channelWithTeamData := range *channels { + actualChannelIds = append(actualChannelIds, channelWithTeamData.Channel.Id) + } + assert.ElementsMatch(t, testCase.ExpectedChannelIds, actualChannelIds) + }) } - // Testing Mixed Case (Ensure we get results for partial word searches) - - // Search by using display name - foobarchannel, err := th.SystemAdminClient.CreateChannel(channel) - CheckNoError(t, err) - - search := &model.ChannelSearch{Term: "bardisplay"} - - channels, resp := th.SystemAdminClient.SearchAllChannels(search) + // Searching with no terms returns all default channels + allChannels, resp := th.SystemAdminClient.SearchAllChannels(&model.ChannelSearch{Term: ""}) CheckNoError(t, resp) + assert.True(t, len(*allChannels) >= 3) - assert.Len(t, *channels, 1) - assert.Equal(t, foobarchannel.Id, (*channels)[0].Id) - - search = &model.ChannelSearch{Term: "foobar"} - - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - - assert.Len(t, *channels, 1) - assert.Equal(t, foobarchannel.Id, (*channels)[0].Id) - - search = &model.ChannelSearch{Term: "displayname"} - - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - - assert.Len(t, *channels, 1) - assert.Equal(t, foobarchannel.Id, (*channels)[0].Id) - - // Search by using Name - search = &model.ChannelSearch{Term: "what"} - - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - - assert.Len(t, *channels, 1) - assert.Equal(t, foobarchannel.Id, (*channels)[0].Id) - - search = &model.ChannelSearch{Term: "ever"} - - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - - // Seach by partial word search and testing case sensitivty - assert.Len(t, *channels, 1) - assert.Equal(t, foobarchannel.Id, (*channels)[0].Id) - - search = &model.ChannelSearch{Term: th.BasicChannel.Name[2:14]} - - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - - assert.Len(t, *channels, 1) - assert.Equal(t, th.BasicChannel.Id, (*channels)[0].Id) - - search = &model.ChannelSearch{Term: strings.ToUpper(th.BasicChannel.Name)} - - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - - assert.Len(t, *channels, 1) - assert.Equal(t, th.BasicChannel.Id, (*channels)[0].Id) - - search = &model.ChannelSearch{Term: th.BasicChannel.Name[0:2] + strings.ToUpper(th.BasicChannel.Name[2:5]) + th.BasicChannel.Name[5:]} - - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - - assert.Len(t, *channels, 1) - assert.Equal(t, th.BasicChannel.Id, (*channels)[0].Id) - - // Testing Non-Mixed Case test cases - search = &model.ChannelSearch{Term: th.BasicChannel.Name} - - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - - assert.Len(t, *channels, 1) - assert.Equal(t, th.BasicChannel.Id, (*channels)[0].Id) - - search.Term = th.BasicPrivateChannel.Name - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - - assert.Len(t, *channels, 1) - assert.Equal(t, th.BasicPrivateChannel.Id, (*channels)[0].Id) - - search.Term = "" - channels, resp = th.SystemAdminClient.SearchAllChannels(search) - CheckNoError(t, resp) - // At least, all the not-deleted channels created during the InitBasic - assert.True(t, len(*channels) >= 3) - - search.Term = th.BasicChannel.Name - _, resp = Client.SearchAllChannels(search) + _, resp = Client.SearchAllChannels(&model.ChannelSearch{Term: ""}) CheckForbiddenStatus(t, resp) } diff --git a/app/channel.go b/app/channel.go index 3636e31ee9..fcc574c0a3 100644 --- a/app/channel.go +++ b/app/channel.go @@ -2192,11 +2192,17 @@ func (a *App) SearchAllChannels(term string, opts model.ChannelSearchOpts) (*mod opts.ExcludeChannelNames = a.DefaultChannelNames() } storeOpts := store.ChannelSearchOpts{ - ExcludeChannelNames: opts.ExcludeChannelNames, - NotAssociatedToGroup: opts.NotAssociatedToGroup, - IncludeDeleted: opts.IncludeDeleted, - Page: opts.Page, - PerPage: opts.PerPage, + ExcludeChannelNames: opts.ExcludeChannelNames, + NotAssociatedToGroup: opts.NotAssociatedToGroup, + IncludeDeleted: opts.IncludeDeleted, + Deleted: opts.Deleted, + TeamIds: opts.TeamIds, + GroupConstrained: opts.GroupConstrained, + ExcludeGroupConstrained: opts.ExcludeGroupConstrained, + Public: opts.Public, + Private: opts.Private, + Page: opts.Page, + PerPage: opts.PerPage, } term = strings.TrimSpace(term) diff --git a/model/channel.go b/model/channel.go index 6a84b355bc..282271adc8 100644 --- a/model/channel.go +++ b/model/channel.go @@ -120,12 +120,18 @@ type ChannelModeratedRolesPatch struct { // PerPage number of results per page, if paginated. // type ChannelSearchOpts struct { - NotAssociatedToGroup string - ExcludeDefaultChannels bool - IncludeDeleted bool - ExcludeChannelNames []string - Page *int - PerPage *int + NotAssociatedToGroup string + ExcludeDefaultChannels bool + IncludeDeleted bool + Deleted bool + ExcludeChannelNames []string + TeamIds []string + GroupConstrained bool + ExcludeGroupConstrained bool + Public bool + Private bool + Page *int + PerPage *int } type ChannelMemberCountByGroup struct { diff --git a/model/channel_search.go b/model/channel_search.go index 2e994227f8..87fd3aef6d 100644 --- a/model/channel_search.go +++ b/model/channel_search.go @@ -11,11 +11,18 @@ import ( const CHANNEL_SEARCH_DEFAULT_LIMIT = 50 type ChannelSearch struct { - Term string `json:"term"` - ExcludeDefaultChannels bool `json:"exclude_default_channels"` - NotAssociatedToGroup string `json:"not_associated_to_group"` - Page *int `json:"page,omitempty"` - PerPage *int `json:"per_page,omitempty"` + Term string `json:"term"` + ExcludeDefaultChannels bool `json:"exclude_default_channels"` + NotAssociatedToGroup string `json:"not_associated_to_group"` + TeamIds []string `json:"team_ids"` + GroupConstrained bool `json:"group_constrained"` + ExcludeGroupConstrained bool `json:"exclude_group_constrained"` + Public bool `json:"public"` + Private bool `json:"private"` + IncludeDeleted bool `json:"include_deleted"` + Deleted bool `json:"deleted"` + Page *int `json:"page,omitempty"` + PerPage *int `json:"per_page,omitempty"` } // ToJson convert a Channel to a json string diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 5987579f90..d4429e16ee 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -2816,8 +2816,7 @@ func (s SqlChannelStore) channelSearchQuery(term string, opts store.ChannelSearc query := s.getQueryBuilder(). Select(selectStr). From("Channels AS c"). - Join("Teams AS t ON t.Id = c.TeamId"). - Where(sq.Eq{"c.Type": []string{model.CHANNEL_PRIVATE, model.CHANNEL_OPEN}}) + Join("Teams AS t ON t.Id = c.TeamId") // don't bother ordering or limiting if we're just getting the count if !countQuery { @@ -2825,8 +2824,9 @@ func (s SqlChannelStore) channelSearchQuery(term string, opts store.ChannelSearc OrderBy("c.DisplayName, t.DisplayName"). Limit(uint64(limit)) } - - if !opts.IncludeDeleted { + if opts.Deleted { + query = query.Where(sq.NotEq{"c.DeleteAt": int(0)}) + } else if !opts.IncludeDeleted { query = query.Where(sq.Eq{"c.DeleteAt": int(0)}) } @@ -2854,6 +2854,30 @@ func (s SqlChannelStore) channelSearchQuery(term string, opts store.ChannelSearc query = query.Where("c.Id NOT IN (SELECT ChannelId FROM GroupChannels WHERE GroupChannels.GroupId = ? AND GroupChannels.DeleteAt = 0)", opts.NotAssociatedToGroup) } + if len(opts.TeamIds) > 0 { + query = query.Where(sq.Eq{"c.TeamId": opts.TeamIds}) + } + + if opts.GroupConstrained { + query = query.Where(sq.Eq{"c.GroupConstrained": true}) + } else if opts.ExcludeGroupConstrained { + query = query.Where(sq.Or{ + sq.NotEq{"c.GroupConstrained": true}, + sq.Eq{"c.GroupConstrained": nil}, + }) + } + + if opts.Public && !opts.Private { + query = query.Where(sq.Eq{"c.Type": model.CHANNEL_OPEN}) + } else if opts.Private && !opts.Public { + query = query.Where(sq.Eq{"c.Type": model.CHANNEL_PRIVATE}) + } else { + query = query.Where(sq.Or{ + sq.Eq{"c.Type": model.CHANNEL_OPEN}, + sq.Eq{"c.Type": model.CHANNEL_PRIVATE}, + }) + } + return query } diff --git a/store/store.go b/store/store.go index 32df563125..4e7b4c1619 100644 --- a/store/store.go +++ b/store/store.go @@ -750,11 +750,17 @@ type LinkMetadataStore interface { // PerPage number of results per page, if paginated. // type ChannelSearchOpts struct { - NotAssociatedToGroup string - IncludeDeleted bool - ExcludeChannelNames []string - Page *int - PerPage *int + NotAssociatedToGroup string + IncludeDeleted bool + Deleted bool + ExcludeChannelNames []string + TeamIds []string + GroupConstrained bool + ExcludeGroupConstrained bool + Public bool + Private bool + Page *int + PerPage *int } func (c *ChannelSearchOpts) IsPaginated() bool { diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index a8084467cd..2376053b50 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -5208,7 +5208,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o1 := model.Channel{ TeamId: t1.Id, - DisplayName: "ChannelA", + DisplayName: "A1 ChannelA", Name: "zz" + model.NewId() + "b", Type: model.CHANNEL_OPEN, } @@ -5217,7 +5217,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o2 := model.Channel{ TeamId: t2.Id, - DisplayName: "ChannelA", + DisplayName: "A2 ChannelA", Name: "zz" + model.NewId() + "b", Type: model.CHANNEL_OPEN, } @@ -5250,7 +5250,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o3 := model.Channel{ TeamId: t1.Id, - DisplayName: "ChannelA (alternate)", + DisplayName: "A3 ChannelA (alternate)", Name: "zz" + model.NewId() + "b", Type: model.CHANNEL_OPEN, } @@ -5259,7 +5259,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o4 := model.Channel{ TeamId: t1.Id, - DisplayName: "ChannelB", + DisplayName: "A4 ChannelB", Name: "zz" + model.NewId() + "b", Type: model.CHANNEL_PRIVATE, } @@ -5267,17 +5267,18 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { require.Nil(t, nErr) o5 := model.Channel{ - TeamId: t1.Id, - DisplayName: "ChannelC", - Name: "zz" + model.NewId() + "b", - Type: model.CHANNEL_PRIVATE, + TeamId: t1.Id, + DisplayName: "A5 ChannelC", + Name: "zz" + model.NewId() + "b", + Type: model.CHANNEL_PRIVATE, + GroupConstrained: model.NewBool(true), } _, nErr = ss.Channel().Save(&o5, -1) require.Nil(t, nErr) o6 := model.Channel{ TeamId: t1.Id, - DisplayName: "Off-Topic", + DisplayName: "A6 Off-Topic", Name: "off-topic", Type: model.CHANNEL_OPEN, } @@ -5286,7 +5287,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o7 := model.Channel{ TeamId: t1.Id, - DisplayName: "Off-Set", + DisplayName: "A7 Off-Set", Name: "off-set", Type: model.CHANNEL_OPEN, } @@ -5307,7 +5308,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o8 := model.Channel{ TeamId: t1.Id, - DisplayName: "Off-Limit", + DisplayName: "A8 Off-Limit", Name: "off-limit", Type: model.CHANNEL_PRIVATE, } @@ -5316,7 +5317,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o9 := model.Channel{ TeamId: t1.Id, - DisplayName: "Town Square", + DisplayName: "A9 Town Square", Name: "town-square", Type: model.CHANNEL_OPEN, } @@ -5325,7 +5326,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o10 := model.Channel{ TeamId: t1.Id, - DisplayName: "The", + DisplayName: "B10 The", Name: "the", Type: model.CHANNEL_OPEN, } @@ -5334,7 +5335,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o11 := model.Channel{ TeamId: t1.Id, - DisplayName: "Native Mobile Apps", + DisplayName: "B11 Native Mobile Apps", Name: "native-mobile-apps", Type: model.CHANNEL_OPEN, } @@ -5343,7 +5344,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o12 := model.Channel{ TeamId: t1.Id, - DisplayName: "ChannelZ", + DisplayName: "B12 ChannelZ", Purpose: "This can now be searchable!", Name: "with-purpose", Type: model.CHANNEL_OPEN, @@ -5353,7 +5354,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o13 := model.Channel{ TeamId: t1.Id, - DisplayName: "ChannelA (deleted)", + DisplayName: "B13 ChannelA (deleted)", Name: model.NewId(), Type: model.CHANNEL_OPEN, } @@ -5367,7 +5368,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { o14 := model.Channel{ TeamId: t2.Id, - DisplayName: "FOOBARDISPLAYNAME", + DisplayName: "B14 FOOBARDISPLAYNAME", Name: "whatever", Type: model.CHANNEL_OPEN, } @@ -5387,20 +5388,34 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { {"Search FooBar by name2", "ever", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o14}, 1}, {"ChannelA", "ChannelA", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o1, &o2, &o3}, 0}, {"ChannelA, include deleted", "ChannelA", store.ChannelSearchOpts{IncludeDeleted: true}, &model.ChannelList{&o1, &o2, &o3, &o13}, 0}, - {"empty string", "", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o1, &o2, &o3, &o4, &o5, &o12, &o14, &o11, &o8, &o7, &o6, &o10, &o9}, 0}, + {"empty string", "", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o1, &o2, &o3, &o4, &o5, &o6, &o7, &o8, &o9, &o10, &o11, &o12, &o14}, 0}, {"no matches", "blargh", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{}, 0}, - {"prefix", "off-", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o8, &o7, &o6}, 0}, + {"prefix", "off-", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o6, &o7, &o8}, 0}, {"full match with dash", "off-topic", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o6}, 0}, {"town square", "town square", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o9}, 0}, {"the in name", "the", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o10}, 0}, {"Mobile", "Mobile", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o11}, 0}, {"search purpose", "now searchable", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o12}, 0}, {"pipe ignored", "town square |", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o9}, 0}, - {"exclude defaults search 'off'", "off-", store.ChannelSearchOpts{IncludeDeleted: false, ExcludeChannelNames: []string{"off-topic"}}, &model.ChannelList{&o8, &o7}, 0}, + {"exclude defaults search 'off'", "off-", store.ChannelSearchOpts{IncludeDeleted: false, ExcludeChannelNames: []string{"off-topic"}}, &model.ChannelList{&o7, &o8}, 0}, {"exclude defaults search 'town'", "town", store.ChannelSearchOpts{IncludeDeleted: false, ExcludeChannelNames: []string{"town-square"}}, &model.ChannelList{}, 0}, - {"exclude by group association", "off-", store.ChannelSearchOpts{IncludeDeleted: false, NotAssociatedToGroup: group.Id}, &model.ChannelList{&o8, &o6}, 0}, - {"paginate includes count", "off-", store.ChannelSearchOpts{IncludeDeleted: false, PerPage: model.NewInt(100)}, &model.ChannelList{&o8, &o7, &o6}, 3}, - {"paginate, page 2 correct entries and count", "off-", store.ChannelSearchOpts{IncludeDeleted: false, PerPage: model.NewInt(2), Page: model.NewInt(1)}, &model.ChannelList{&o6}, 3}, + {"exclude by group association", "off-", store.ChannelSearchOpts{IncludeDeleted: false, NotAssociatedToGroup: group.Id}, &model.ChannelList{&o6, &o8}, 0}, + {"paginate includes count", "off-", store.ChannelSearchOpts{IncludeDeleted: false, PerPage: model.NewInt(100)}, &model.ChannelList{&o6, &o7, &o8}, 3}, + {"paginate, page 2 correct entries and count", "off-", store.ChannelSearchOpts{IncludeDeleted: false, PerPage: model.NewInt(2), Page: model.NewInt(1)}, &model.ChannelList{&o8}, 3}, + {"Filter private", "", store.ChannelSearchOpts{IncludeDeleted: false, Private: true}, &model.ChannelList{&o4, &o5, &o8}, 3}, + {"Filter public", "", store.ChannelSearchOpts{IncludeDeleted: false, Public: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o1, &o2, &o3, &o6, &o7}, 10}, + {"Filter public and private", "", store.ChannelSearchOpts{IncludeDeleted: false, Public: true, Private: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o1, &o2, &o3, &o4, &o5}, 13}, + {"Filter public and private and include deleted", "", store.ChannelSearchOpts{IncludeDeleted: true, Public: true, Private: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o1, &o2, &o3, &o4, &o5}, 14}, + {"Filter group constrained", "", store.ChannelSearchOpts{IncludeDeleted: false, GroupConstrained: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o5}, 1}, + {"Filter exclude group constrained and include deleted", "", store.ChannelSearchOpts{IncludeDeleted: true, ExcludeGroupConstrained: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o1, &o2, &o3, &o4, &o6}, 13}, + {"Filter private and exclude group constrained", "", store.ChannelSearchOpts{IncludeDeleted: false, ExcludeGroupConstrained: true, Private: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o4, &o8}, 2}, + {"Filter team 2", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t2.Id}, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o2, &o14}, 2}, + {"Filter team 2, private", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t2.Id}, Private: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{}, 0}, + {"Filter team 1 and team 2, private", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t1.Id, t2.Id}, Private: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o4, &o5, &o8}, 3}, + {"Filter team 1 and team 2, public and private", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t1.Id, t2.Id}, Public: true, Private: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o1, &o2, &o3, &o4, &o5}, 13}, + {"Filter team 1 and team 2, public and private and group constrained", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t1.Id, t2.Id}, Public: true, Private: true, GroupConstrained: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o5}, 1}, + {"Filter team 1 and team 2, public and private and exclude group constrained", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t1.Id, t2.Id}, Public: true, Private: true, ExcludeGroupConstrained: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o1, &o2, &o3, &o4, &o6}, 12}, + {"Filter deleted returns only deleted channels", "", store.ChannelSearchOpts{Deleted: true, Page: model.NewInt(0), PerPage: model.NewInt(5)}, &model.ChannelList{&o13}, 1}, } for _, testCase := range testCases {