diff --git a/src/cli/utils/sharepoint_test.go b/src/cli/utils/sharepoint_test.go index d00aae671..de5cd5a6e 100644 --- a/src/cli/utils/sharepoint_test.go +++ b/src/cli/utils/sharepoint_test.go @@ -41,7 +41,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() { Sites: empty, WebURLs: empty, }, - expectIncludeLen: 1, + expectIncludeLen: 2, }, { name: "single inputs", @@ -51,7 +51,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() { Sites: single, WebURLs: single, }, - expectIncludeLen: 2, + expectIncludeLen: 3, }, { name: "multi inputs", @@ -61,7 +61,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() { Sites: multi, WebURLs: multi, }, - expectIncludeLen: 2, + expectIncludeLen: 3, }, { name: "library contains", @@ -101,7 +101,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() { Sites: empty, WebURLs: containsOnly, }, - expectIncludeLen: 1, + expectIncludeLen: 2, }, { name: "library suffixes", @@ -111,7 +111,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() { Sites: empty, WebURLs: prefixOnly, // prefix pattern matches suffix pattern }, - expectIncludeLen: 1, + expectIncludeLen: 2, }, { name: "library suffixes and contains", @@ -121,15 +121,16 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() { Sites: empty, WebURLs: containsAndPrefix, // prefix pattern matches suffix pattern }, - expectIncludeLen: 2, + expectIncludeLen: 4, }, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { sel := selectors.NewSharePointRestore() // no return, mutates sel as a side effect + t.Logf("Options sent: %v\n", test.opts) utils.IncludeSharePointRestoreDataSelectors(sel, test.opts) - assert.Len(t, sel.Includes, test.expectIncludeLen) + assert.Len(t, sel.Includes, test.expectIncludeLen, sel) }) } } diff --git a/src/internal/connector/exchange/service_restore.go b/src/internal/connector/exchange/service_restore.go index de9080a0d..ffed09563 100644 --- a/src/internal/connector/exchange/service_restore.go +++ b/src/internal/connector/exchange/service_restore.go @@ -247,7 +247,7 @@ func SendMailToBackStore( sentMessage, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(ctx, message, nil) if err != nil { return errors.Wrap(err, - user+": failure sendMailAPI: "+support.ConnectorStackErrorTrace(err), + user+": failure sendMailAPI: Dest: "+destination+" Details: "+support.ConnectorStackErrorTrace(err), ) } diff --git a/src/internal/operations/backup_test.go b/src/internal/operations/backup_test.go index b56d19b0b..8f92e284c 100644 --- a/src/internal/operations/backup_test.go +++ b/src/internal/operations/backup_test.go @@ -509,8 +509,8 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_sharePoint() { siteID = tester.M365SiteID(t) sel = selectors.NewSharePointBackup() ) - - sel.Include(sel.Sites([]string{siteID})) + // TODO: dadams39 Issue #1795: Revert to Sites Upon List Integration + sel.Include(sel.Libraries([]string{siteID}, selectors.Any())) bo, _, _, _, closer := prepNewBackupOp(t, ctx, mb, sel.Selector) defer closer() diff --git a/src/pkg/selectors/sharepoint.go b/src/pkg/selectors/sharepoint.go index 579242fa8..7dc6ebe08 100644 --- a/src/pkg/selectors/sharepoint.go +++ b/src/pkg/selectors/sharepoint.go @@ -190,14 +190,23 @@ func (s *sharePoint) DiscreteScopes(siteIDs []string) []SharePointScope { // 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 *SharePointRestore) WebURL(urlSuffixes []string, opts ...option) []SharePointScope { - return []SharePointScope{ + scopes := []SharePointScope{} + + scopes = append( + scopes, makeFilterScope[SharePointScope]( SharePointLibraryItem, SharePointWebURL, urlSuffixes, pathFilterFactory(opts...)), - // TODO: list scope - } + makeFilterScope[SharePointScope]( + SharePointListItem, + SharePointWebURL, + urlSuffixes, + pathFilterFactory(opts...)), + ) + + return scopes } // Produces one or more SharePoint site scopes. @@ -208,7 +217,43 @@ func (s *SharePointRestore) WebURL(urlSuffixes []string, opts ...option) []Share func (s *sharePoint) Sites(sites []string) []SharePointScope { scopes := []SharePointScope{} - scopes = append(scopes, makeScope[SharePointScope](SharePointLibrary, sites, Any())) + scopes = append( + scopes, + makeScope[SharePointScope](SharePointLibrary, sites, Any()), + makeScope[SharePointScope](SharePointList, sites, Any()), + ) + + return scopes +} + +// Lists produces one or more SharePoint list scopes. +// 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] +// Any empty slice defaults to [selectors.None] +func (s *sharePoint) Lists(sites, lists []string, opts ...option) []SharePointScope { + var ( + scopes = []SharePointScope{} + os = append([]option{pathComparator()}, opts...) + ) + + scopes = append(scopes, makeScope[SharePointScope](SharePointList, sites, lists, os...)) + + return scopes +} + +// ListItems produces one or more SharePoint list item scopes. +// 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] +// options are only applied to the list scopes. +func (s *sharePoint) ListItems(sites, lists, items []string, opts ...option) []SharePointScope { + scopes := []SharePointScope{} + + scopes = append( + scopes, + makeScope[SharePointScope](SharePointListItem, sites, items). + set(SharePointList, lists, opts...), + ) return scopes } @@ -268,6 +313,8 @@ const ( // types of data identified by SharePoint SharePointWebURL sharePointCategory = "SharePointWebURL" SharePointSite sharePointCategory = "SharePointSite" + SharePointList sharePointCategory = "SharePointList" + SharePointListItem sharePointCategory = "SharePointListItem" SharePointLibrary sharePointCategory = "SharePointLibrary" SharePointLibraryItem sharePointCategory = "SharePointLibraryItem" @@ -284,6 +331,10 @@ var sharePointLeafProperties = map[categorizer]leafProperty{ pathKeys: []categorizer{SharePointSite}, pathType: path.UnknownCategory, }, + SharePointListItem: { + pathKeys: []categorizer{SharePointSite, SharePointList, SharePointListItem}, + pathType: path.ListsCategory, + }, } func (c sharePointCategory) String() string { @@ -299,6 +350,8 @@ func (c sharePointCategory) leafCat() categorizer { switch c { case SharePointLibrary, SharePointLibraryItem: return SharePointLibraryItem + case SharePointList, SharePointListItem: + return SharePointListItem } return c @@ -331,10 +384,19 @@ func (c sharePointCategory) isLeaf() bool { // [tenantID, service, siteID, category, folder, itemID] // => {spSite: siteID, spFolder: folder, spItemID: itemID} func (c sharePointCategory) pathValues(p path.Path) map[categorizer]string { + var folderCat, itemCat categorizer + + switch c { + case SharePointLibrary, SharePointLibraryItem: + folderCat, itemCat = SharePointLibrary, SharePointLibraryItem + case SharePointList, SharePointListItem: + folderCat, itemCat = SharePointList, SharePointListItem + } + return map[categorizer]string{ - SharePointSite: p.ResourceOwner(), - SharePointLibrary: p.Folder(), - SharePointLibraryItem: p.Item(), + SharePointSite: p.ResourceOwner(), + folderCat: p.Folder(), + itemCat: p.Item(), } } @@ -406,7 +468,9 @@ func (s SharePointScope) Get(cat sharePointCategory) []string { // sets a value by category to the scope. Only intended for internal use. func (s SharePointScope) set(cat sharePointCategory, v []string, opts ...option) SharePointScope { os := []option{} - if cat == SharePointLibrary { + + switch cat { + case SharePointLibrary, SharePointList: os = append(os, pathComparator()) } @@ -419,8 +483,12 @@ func (s SharePointScope) setDefaults() { case SharePointSite: s[SharePointLibrary.String()] = passAny s[SharePointLibraryItem.String()] = passAny + s[SharePointList.String()] = passAny + s[SharePointListItem.String()] = passAny case SharePointLibrary: s[SharePointLibraryItem.String()] = passAny + case SharePointList: + s[SharePointListItem.String()] = passAny } } @@ -443,7 +511,7 @@ func (s sharePoint) Reduce(ctx context.Context, deets *details.Details) *details s.Selector, map[path.CategoryType]sharePointCategory{ path.LibrariesCategory: SharePointLibraryItem, - // TODO: list category type + path.ListsCategory: SharePointListItem, }, ) } diff --git a/src/pkg/selectors/sharepoint_test.go b/src/pkg/selectors/sharepoint_test.go index c8e54bd0d..bb77360ff 100644 --- a/src/pkg/selectors/sharepoint_test.go +++ b/src/pkg/selectors/sharepoint_test.go @@ -109,7 +109,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Sites() { } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - require.Len(t, test.scopesToCheck, 1) + require.Len(t, test.scopesToCheck, 2) for _, scope := range test.scopesToCheck { // Scope value is s1,s2 assert.Contains(t, join(s1, s2), scope[SharePointSite.String()].Target) @@ -129,7 +129,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs() { sel.Include(sel.WebURL([]string{s1, s2})) scopes := sel.Includes - require.Len(t, scopes, 1) + require.Len(t, scopes, 2) for _, sc := range scopes { scopeMustHave( @@ -162,7 +162,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs_any sel := NewSharePointRestore() sel.Include(sel.WebURL(test.in)) scopes := sel.Includes - require.Len(t, scopes, 1) + require.Len(t, scopes, 2) for _, sc := range scopes { scopeMustHave( @@ -186,7 +186,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_WebURLs() { sel.Exclude(sel.WebURL([]string{s1, s2})) scopes := sel.Excludes - require.Len(t, scopes, 1) + require.Len(t, scopes, 2) for _, sc := range scopes { scopeMustHave( @@ -197,6 +197,8 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_WebURLs() { } } +// TestSharePointselector_Include_Sites ensures that the scopes of +// SharePoint Libraries & SharePoint Lists are created. func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_Sites() { t := suite.T() sel := NewSharePointBackup() @@ -208,7 +210,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_Sites() { sel.Include(sel.Sites([]string{s1, s2})) scopes := sel.Includes - require.Len(t, scopes, 1) + require.Len(t, scopes, 2) for _, sc := range scopes { scopeMustHave( @@ -230,7 +232,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_Sites() { sel.Exclude(sel.Sites([]string{s1, s2})) scopes := sel.Excludes - require.Len(t, scopes, 1) + require.Len(t, scopes, 2) for _, sc := range scopes { scopeMustHave( @@ -352,18 +354,46 @@ func (suite *SharePointSelectorSuite) TestSharePointRestore_Reduce() { func (suite *SharePointSelectorSuite) TestSharePointCategory_PathValues() { t := suite.T() - pathBuilder := path.Builder{}.Append("dir1", "dir2", "item") - itemPath, err := pathBuilder.ToDataLayerSharePointPath("tenant", "site", path.LibrariesCategory, true) - require.NoError(t, err) - - expected := map[categorizer]string{ - SharePointSite: "site", - SharePointLibrary: "dir1/dir2", - SharePointLibraryItem: "item", + ten := "tenant" + site := "site" + table := []struct { + name string + sc sharePointCategory + expected map[categorizer]string + }{ + { + name: "SharePoint Libraries", + sc: SharePointLibraryItem, + expected: map[categorizer]string{ + SharePointSite: site, + SharePointLibrary: "dir1/dir2", + SharePointLibraryItem: "item", + }, + }, + { + name: "SharePoint Lists", + sc: SharePointListItem, + expected: map[categorizer]string{ + SharePointSite: site, + SharePointList: "dir1/dir2", + SharePointListItem: "item", + }, + }, } - assert.Equal(t, expected, SharePointLibraryItem.pathValues(itemPath)) + for _, test := range table { + t.Run(test.name, func(t *testing.T) { + itemPath, err := pathBuilder.ToDataLayerSharePointPath( + ten, + site, + test.sc.PathType(), + true, + ) + require.NoError(t, err) + assert.Equal(t, test.expected, test.sc.pathValues(itemPath)) + }) + } } func (suite *SharePointSelectorSuite) TestSharePointScope_MatchesInfo() { @@ -418,6 +448,7 @@ func (suite *SharePointSelectorSuite) TestCategory_PathType() { {SharePointSite, path.UnknownCategory}, {SharePointLibrary, path.LibrariesCategory}, {SharePointLibraryItem, path.LibrariesCategory}, + {SharePointList, path.ListsCategory}, } for _, test := range table { suite.T().Run(test.cat.String(), func(t *testing.T) {