From e96c21d342f669c46b3a172014958e6ba91a0496 Mon Sep 17 00:00:00 2001 From: Keepers Date: Fri, 25 Aug 2023 12:09:20 -0600 Subject: [PATCH] add channels info filters to selectors (#4093) Adds groups channel message filters to group selectors. Includes scopes for message creation time, last response time, and message created by. --- #### Does this PR need a docs update or release note? - [x] :no_entry: No #### Type of change - [x] :sunflower: Feature #### Issue(s) * #3989 #### Test Plan - [x] :zap: Unit test - [x] :green_heart: E2E --- src/pkg/backup/details/groups.go | 2 +- src/pkg/selectors/exchange.go | 4 +- src/pkg/selectors/groups.go | 112 +++++- src/pkg/selectors/groups_test.go | 597 +++++++++++++++++-------------- 4 files changed, 430 insertions(+), 285 deletions(-) diff --git a/src/pkg/backup/details/groups.go b/src/pkg/backup/details/groups.go index 1b67dac4f..92281b592 100644 --- a/src/pkg/backup/details/groups.go +++ b/src/pkg/backup/details/groups.go @@ -49,7 +49,7 @@ type GroupsInfo struct { // Channels Specific ChannelName string `json:"channelName,omitempty"` ChannelID string `json:"channelID,omitempty"` - LastResponseAt time.Time `json:"lastResponseAt,omitempty"` + LastReplyAt time.Time `json:"lastResponseAt,omitempty"` MessageCreator string `json:"messageCreator,omitempty"` MessagePreview string `json:"messagePreview,omitempty"` ReplyCount int `json:"replyCount,omitempty"` diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index 9a6b87638..68f45263c 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -335,7 +335,7 @@ func (s *exchange) AllData() []ExchangeScope { } // ------------------- -// Info Factories +// ItemInfo Factories // ContactName produces one or more exchange contact name info scopes. // Matches any contact whose name contains the provided string. @@ -352,7 +352,7 @@ func (sr *ExchangeRestore) ContactName(senderID string) []ExchangeScope { } } -// EventSubject produces one or more exchange event subject info scopes. +// EventOrganizer produces one or more exchange event subject info scopes. // Matches any event where the event subject contains one of the provided strings. // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] diff --git a/src/pkg/selectors/groups.go b/src/pkg/selectors/groups.go index 6f1bd1d74..a6e186da5 100644 --- a/src/pkg/selectors/groups.go +++ b/src/pkg/selectors/groups.go @@ -6,6 +6,7 @@ import ( "github.com/alcionai/clues" + "github.com/alcionai/corso/src/internal/common/dttm" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/identity" "github.com/alcionai/corso/src/pkg/fault" @@ -242,7 +243,7 @@ func (s *groups) Channel(channel string) []GroupsScope { // If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice is empty, it defaults to [selectors.None] -func (s *sharePoint) ChannelMessages(channels, messages []string, opts ...option) []GroupsScope { +func (s *groups) ChannelMessages(channels, messages []string, opts ...option) []GroupsScope { var ( scopes = []GroupsScope{} os = append([]option{pathComparator()}, opts...) @@ -309,7 +310,76 @@ func (s *groups) LibraryItems(libraries, items []string, opts ...option) []Group // ------------------- // ItemInfo Factories -// TODO +// MessageCreator produces one or more groups channelMessage info scopes. +// Matches any channel message created by the specified user. +// If any slice contains selectors.Any, that slice is reduced to [selectors.Any] +// If any slice contains selectors.None, that slice is reduced to [selectors.None] +// If any slice is empty, it defaults to [selectors.None] +func (s *GroupsRestore) MessageCreator(creator string) []GroupsScope { + return []GroupsScope{ + makeInfoScope[GroupsScope]( + GroupsChannelMessage, + GroupsInfoChannelMessageCreator, + []string{creator}, + filters.In), + } +} + +// MessageCreatedAfter produces a channel message created-after info scope. +// Matches any message where the creation time is after the timestring. +// If the input equals selectors.Any, the scope will match all times. +// If the input is empty or selectors.None, the scope will always fail comparisons. +func (s *GroupsRestore) MessageCreatedAfter(timeStrings string) []GroupsScope { + return []GroupsScope{ + makeInfoScope[GroupsScope]( + GroupsChannelMessage, + GroupsInfoChannelMessageCreatedAfter, + []string{timeStrings}, + filters.Less), + } +} + +// MessageCreatedBefore produces a channel message created-before info scope. +// Matches any message where the creation time is after the timestring. +// If the input equals selectors.Any, the scope will match all times. +// If the input is empty or selectors.None, the scope will always fail comparisons. +func (s *GroupsRestore) MessageCreatedBefore(timeStrings string) []GroupsScope { + return []GroupsScope{ + makeInfoScope[GroupsScope]( + GroupsChannelMessage, + GroupsInfoChannelMessageCreatedBefore, + []string{timeStrings}, + filters.Greater), + } +} + +// MessageLastReplyAfter produces a channel message last-response-after info scope. +// Matches any message where last response time is after the timestring. +// If the input equals selectors.Any, the scope will match all times. +// If the input is empty or selectors.None, the scope will always fail comparisons. +func (s *GroupsRestore) MessageLastReplyAfter(timeStrings string) []GroupsScope { + return []GroupsScope{ + makeInfoScope[GroupsScope]( + GroupsChannelMessage, + GroupsInfoChannelMessageLastReplyAfter, + []string{timeStrings}, + filters.Less), + } +} + +// MessageLastReplyBefore produces a channel message last-response-before info scope. +// Matches any message where last response time is after the timestring. +// If the input equals selectors.Any, the scope will match all times. +// If the input is empty or selectors.None, the scope will always fail comparisons. +func (s *GroupsRestore) MessageLastReplyBefore(timeStrings string) []GroupsScope { + return []GroupsScope{ + makeInfoScope[GroupsScope]( + GroupsChannelMessage, + GroupsInfoChannelMessageLastReplyBefore, + []string{timeStrings}, + filters.Greater), + } +} // --------------------------------------------------------------------------- // Categories @@ -334,9 +404,16 @@ const ( // details.itemInfo comparables - // channel drive selection + // channel and drive selection GroupsInfoSiteLibraryDrive groupsCategory = "GroupsInfoSiteLibraryDrive" GroupsInfoChannel groupsCategory = "GroupsInfoChannel" + + // data contained within details.ItemInfo + GroupsInfoChannelMessageCreatedAfter groupsCategory = "GroupsInfoChannelMessageCreatedAfter" + GroupsInfoChannelMessageCreatedBefore groupsCategory = "GroupsInfoChannelMessageCreatedBefore" + GroupsInfoChannelMessageCreator groupsCategory = "GroupsInfoChannelMessageCreator" + GroupsInfoChannelMessageLastReplyAfter groupsCategory = "GroupsInfoChannelMessageLastReplyAfter" + GroupsInfoChannelMessageLastReplyBefore groupsCategory = "GroupsInfoChannelMessageLastReplyBefore" ) // groupsLeafProperties describes common metadata of the leaf categories @@ -368,7 +445,9 @@ func (c groupsCategory) leafCat() categorizer { switch c { // TODO: if channels ever contain more than one type of item, // we'll need to fix this up. - case GroupsChannel, GroupsChannelMessage: + case GroupsChannel, GroupsChannelMessage, + GroupsInfoChannelMessageCreatedAfter, GroupsInfoChannelMessageCreatedBefore, GroupsInfoChannelMessageCreator, + GroupsInfoChannelMessageLastReplyAfter, GroupsInfoChannelMessageLastReplyBefore: return GroupsChannelMessage case GroupsLibraryFolder, GroupsLibraryItem, GroupsInfoSiteLibraryDrive: return GroupsLibraryItem @@ -414,15 +493,15 @@ func (c groupsCategory) pathValues( rFld string ) + if ent.Groups == nil { + return nil, clues.New("no Groups ItemInfo in details") + } + switch c { case GroupsChannel, GroupsChannelMessage: folderCat, itemCat = GroupsChannel, GroupsChannelMessage rFld = ent.Groups.ParentPath case GroupsLibraryFolder, GroupsLibraryItem: - if ent.Groups == nil { - return nil, clues.New("no Groups ItemInfo in details") - } - folderCat, itemCat = GroupsLibraryFolder, GroupsLibraryItem rFld = ent.Groups.ParentPath default: @@ -591,8 +670,23 @@ func (s GroupsScope) matchesInfo(dii details.ItemInfo) bool { return matchesAny(s, GroupsInfoSiteLibraryDrive, ds) case GroupsInfoChannel: - ds := Any() + ds := []string{} + + if len(info.ChannelID) > 0 { + ds = append(ds, info.ChannelID) + } + + if len(info.ChannelName) > 0 { + ds = append(ds, info.ChannelName) + } + return matchesAny(s, GroupsInfoChannel, ds) + case GroupsInfoChannelMessageCreator: + i = info.MessageCreator + case GroupsInfoChannelMessageCreatedAfter, GroupsInfoChannelMessageCreatedBefore: + i = dttm.Format(info.Created) + case GroupsInfoChannelMessageLastReplyAfter, GroupsInfoChannelMessageLastReplyBefore: + i = dttm.Format(info.LastReplyAt) } return s.Matches(infoCat, i) diff --git a/src/pkg/selectors/groups_test.go b/src/pkg/selectors/groups_test.go index a0912a144..dc5b6cc09 100644 --- a/src/pkg/selectors/groups_test.go +++ b/src/pkg/selectors/groups_test.go @@ -1,15 +1,21 @@ package selectors import ( + "strings" "testing" + "time" "github.com/alcionai/clues" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "golang.org/x/exp/slices" + "github.com/alcionai/corso/src/internal/common/dttm" + odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts" "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/pkg/backup/details" + "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" ) @@ -55,211 +61,236 @@ func (suite *GroupsSelectorSuite) TestToGroupsRestore() { assert.NotZero(t, or.Scopes()) } -// TODO(rkeepers): implement -// func (suite *GroupsSelectorSuite) TestGroupsRestore_Reduce() { -// toRR := func(cat path.CategoryType, siteID string, folders []string, item string) string { -// folderElems := make([]string, 0, len(folders)) +func (suite *GroupsSelectorSuite) TestGroupsRestore_Reduce() { + toRR := func(cat path.CategoryType, midID string, folders []string, item string) string { + var ( + folderElems = make([]string, 0, len(folders)) + isDrive = cat == path.LibrariesCategory + ) -// for _, f := range folders { -// folderElems = append(folderElems, f+".d") -// } + for _, f := range folders { + if isDrive { + f = f + ".d" + } -// return stubRepoRef( -// path.GroupsService, -// cat, -// siteID, -// strings.Join(folderElems, "/"), -// item) -// } + folderElems = append(folderElems, f) + } -// var ( -// prefixElems = []string{ -// odConsts.DrivesPathDir, -// "drive!id", -// odConsts.RootPathDir, -// } -// itemElems1 = []string{"folderA", "folderB"} -// itemElems2 = []string{"folderA", "folderC"} -// itemElems3 = []string{"folderD", "folderE"} -// pairAC = "folderA/folderC" -// pairGH = "folderG/folderH" -// item = toRR( -// path.LibrariesCategory, -// "sid", -// append(slices.Clone(prefixElems), itemElems1...), -// "item") -// item2 = toRR( -// path.LibrariesCategory, -// "sid", -// append(slices.Clone(prefixElems), itemElems2...), -// "item2") -// item3 = toRR( -// path.LibrariesCategory, -// "sid", -// append(slices.Clone(prefixElems), itemElems3...), -// "item3") -// item4 = stubRepoRef(path.GroupsService, path.PagesCategory, "sid", pairGH, "item4") -// item5 = stubRepoRef(path.GroupsService, path.PagesCategory, "sid", pairGH, "item5") -// ) + return stubRepoRef( + path.GroupsService, + cat, + midID, + strings.Join(folderElems, "/"), + item) + } -// deets := &details.Details{ -// DetailsModel: details.DetailsModel{ -// Entries: []details.Entry{ -// { -// RepoRef: item, -// ItemRef: "item", -// LocationRef: strings.Join(append([]string{odConsts.RootPathDir}, itemElems1...), "/"), -// ItemInfo: details.ItemInfo{ -// Groups: &details.GroupsInfo{ -// ItemType: details.GroupsLibrary, -// ItemName: "itemName", -// ParentPath: strings.Join(itemElems1, "/"), -// }, -// }, -// }, -// { -// RepoRef: item2, -// LocationRef: strings.Join(append([]string{odConsts.RootPathDir}, itemElems2...), "/"), -// // ItemRef intentionally blank to test fallback case -// ItemInfo: details.ItemInfo{ -// Groups: &details.GroupsInfo{ -// ItemType: details.GroupsLibrary, -// ItemName: "itemName2", -// ParentPath: strings.Join(itemElems2, "/"), -// }, -// }, -// }, -// { -// RepoRef: item3, -// ItemRef: "item3", -// LocationRef: strings.Join(append([]string{odConsts.RootPathDir}, itemElems3...), "/"), -// ItemInfo: details.ItemInfo{ -// Groups: &details.GroupsInfo{ -// ItemType: details.GroupsLibrary, -// ItemName: "itemName3", -// ParentPath: strings.Join(itemElems3, "/"), -// }, -// }, -// }, -// { -// RepoRef: item4, -// LocationRef: pairGH, -// ItemRef: "item4", -// ItemInfo: details.ItemInfo{ -// Groups: &details.GroupsInfo{ -// ItemType: details.GroupsPage, -// ItemName: "itemName4", -// ParentPath: pairGH, -// }, -// }, -// }, -// { -// RepoRef: item5, -// LocationRef: pairGH, -// // ItemRef intentionally blank to test fallback case -// ItemInfo: details.ItemInfo{ -// Groups: &details.GroupsInfo{ -// ItemType: details.GroupsPage, -// ItemName: "itemName5", -// ParentPath: pairGH, -// }, -// }, -// }, -// }, -// }, -// } + var ( + drivePrefixElems = []string{ + odConsts.DrivesPathDir, + "drive!id", + odConsts.RootPathDir, + } + itemElems1 = []string{"folderA", "folderB"} + itemElems2 = []string{"folderA", "folderC"} + itemElems3 = []string{"folderD", "folderE"} + pairAC = "folderA/folderC" + libItem = toRR( + path.LibrariesCategory, + "sid", + append(slices.Clone(drivePrefixElems), itemElems1...), + "item") + libItem2 = toRR( + path.LibrariesCategory, + "sid", + append(slices.Clone(drivePrefixElems), itemElems2...), + "item2") + libItem3 = toRR( + path.LibrariesCategory, + "sid", + append(slices.Clone(drivePrefixElems), itemElems3...), + "item3") + chanItem = toRR(path.ChannelMessagesCategory, "gid", slices.Clone(itemElems1), "chitem") + chanItem2 = toRR(path.ChannelMessagesCategory, "gid", slices.Clone(itemElems2), "chitem2") + chanItem3 = toRR(path.ChannelMessagesCategory, "gid", slices.Clone(itemElems3), "chitem3") + ) -// arr := func(s ...string) []string { -// return s -// } + deets := &details.Details{ + DetailsModel: details.DetailsModel{ + Entries: []details.Entry{ + { + RepoRef: libItem, + ItemRef: "item", + LocationRef: strings.Join(append([]string{odConsts.RootPathDir}, itemElems1...), "/"), + ItemInfo: details.ItemInfo{ + Groups: &details.GroupsInfo{ + ItemType: details.SharePointLibrary, + ItemName: "itemName", + ParentPath: strings.Join(itemElems1, "/"), + }, + }, + }, + { + RepoRef: libItem2, + LocationRef: strings.Join(append([]string{odConsts.RootPathDir}, itemElems2...), "/"), + // ItemRef intentionally blank to test fallback case + ItemInfo: details.ItemInfo{ + Groups: &details.GroupsInfo{ + ItemType: details.SharePointLibrary, + ItemName: "itemName2", + ParentPath: strings.Join(itemElems2, "/"), + }, + }, + }, + { + RepoRef: libItem3, + ItemRef: "item3", + LocationRef: strings.Join(append([]string{odConsts.RootPathDir}, itemElems3...), "/"), + ItemInfo: details.ItemInfo{ + Groups: &details.GroupsInfo{ + ItemType: details.SharePointLibrary, + ItemName: "itemName3", + ParentPath: strings.Join(itemElems3, "/"), + }, + }, + }, + { + RepoRef: chanItem, + ItemRef: "citem", + LocationRef: strings.Join(itemElems1, "/"), + ItemInfo: details.ItemInfo{ + Groups: &details.GroupsInfo{ + ItemType: details.TeamsChannelMessage, + ParentPath: strings.Join(itemElems1, "/"), + }, + }, + }, + { + RepoRef: chanItem2, + LocationRef: strings.Join(itemElems2, "/"), + // ItemRef intentionally blank to test fallback case + ItemInfo: details.ItemInfo{ + Groups: &details.GroupsInfo{ + ItemType: details.TeamsChannelMessage, + ParentPath: strings.Join(itemElems2, "/"), + }, + }, + }, + { + RepoRef: chanItem3, + ItemRef: "citem3", + LocationRef: strings.Join(itemElems3, "/"), + ItemInfo: details.ItemInfo{ + Groups: &details.GroupsInfo{ + ItemType: details.TeamsChannelMessage, + ParentPath: strings.Join(itemElems3, "/"), + }, + }, + }, + }, + }, + } -// table := []struct { -// name string -// makeSelector func() *GroupsRestore -// expect []string -// cfg Config -// }{ -// { -// name: "all", -// makeSelector: func() *GroupsRestore { -// odr := NewGroupsRestore(Any()) -// odr.Include(odr.AllData()) -// return odr -// }, -// expect: arr(item, item2, item3, item4, item5), -// }, -// { -// name: "only match item", -// makeSelector: func() *GroupsRestore { -// odr := NewGroupsRestore(Any()) -// odr.Include(odr.LibraryItems(Any(), []string{"item2"})) -// return odr -// }, -// expect: arr(item2), -// }, -// { -// name: "id doesn't match name", -// makeSelector: func() *GroupsRestore { -// odr := NewGroupsRestore(Any()) -// odr.Include(odr.LibraryItems(Any(), []string{"item2"})) -// return odr -// }, -// expect: []string{}, -// cfg: Config{OnlyMatchItemNames: true}, -// }, -// { -// name: "only match item name", -// makeSelector: func() *GroupsRestore { -// odr := NewGroupsRestore(Any()) -// odr.Include(odr.LibraryItems(Any(), []string{"itemName2"})) -// return odr -// }, -// expect: arr(item2), -// cfg: Config{OnlyMatchItemNames: true}, -// }, -// { -// name: "name doesn't match", -// makeSelector: func() *GroupsRestore { -// odr := NewGroupsRestore(Any()) -// odr.Include(odr.LibraryItems(Any(), []string{"itemName2"})) -// return odr -// }, -// expect: []string{}, -// }, -// { -// name: "only match folder", -// makeSelector: func() *GroupsRestore { -// odr := NewGroupsRestore([]string{"sid"}) -// odr.Include(odr.LibraryFolders([]string{"folderA/folderB", pairAC})) -// return odr -// }, -// expect: arr(item, item2), -// }, -// { -// name: "pages match folder", -// makeSelector: func() *GroupsRestore { -// odr := NewGroupsRestore([]string{"sid"}) -// odr.Include(odr.Pages([]string{pairGH, pairAC})) -// return odr -// }, -// expect: arr(item4, item5), -// }, -// } -// for _, test := range table { -// suite.Run(test.name, func() { -// t := suite.T() + arr := func(s ...string) []string { + return s + } -// ctx, flush := tester.NewContext(t) -// defer flush() + table := []struct { + name string + makeSelector func() *GroupsRestore + expect []string + cfg Config + }{ + { + name: "all", + makeSelector: func() *GroupsRestore { + sel := NewGroupsRestore(Any()) + sel.Include(sel.AllData()) + return sel + }, + expect: arr(libItem, libItem2, libItem3, chanItem, chanItem2, chanItem3), + }, + { + name: "only match library item", + makeSelector: func() *GroupsRestore { + sel := NewGroupsRestore(Any()) + sel.Include(sel.LibraryItems(Any(), []string{"item2"})) + return sel + }, + expect: arr(libItem2), + }, + { + name: "only match channel item", + makeSelector: func() *GroupsRestore { + sel := NewGroupsRestore(Any()) + sel.Include(sel.ChannelMessages(Any(), []string{"chitem2"})) + return sel + }, + expect: arr(chanItem2), + }, + { + name: "library id doesn't match name", + makeSelector: func() *GroupsRestore { + sel := NewGroupsRestore(Any()) + sel.Include(sel.LibraryItems(Any(), []string{"item2"})) + return sel + }, + expect: []string{}, + cfg: Config{OnlyMatchItemNames: true}, + }, + { + name: "channel id doesn't match name", + makeSelector: func() *GroupsRestore { + sel := NewGroupsRestore(Any()) + sel.Include(sel.ChannelMessages(Any(), []string{"item2"})) + return sel + }, + expect: []string{}, + cfg: Config{OnlyMatchItemNames: true}, + }, + { + name: "library only match item name", + makeSelector: func() *GroupsRestore { + sel := NewGroupsRestore(Any()) + sel.Include(sel.LibraryItems(Any(), []string{"itemName2"})) + return sel + }, + expect: arr(libItem2), + cfg: Config{OnlyMatchItemNames: true}, + }, + { + name: "name doesn't match", + makeSelector: func() *GroupsRestore { + sel := NewGroupsRestore(Any()) + sel.Include(sel.LibraryItems(Any(), []string{"itemName2"})) + return sel + }, + expect: []string{}, + }, + { + name: "only match folder", + makeSelector: func() *GroupsRestore { + sel := NewGroupsRestore([]string{"sid"}) + sel.Include(sel.LibraryFolders([]string{"folderA/folderB", pairAC})) + return sel + }, + expect: arr(libItem, libItem2), + }, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() -// sel := test.makeSelector() -// sel.Configure(test.cfg) -// results := sel.Reduce(ctx, deets, fault.New(true)) -// paths := results.Paths() -// assert.Equal(t, test.expect, paths) -// }) -// } -// } + ctx, flush := tester.NewContext(t) + defer flush() + + sel := test.makeSelector() + sel.Configure(test.cfg) + results := sel.Reduce(ctx, deets, fault.New(true)) + paths := results.Paths() + assert.Equal(t, test.expect, paths) + }) + } +} func (suite *GroupsSelectorSuite) TestGroupsCategory_PathValues() { var ( @@ -324,91 +355,111 @@ func (suite *GroupsSelectorSuite) TestGroupsCategory_PathValues() { } } -// TODO(abin): implement -// func (suite *GroupsSelectorSuite) TestGroupsScope_MatchesInfo() { -// var ( -// sel = NewGroupsRestore(Any()) -// host = "www.website.com" -// pth = "/foo" -// url = host + pth -// epoch = time.Time{} -// now = time.Now() -// modification = now.Add(15 * time.Minute) -// future = now.Add(45 * time.Minute) -// ) +func (suite *GroupsSelectorSuite) TestGroupsScope_MatchesInfo() { + var ( + sel = NewGroupsRestore(Any()) + user = "user@mail.com" + host = "www.website.com" + // pth = "/foo" + // url = host + pth + epoch = time.Time{} + now = time.Now() + modification = now.Add(15 * time.Minute) + future = now.Add(45 * time.Minute) + dtch = details.TeamsChannelMessage + ) -// table := []struct { -// name string -// infoURL string -// scope []GroupsScope -// expect assert.BoolAssertionFunc -// }{ -// {"host match", host, sel.WebURL([]string{host}), assert.True}, -// {"url match", url, sel.WebURL([]string{url}), assert.True}, -// {"host suffixes host", host, sel.WebURL([]string{host}, SuffixMatch()), assert.True}, -// {"url does not suffix host", url, sel.WebURL([]string{host}, SuffixMatch()), assert.False}, -// {"url has path suffix", url, sel.WebURL([]string{pth}, SuffixMatch()), assert.True}, -// {"host does not contain substring", host, sel.WebURL([]string{"website"}), assert.False}, -// {"url does not suffix substring", url, sel.WebURL([]string{"oo"}, SuffixMatch()), assert.False}, -// {"host mismatch", host, sel.WebURL([]string{"www.google.com"}), assert.False}, -// {"file create after the epoch", host, sel.CreatedAfter(dttm.Format(epoch)), assert.True}, -// {"file create after now", host, sel.CreatedAfter(dttm.Format(now)), assert.False}, -// {"file create after later", url, sel.CreatedAfter(dttm.Format(future)), assert.False}, -// {"file create before future", host, sel.CreatedBefore(dttm.Format(future)), assert.True}, -// {"file create before now", host, sel.CreatedBefore(dttm.Format(now)), assert.False}, -// {"file create before modification", host, sel.CreatedBefore(dttm.Format(modification)), assert.True}, -// {"file create before epoch", host, sel.CreatedBefore(dttm.Format(now)), assert.False}, -// {"file modified after the epoch", host, sel.ModifiedAfter(dttm.Format(epoch)), assert.True}, -// {"file modified after now", host, sel.ModifiedAfter(dttm.Format(now)), assert.True}, -// {"file modified after later", host, sel.ModifiedAfter(dttm.Format(future)), assert.False}, -// {"file modified before future", host, sel.ModifiedBefore(dttm.Format(future)), assert.True}, -// {"file modified before now", host, sel.ModifiedBefore(dttm.Format(now)), assert.False}, -// {"file modified before epoch", host, sel.ModifiedBefore(dttm.Format(now)), assert.False}, -// {"in library", host, sel.Library("included-library"), assert.True}, -// {"not in library", host, sel.Library("not-included-library"), assert.False}, -// {"library id", host, sel.Library("1234"), assert.True}, -// {"not library id", host, sel.Library("abcd"), assert.False}, -// } -// for _, test := range table { -// suite.Run(test.name, func() { -// t := suite.T() + table := []struct { + name string + itemType details.ItemType + creator string + scope []GroupsScope + expect assert.BoolAssertionFunc + }{ + // TODO(abin): implement + // {"host match", host, sel.WebURL([]string{host}), assert.True}, + // {"url match", url, sel.WebURL([]string{url}), assert.True}, + // {"host suffixes host", host, sel.WebURL([]string{host}, SuffixMatch()), assert.True}, + // {"url does not suffix host", url, sel.WebURL([]string{host}, SuffixMatch()), assert.False}, + // {"url has path suffix", url, sel.WebURL([]string{pth}, SuffixMatch()), assert.True}, + // {"host does not contain substring", host, sel.WebURL([]string{"website"}), assert.False}, + // {"url does not suffix substring", url, sel.WebURL([]string{"oo"}, SuffixMatch()), assert.False}, + // {"host mismatch", host, sel.WebURL([]string{"www.google.com"}), assert.False}, + // {"file create after the epoch", host, sel.CreatedAfter(dttm.Format(epoch)), assert.True}, + // {"file create after now", host, sel.CreatedAfter(dttm.Format(now)), assert.False}, + // {"file create after later", url, sel.CreatedAfter(dttm.Format(future)), assert.False}, + // {"file create before future", host, sel.CreatedBefore(dttm.Format(future)), assert.True}, + // {"file create before now", host, sel.CreatedBefore(dttm.Format(now)), assert.False}, + // {"file create before modification", host, sel.CreatedBefore(dttm.Format(modification)), assert.True}, + // {"file create before epoch", host, sel.CreatedBefore(dttm.Format(now)), assert.False}, + // {"file modified after the epoch", host, sel.ModifiedAfter(dttm.Format(epoch)), assert.True}, + // {"file modified after now", host, sel.ModifiedAfter(dttm.Format(now)), assert.True}, + // {"file modified after later", host, sel.ModifiedAfter(dttm.Format(future)), assert.False}, + // {"file modified before future", host, sel.ModifiedBefore(dttm.Format(future)), assert.True}, + // {"file modified before now", host, sel.ModifiedBefore(dttm.Format(now)), assert.False}, + // {"file modified before epoch", host, sel.ModifiedBefore(dttm.Format(now)), assert.False}, + // {"in library", host, sel.Library("included-library"), assert.True}, + // {"not in library", host, sel.Library("not-included-library"), assert.False}, + // {"library id", host, sel.Library("1234"), assert.True}, + // {"not library id", host, sel.Library("abcd"), assert.False}, -// itemInfo := details.ItemInfo{ -// Groups: &details.GroupsInfo{ -// ItemType: details.GroupsPage, -// WebURL: test.infoURL, -// Created: now, -// Modified: modification, -// DriveName: "included-library", -// DriveID: "1234", -// }, -// } + {"channel message created by", dtch, user, sel.MessageCreator(user), assert.True}, + {"channel message not created by", dtch, user, sel.MessageCreator(host), assert.False}, + {"chan msg create after the epoch", dtch, user, sel.MessageCreatedAfter(dttm.Format(epoch)), assert.True}, + {"chan msg create after now", dtch, user, sel.MessageCreatedAfter(dttm.Format(now)), assert.False}, + {"chan msg create after later", dtch, user, sel.MessageCreatedAfter(dttm.Format(future)), assert.False}, + {"chan msg create before future", dtch, user, sel.MessageCreatedBefore(dttm.Format(future)), assert.True}, + {"chan msg create before now", dtch, user, sel.MessageCreatedBefore(dttm.Format(now)), assert.False}, + {"chan msg create before reply", dtch, user, sel.MessageCreatedBefore(dttm.Format(modification)), assert.True}, + {"chan msg create before epoch", dtch, user, sel.MessageCreatedBefore(dttm.Format(now)), assert.False}, + {"chan msg last reply after the epoch", dtch, user, sel.MessageLastReplyAfter(dttm.Format(epoch)), assert.True}, + {"chan msg last reply after now", dtch, user, sel.MessageLastReplyAfter(dttm.Format(now)), assert.True}, + {"chan msg last reply after later", dtch, user, sel.MessageLastReplyAfter(dttm.Format(future)), assert.False}, + {"chan msg last reply before future", dtch, user, sel.MessageLastReplyBefore(dttm.Format(future)), assert.True}, + {"chan msg last reply before now", dtch, user, sel.MessageLastReplyBefore(dttm.Format(now)), assert.False}, + {"chan msg last reply before epoch", dtch, user, sel.MessageLastReplyBefore(dttm.Format(now)), assert.False}, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() -// scopes := setScopesToDefault(test.scope) -// for _, scope := range scopes { -// test.expect(t, scope.matchesInfo(itemInfo)) -// } -// }) -// } -// } + itemInfo := details.ItemInfo{ + Groups: &details.GroupsInfo{ + ItemType: test.itemType, + WebURL: test.creator, + MessageCreator: test.creator, + Created: now, + Modified: modification, + LastReplyAt: modification, + DriveName: "included-library", + DriveID: "1234", + }, + } + + scopes := setScopesToDefault(test.scope) + for _, scope := range scopes { + test.expect(t, scope.matchesInfo(itemInfo)) + } + }) + } +} func (suite *GroupsSelectorSuite) TestCategory_PathType() { table := []struct { cat groupsCategory pathType path.CategoryType }{ - { - cat: GroupsCategoryUnknown, - pathType: path.UnknownCategory, - }, - { - cat: GroupsChannel, - pathType: path.ChannelMessagesCategory, - }, - { - cat: GroupsChannelMessage, - pathType: path.ChannelMessagesCategory, - }, + {GroupsCategoryUnknown, path.UnknownCategory}, + {GroupsChannel, path.ChannelMessagesCategory}, + {GroupsChannelMessage, path.ChannelMessagesCategory}, + {GroupsInfoChannelMessageCreator, path.ChannelMessagesCategory}, + {GroupsInfoChannelMessageCreatedAfter, path.ChannelMessagesCategory}, + {GroupsInfoChannelMessageCreatedBefore, path.ChannelMessagesCategory}, + {GroupsInfoChannelMessageLastReplyAfter, path.ChannelMessagesCategory}, + {GroupsInfoChannelMessageLastReplyBefore, path.ChannelMessagesCategory}, + {GroupsLibraryFolder, path.LibrariesCategory}, + {GroupsLibraryItem, path.LibrariesCategory}, + {GroupsInfoSiteLibraryDrive, path.LibrariesCategory}, } for _, test := range table { suite.Run(test.cat.String(), func() {