SharePoint List selector Expansion (#1786)

## Description
Initial changes to support SharePoint Lists being chosen for Backup Operations. 
<!-- Insert PR description-->

## Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature


## Issue(s)

* close #1785<issue>

## Test Plan

- [x]  Unit test
This commit is contained in:
Danny 2022-12-16 08:24:15 -05:00 committed by GitHub
parent ea445ec471
commit 0d5043aa1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 34 deletions

View File

@ -41,7 +41,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty, Sites: empty,
WebURLs: empty, WebURLs: empty,
}, },
expectIncludeLen: 1, expectIncludeLen: 2,
}, },
{ {
name: "single inputs", name: "single inputs",
@ -51,7 +51,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: single, Sites: single,
WebURLs: single, WebURLs: single,
}, },
expectIncludeLen: 2, expectIncludeLen: 3,
}, },
{ {
name: "multi inputs", name: "multi inputs",
@ -61,7 +61,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: multi, Sites: multi,
WebURLs: multi, WebURLs: multi,
}, },
expectIncludeLen: 2, expectIncludeLen: 3,
}, },
{ {
name: "library contains", name: "library contains",
@ -101,7 +101,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty, Sites: empty,
WebURLs: containsOnly, WebURLs: containsOnly,
}, },
expectIncludeLen: 1, expectIncludeLen: 2,
}, },
{ {
name: "library suffixes", name: "library suffixes",
@ -111,7 +111,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty, Sites: empty,
WebURLs: prefixOnly, // prefix pattern matches suffix pattern WebURLs: prefixOnly, // prefix pattern matches suffix pattern
}, },
expectIncludeLen: 1, expectIncludeLen: 2,
}, },
{ {
name: "library suffixes and contains", name: "library suffixes and contains",
@ -121,15 +121,16 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty, Sites: empty,
WebURLs: containsAndPrefix, // prefix pattern matches suffix pattern WebURLs: containsAndPrefix, // prefix pattern matches suffix pattern
}, },
expectIncludeLen: 2, expectIncludeLen: 4,
}, },
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
sel := selectors.NewSharePointRestore() sel := selectors.NewSharePointRestore()
// no return, mutates sel as a side effect // no return, mutates sel as a side effect
t.Logf("Options sent: %v\n", test.opts)
utils.IncludeSharePointRestoreDataSelectors(sel, test.opts) utils.IncludeSharePointRestoreDataSelectors(sel, test.opts)
assert.Len(t, sel.Includes, test.expectIncludeLen) assert.Len(t, sel.Includes, test.expectIncludeLen, sel)
}) })
} }
} }

View File

@ -247,7 +247,7 @@ func SendMailToBackStore(
sentMessage, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(ctx, message, nil) sentMessage, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(ctx, message, nil)
if err != nil { if err != nil {
return errors.Wrap(err, return errors.Wrap(err,
user+": failure sendMailAPI: "+support.ConnectorStackErrorTrace(err), user+": failure sendMailAPI: Dest: "+destination+" Details: "+support.ConnectorStackErrorTrace(err),
) )
} }

View File

@ -509,8 +509,8 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_sharePoint() {
siteID = tester.M365SiteID(t) siteID = tester.M365SiteID(t)
sel = selectors.NewSharePointBackup() sel = selectors.NewSharePointBackup()
) )
// TODO: dadams39 Issue #1795: Revert to Sites Upon List Integration
sel.Include(sel.Sites([]string{siteID})) sel.Include(sel.Libraries([]string{siteID}, selectors.Any()))
bo, _, _, _, closer := prepNewBackupOp(t, ctx, mb, sel.Selector) bo, _, _, _, closer := prepNewBackupOp(t, ctx, mb, sel.Selector)
defer closer() defer closer()

View File

