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,
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)
})
}
}

View File

@ -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),
)
}

View File

@ -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()

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 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,
},
)
}

View File

@ -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) {