@ -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 contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *SharePointRestore) WebURL(urlSuffixes []string, opts ...option) []SharePointScope { func (s *SharePointRestore) WebURL(urlSuffixes []string, opts ...option) []SharePointScope {
return []SharePointScope{ scopes := []SharePointScope{}
scopes = append(
scopes,
makeFilterScope[SharePointScope]( makeFilterScope[SharePointScope](
SharePointLibraryItem, SharePointLibraryItem,
SharePointWebURL, SharePointWebURL,
urlSuffixes, urlSuffixes,
pathFilterFactory(opts...)), pathFilterFactory(opts...)),
// TODO: list scope makeFilterScope[SharePointScope](
} SharePointListItem,
SharePointWebURL,
urlSuffixes,
pathFilterFactory(opts...)),
)
return scopes
} }
// Produces one or more SharePoint site 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 { func (s *sharePoint) Sites(sites []string) []SharePointScope {
scopes := []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 return scopes
} }
@ -268,6 +313,8 @@ const (
// types of data identified by SharePoint // types of data identified by SharePoint
SharePointWebURL sharePointCategory = "SharePointWebURL" SharePointWebURL sharePointCategory = "SharePointWebURL"
SharePointSite sharePointCategory = "SharePointSite" SharePointSite sharePointCategory = "SharePointSite"
SharePointList sharePointCategory = "SharePointList"
SharePointListItem sharePointCategory = "SharePointListItem"
SharePointLibrary sharePointCategory = "SharePointLibrary" SharePointLibrary sharePointCategory = "SharePointLibrary"
SharePointLibraryItem sharePointCategory = "SharePointLibraryItem" SharePointLibraryItem sharePointCategory = "SharePointLibraryItem"
@ -284,6 +331,10 @@ var sharePointLeafProperties = map[categorizer]leafProperty{
pathKeys: []categorizer{SharePointSite}, pathKeys: []categorizer{SharePointSite},
pathType: path.UnknownCategory, pathType: path.UnknownCategory,
}, },
SharePointListItem: {
pathKeys: []categorizer{SharePointSite, SharePointList, SharePointListItem},
pathType: path.ListsCategory,
},
} }
func (c sharePointCategory) String() string { func (c sharePointCategory) String() string {
@ -299,6 +350,8 @@ func (c sharePointCategory) leafCat() categorizer {
switch c { switch c {
case SharePointLibrary, SharePointLibraryItem: case SharePointLibrary, SharePointLibraryItem:
return SharePointLibraryItem return SharePointLibraryItem
case SharePointList, SharePointListItem:
return SharePointListItem
} }
return c return c
@ -331,10 +384,19 @@ func (c sharePointCategory) isLeaf() bool {
// [tenantID, service, siteID, category, folder, itemID] // [tenantID, service, siteID, category, folder, itemID]
// => {spSite: siteID, spFolder: folder, spItemID: itemID} // => {spSite: siteID, spFolder: folder, spItemID: itemID}
func (c sharePointCategory) pathValues(p path.Path) map[categorizer]string { 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{ return map[categorizer]string{
SharePointSite: p.ResourceOwner(), SharePointSite: p.ResourceOwner(),
SharePointLibrary: p.Folder(), folderCat: p.Folder(),
SharePointLibraryItem: p.Item(), 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. // sets a value by category to the scope. Only intended for internal use.
func (s SharePointScope) set(cat sharePointCategory, v []string, opts ...option) SharePointScope { func (s SharePointScope) set(cat sharePointCategory, v []string, opts ...option) SharePointScope {
os := []option{} os := []option{}
if cat == SharePointLibrary {
switch cat {
case SharePointLibrary, SharePointList:
os = append(os, pathComparator()) os = append(os, pathComparator())
} }
@ -419,8 +483,12 @@ func (s SharePointScope) setDefaults() {
case SharePointSite: case SharePointSite:
s[SharePointLibrary.String()] = passAny s[SharePointLibrary.String()] = passAny
s[SharePointLibraryItem.String()] = passAny s[SharePointLibraryItem.String()] = passAny
s[SharePointList.String()] = passAny
s[SharePointListItem.String()] = passAny
case SharePointLibrary: case SharePointLibrary:
s[SharePointLibraryItem.String()] = passAny 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, s.Selector,
map[path.CategoryType]sharePointCategory{ map[path.CategoryType]sharePointCategory{
path.LibrariesCategory: SharePointLibraryItem, path.LibrariesCategory: SharePointLibraryItem,
// TODO: list category type path.ListsCategory: SharePointListItem,
}, },
) )
} }

View File

@ -109,7 +109,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Sites() {
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { 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 { for _, scope := range test.scopesToCheck {
// Scope value is s1,s2 // Scope value is s1,s2
assert.Contains(t, join(s1, s2), scope[SharePointSite.String()].Target) 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})) sel.Include(sel.WebURL([]string{s1, s2}))
scopes := sel.Includes scopes := sel.Includes
require.Len(t, scopes, 1) require.Len(t, scopes, 2)
for _, sc := range scopes { for _, sc := range scopes {
scopeMustHave( scopeMustHave(
@ -162,7 +162,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs_any
sel := NewSharePointRestore() sel := NewSharePointRestore()
sel.Include(sel.WebURL(test.in)) sel.Include(sel.WebURL(test.in))
scopes := sel.Includes scopes := sel.Includes
require.Len(t, scopes, 1) require.Len(t, scopes, 2)
for _, sc := range scopes { for _, sc := range scopes {
scopeMustHave( scopeMustHave(
@ -186,7 +186,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_WebURLs() {
sel.Exclude(sel.WebURL([]string{s1, s2})) sel.Exclude(sel.WebURL([]string{s1, s2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Len(t, scopes, 1) require.Len(t, scopes, 2)
for _, sc := range scopes { for _, sc := range scopes {
scopeMustHave( 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() { func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_Sites() {
t := suite.T() t := suite.T()
sel := NewSharePointBackup() sel := NewSharePointBackup()
@ -208,7 +210,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_Sites() {
sel.Include(sel.Sites([]string{s1, s2})) sel.Include(sel.Sites([]string{s1, s2}))
scopes := sel.Includes scopes := sel.Includes
require.Len(t, scopes, 1) require.Len(t, scopes, 2)
for _, sc := range scopes { for _, sc := range scopes {
scopeMustHave( scopeMustHave(
@ -230,7 +232,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_Sites() {
sel.Exclude(sel.Sites([]string{s1, s2})) sel.Exclude(sel.Sites([]string{s1, s2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Len(t, scopes, 1) require.Len(t, scopes, 2)
for _, sc := range scopes { for _, sc := range scopes {
scopeMustHave( scopeMustHave(
@ -352,18 +354,46 @@ func (suite *SharePointSelectorSuite) TestSharePointRestore_Reduce() {
func (suite *SharePointSelectorSuite) TestSharePointCategory_PathValues() { func (suite *SharePointSelectorSuite) TestSharePointCategory_PathValues() {
t := suite.T() t := suite.T()
pathBuilder := path.Builder{}.Append("dir1", "dir2", "item") pathBuilder := path.Builder{}.Append("dir1", "dir2", "item")
itemPath, err := pathBuilder.ToDataLayerSharePointPath("tenant", "site", path.LibrariesCategory, true) ten := "tenant"
require.NoError(t, err) site := "site"
table := []struct {
expected := map[categorizer]string{ name string
SharePointSite: "site", sc sharePointCategory
expected map[categorizer]string
}{
{
name: "SharePoint Libraries",
sc: SharePointLibraryItem,
expected: map[categorizer]string{
SharePointSite: site,
SharePointLibrary: "dir1/dir2", SharePointLibrary: "dir1/dir2",
SharePointLibraryItem: "item", 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() { func (suite *SharePointSelectorSuite) TestSharePointScope_MatchesInfo() {
@ -418,6 +448,7 @@ func (suite *SharePointSelectorSuite) TestCategory_PathType() {
{SharePointSite, path.UnknownCategory}, {SharePointSite, path.UnknownCategory},
{SharePointLibrary, path.LibrariesCategory}, {SharePointLibrary, path.LibrariesCategory},
{SharePointLibraryItem, path.LibrariesCategory}, {SharePointLibraryItem, path.LibrariesCategory},
{SharePointList, path.ListsCategory},
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.cat.String(), func(t *testing.T) { suite.T().Run(test.cat.String(), func(t *testing.T) {