Selectors: Add selectors for new sharepoint category (#2111)

## Description
Expands SharePoint Selectors to include `SharePoint.Pages` within the Corso package.<!-- Insert PR description-->

## Does this PR need a docs update or release note?

- [x]  Yes, it's included


## Type of change

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

Prerequisites
-----
- [ ] Merge #2114 
- [x] Resolve #2112
## Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* closes #2110<issue>
* related #2107

## Test Plan
Testing will 
- [x]  Unit test
This commit is contained in:
Danny 2023-01-18 21:27:40 -05:00 committed by GitHub
parent 51e29f2975
commit 9f185a9b41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 253 additions and 30 deletions

View File

@ -11,6 +11,8 @@ const (
LibraryFN = "library" LibraryFN = "library"
ListItemFN = "list-item" ListItemFN = "list-item"
ListFN = "list" ListFN = "list"
PageFN = "page"
PageItemFN = "page-item"
WebURLFN = "web-url" WebURLFN = "web-url"
) )
@ -19,6 +21,8 @@ type SharePointOpts struct {
LibraryPaths []string LibraryPaths []string
ListItems []string ListItems []string
ListPaths []string ListPaths []string
PageFolders []string
Pages []string
Sites []string Sites []string
WebURLs []string WebURLs []string
@ -60,6 +64,7 @@ func IncludeSharePointRestoreDataSelectors(opts SharePointOpts) *selectors.Share
lp, li := len(opts.LibraryPaths), len(opts.LibraryItems) lp, li := len(opts.LibraryPaths), len(opts.LibraryItems)
ls, lwu := len(opts.Sites), len(opts.WebURLs) ls, lwu := len(opts.Sites), len(opts.WebURLs)
slp, sli := len(opts.ListPaths), len(opts.ListItems) slp, sli := len(opts.ListPaths), len(opts.ListItems)
pf, pi := len(opts.PageFolders), len(opts.Pages)
if ls == 0 { if ls == 0 {
sites = selectors.Any() sites = selectors.Any()
@ -67,7 +72,7 @@ func IncludeSharePointRestoreDataSelectors(opts SharePointOpts) *selectors.Share
sel := selectors.NewSharePointRestore(sites) sel := selectors.NewSharePointRestore(sites)
if lp+li+lwu+slp+sli == 0 { if lp+li+lwu+slp+sli+pf+pi == 0 {
sel.Include(sel.AllData()) sel.Include(sel.AllData())
return sel return sel
} }
@ -106,6 +111,23 @@ func IncludeSharePointRestoreDataSelectors(opts SharePointOpts) *selectors.Share
} }
} }
if pf+pi > 0 {
if pi == 0 {
opts.Pages = selectors.Any()
}
opts.PageFolders = trimFolderSlash(opts.PageFolders)
containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.PageFolders)
if len(containsFolders) > 0 {
sel.Include(sel.PageItems(containsFolders, opts.Pages))
}
if len(prefixFolders) > 0 {
sel.Include(sel.PageItems(prefixFolders, opts.Pages, selectors.PrefixMatch()))
}
}
if lwu > 0 { if lwu > 0 {
opts.WebURLs = trimFolderSlash(opts.WebURLs) opts.WebURLs = trimFolderSlash(opts.WebURLs)
containsURLs, suffixURLs := splitFoldersIntoContainsAndPrefix(opts.WebURLs) containsURLs, suffixURLs := splitFoldersIntoContainsAndPrefix(opts.WebURLs)

View File

@ -17,6 +17,8 @@ func TestSharePointUtilsSuite(t *testing.T) {
suite.Run(t, new(SharePointUtilsSuite)) suite.Run(t, new(SharePointUtilsSuite))
} }
// Tests selector build for SharePoint properly
// differentiates between the 3 categories: Pages, Libraries and Lists CLI
func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() { func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
var ( var (
empty = []string{} empty = []string{}
@ -34,13 +36,8 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
}{ }{
{ {
name: "no inputs", name: "no inputs",
opts: utils.SharePointOpts{ opts: utils.SharePointOpts{},
LibraryItems: empty, expectIncludeLen: 3,
LibraryPaths: empty,
Sites: empty,
WebURLs: empty,
},
expectIncludeLen: 2,
}, },
{ {
name: "single inputs", name: "single inputs",
@ -50,7 +47,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: single, Sites: single,
WebURLs: single, WebURLs: single,
}, },
expectIncludeLen: 3, expectIncludeLen: 4,
}, },
{ {
name: "single extended", name: "single extended",
@ -62,7 +59,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: single, Sites: single,
WebURLs: single, WebURLs: single,
}, },
expectIncludeLen: 4, expectIncludeLen: 5,
}, },
{ {
name: "multi inputs", name: "multi inputs",
@ -72,7 +69,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: multi, Sites: multi,
WebURLs: multi, WebURLs: multi,
}, },
expectIncludeLen: 3, expectIncludeLen: 4,
}, },
{ {
name: "library contains", name: "library contains",
@ -138,7 +135,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty, Sites: empty,
WebURLs: containsOnly, WebURLs: containsOnly,
}, },
expectIncludeLen: 2, expectIncludeLen: 3,
}, },
{ {
name: "library suffixes", name: "library suffixes",
@ -148,7 +145,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty, Sites: empty,
WebURLs: prefixOnly, // prefix pattern matches suffix pattern WebURLs: prefixOnly, // prefix pattern matches suffix pattern
}, },
expectIncludeLen: 2, expectIncludeLen: 3,
}, },
{ {
name: "library suffixes and contains", name: "library suffixes and contains",
@ -158,7 +155,29 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty, Sites: empty,
WebURLs: containsAndPrefix, // prefix pattern matches suffix pattern WebURLs: containsAndPrefix, // prefix pattern matches suffix pattern
}, },
expectIncludeLen: 4, expectIncludeLen: 6,
},
{
name: "Page Folder",
opts: utils.SharePointOpts{
PageFolders: single,
},
expectIncludeLen: 1,
},
{
name: "Site Page ",
opts: utils.SharePointOpts{
Pages: single,
},
expectIncludeLen: 1,
},
{
name: "Page & Library",
opts: utils.SharePointOpts{
PageFolders: single,
LibraryItems: multi,
},
expectIncludeLen: 2,
}, },
} }
for _, test := range table { for _, test := range table {

View File

@ -239,3 +239,130 @@ func (suite *SelectorSuite) TestSplitByResourceOnwer() {
}) })
} }
} }
// TestPathCategories verifies that no scope produces a `path.UnknownCategory`
func (suite *SelectorSuite) TestPathCategories_includes() {
users := []string{"someuser@onmicrosoft.com"}
table := []struct {
name string
getSelector func(t *testing.T) *Selector
isErr assert.ErrorAssertionFunc
}{
{
name: "empty",
isErr: assert.Error,
getSelector: func(t *testing.T) *Selector {
return &Selector{}
},
},
{
name: "Mail_B",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewExchangeBackup(users)
sel.Include(sel.MailFolders([]string{"MailFolder"}, PrefixMatch()))
sel.Mails([]string{"MailFolder2"}, []string{"Mail"})
return &sel.Selector
},
},
{
name: "Mail_R",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewExchangeRestore(users)
sel.Include(sel.MailFolders([]string{"MailFolder"}, PrefixMatch()))
return &sel.Selector
},
},
{
name: "Contacts",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewExchangeBackup(users)
sel.Include(sel.ContactFolders([]string{"Contact Folder"}, PrefixMatch()))
return &sel.Selector
},
},
{
name: "Contacts_R",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewExchangeRestore(users)
sel.Include(sel.ContactFolders([]string{"Contact Folder"}, PrefixMatch()))
return &sel.Selector
},
},
{
name: "Events",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewExchangeBackup(users)
sel.Include(sel.EventCalendars([]string{"July"}, PrefixMatch()))
return &sel.Selector
},
},
{
name: "Events_R",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewExchangeRestore(users)
sel.Include(sel.EventCalendars([]string{"July"}, PrefixMatch()))
sel.EventCalendars([]string{"Independence Day EventID"})
return &sel.Selector
},
},
{
name: "SharePoint Pages",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewSharePointBackup(users)
sel.Include(sel.Pages([]string{"Something"}, SuffixMatch()))
sel.PageItems([]string{"Home Directory"}, []string{"Event Page"})
return &sel.Selector
},
},
{
name: "SharePoint Lists",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewSharePointBackup(users)
sel.Include(sel.Lists([]string{"Lists from website"}, SuffixMatch()))
return &sel.Selector
},
},
{
name: "SharePoint Libraries",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewSharePointBackup(users)
sel.Include(sel.Libraries([]string{"A directory"}, SuffixMatch()))
return &sel.Selector
},
},
{
name: "OneDrive",
isErr: assert.NoError,
getSelector: func(t *testing.T) *Selector {
sel := NewOneDriveBackup(users)
sel.Include(sel.Folders([]string{"Single Folder"}, PrefixMatch()))
return &sel.Selector
},
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
obj := test.getSelector(t)
cats, err := obj.PathCategories()
for _, entry := range cats.Includes {
assert.NotEqual(t, entry, path.UnknownCategory)
}
test.isErr(t, err)
})
}
}

View File

@ -202,6 +202,11 @@ func (s *SharePointRestore) WebURL(urlSuffixes []string, opts ...option) []Share
SharePointWebURL, SharePointWebURL,
urlSuffixes, urlSuffixes,
pathFilterFactory(opts...)), pathFilterFactory(opts...)),
makeFilterScope[SharePointScope](
SharePointPage,
SharePointWebURL,
urlSuffixes,
pathFilterFactory(opts...)),
) )
return scopes return scopes
@ -219,6 +224,7 @@ func (s *sharePoint) AllData() []SharePointScope {
scopes, scopes,
makeScope[SharePointScope](SharePointLibrary, Any()), makeScope[SharePointScope](SharePointLibrary, Any()),
makeScope[SharePointScope](SharePointList, Any()), makeScope[SharePointScope](SharePointList, Any()),
makeScope[SharePointScope](SharePointPage, Any()),
) )
return scopes return scopes
@ -291,6 +297,38 @@ func (s *sharePoint) LibraryItems(libraries, items []string, opts ...option) []S
return scopes return scopes
} }
// Pages produces one or more SharePoint page 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]
func (s *sharePoint) Pages(pages []string, opts ...option) []SharePointScope {
var (
scopes = []SharePointScope{}
os = append([]option{pathComparator()}, opts...)
)
scopes = append(scopes, makeScope[SharePointScope](SharePointPageFolder, pages, os...))
return scopes
}
// PageItems produces one or more SharePoint page 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 page scopes.
func (s *sharePoint) PageItems(pages, items []string, opts ...option) []SharePointScope {
scopes := []SharePointScope{}
scopes = append(
scopes,
makeScope[SharePointScope](SharePointPage, items).
set(SharePointPage, pages, opts...),
)
return scopes
}
// ------------------- // -------------------
// Filter Factories // Filter Factories
@ -315,6 +353,8 @@ const (
SharePointListItem sharePointCategory = "SharePointListItem" SharePointListItem sharePointCategory = "SharePointListItem"
SharePointLibrary sharePointCategory = "SharePointLibrary" SharePointLibrary sharePointCategory = "SharePointLibrary"
SharePointLibraryItem sharePointCategory = "SharePointLibraryItem" SharePointLibraryItem sharePointCategory = "SharePointLibraryItem"
SharePointPageFolder sharePointCategory = "SharePointPageFolder"
SharePointPage sharePointCategory = "SharePointPage"
// filterable topics identified by SharePoint // filterable topics identified by SharePoint
) )
@ -325,14 +365,18 @@ var sharePointLeafProperties = map[categorizer]leafProperty{
pathKeys: []categorizer{SharePointLibrary, SharePointLibraryItem}, pathKeys: []categorizer{SharePointLibrary, SharePointLibraryItem},
pathType: path.LibrariesCategory, pathType: path.LibrariesCategory,
}, },
SharePointListItem: {
pathKeys: []categorizer{SharePointList, SharePointListItem},
pathType: path.ListsCategory,
},
SharePointPage: {
pathKeys: []categorizer{SharePointPageFolder, SharePointPage},
pathType: path.PagesCategory,
},
SharePointSite: { // the root category must be represented, even though it isn't a leaf SharePointSite: { // the root category must be represented, even though it isn't a leaf
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 {
@ -350,6 +394,8 @@ func (c sharePointCategory) leafCat() categorizer {
return SharePointLibraryItem return SharePointLibraryItem
case SharePointList, SharePointListItem: case SharePointList, SharePointListItem:
return SharePointListItem return SharePointListItem
case SharePointPage, SharePointPageFolder:
return SharePointPage
} }
return c return c
@ -389,6 +435,10 @@ func (c sharePointCategory) pathValues(p path.Path) map[categorizer]string {
folderCat, itemCat = SharePointLibrary, SharePointLibraryItem folderCat, itemCat = SharePointLibrary, SharePointLibraryItem
case SharePointList, SharePointListItem: case SharePointList, SharePointListItem:
folderCat, itemCat = SharePointList, SharePointListItem folderCat, itemCat = SharePointList, SharePointListItem
case SharePointPage, SharePointPageFolder:
folderCat, itemCat = SharePointPageFolder, SharePointPage
default:
return map[categorizer]string{}
} }
return map[categorizer]string{ return map[categorizer]string{
@ -429,6 +479,12 @@ func (s SharePointScope) categorizer() categorizer {
return s.Category() return s.Category()
} }
// Matches returns true if the category is included in the scope's
// data type, and the target string matches that category's comparator.
func (s SharePointScope) Matches(cat sharePointCategory, target string) bool {
return matches(s, cat, target)
}
// FilterCategory returns the category enum of the scope filter. // FilterCategory returns the category enum of the scope filter.
// If the scope is not a filter type, returns SharePointUnknownCategory. // If the scope is not a filter type, returns SharePointUnknownCategory.
func (s SharePointScope) FilterCategory() sharePointCategory { func (s SharePointScope) FilterCategory() sharePointCategory {
@ -443,12 +499,6 @@ func (s SharePointScope) IncludesCategory(cat sharePointCategory) bool {
return categoryMatches(s.Category(), cat) return categoryMatches(s.Category(), cat)
} }
// Matches returns true if the category is included in the scope's
// data type, and the target string matches that category's comparator.
func (s SharePointScope) Matches(cat sharePointCategory, target string) bool {
return matches(s, cat, target)
}
// returns true if the category is included in the scope's data type, // returns true if the category is included in the scope's data type,
// and the value is set to Any(). // and the value is set to Any().
func (s SharePointScope) IsAny(cat sharePointCategory) bool { func (s SharePointScope) IsAny(cat sharePointCategory) bool {
@ -467,7 +517,7 @@ func (s SharePointScope) set(cat sharePointCategory, v []string, opts ...option)
os := []option{} os := []option{}
switch cat { switch cat {
case SharePointLibrary, SharePointList: case SharePointLibrary, SharePointList, SharePointPage:
os = append(os, pathComparator()) os = append(os, pathComparator())
} }
@ -482,10 +532,14 @@ func (s SharePointScope) setDefaults() {
s[SharePointLibraryItem.String()] = passAny s[SharePointLibraryItem.String()] = passAny
s[SharePointList.String()] = passAny s[SharePointList.String()] = passAny
s[SharePointListItem.String()] = passAny s[SharePointListItem.String()] = passAny
s[SharePointPageFolder.String()] = passAny
s[SharePointPage.String()] = passAny
case SharePointLibrary: case SharePointLibrary:
s[SharePointLibraryItem.String()] = passAny s[SharePointLibraryItem.String()] = passAny
case SharePointList: case SharePointList:
s[SharePointListItem.String()] = passAny s[SharePointListItem.String()] = passAny
case SharePointPageFolder:
s[SharePointPage.String()] = passAny
} }
} }
@ -509,6 +563,7 @@ func (s sharePoint) Reduce(ctx context.Context, deets *details.Details) *details
map[path.CategoryType]sharePointCategory{ map[path.CategoryType]sharePointCategory{
path.LibrariesCategory: SharePointLibraryItem, path.LibrariesCategory: SharePointLibraryItem,
path.ListsCategory: SharePointListItem, path.ListsCategory: SharePointListItem,
path.PagesCategory: SharePointPage,
}, },
) )
} }

View File

@ -61,7 +61,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_AllData() {
{"Filter Scopes", sel.Filters}, {"Filter Scopes", sel.Filters},
} }
for _, test := range table { for _, test := range table {
require.Len(t, test.scopesToCheck, 2) require.Len(t, test.scopesToCheck, 3)
for _, scope := range test.scopesToCheck { for _, scope := range test.scopesToCheck {
var ( var (
@ -106,7 +106,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs() {
sel := NewSharePointRestore([]string{s1, s2}) sel := NewSharePointRestore([]string{s1, s2})
sel.Include(sel.WebURL([]string{s1, s2})) sel.Include(sel.WebURL([]string{s1, s2}))
scopes := sel.Includes scopes := sel.Includes
require.Len(t, scopes, 2) require.Len(t, scopes, 3)
for _, sc := range scopes { for _, sc := range scopes {
scopeMustHave( scopeMustHave(
@ -139,7 +139,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs_any
sel := NewSharePointRestore(Any()) sel := NewSharePointRestore(Any())
sel.Include(sel.WebURL(test.in)) sel.Include(sel.WebURL(test.in))
scopes := sel.Includes scopes := sel.Includes
require.Len(t, scopes, 2) require.Len(t, scopes, 3)
for _, sc := range scopes { for _, sc := range scopes {
scopeMustHave( scopeMustHave(
@ -163,7 +163,7 @@ func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_WebURLs() {
sel := NewSharePointRestore([]string{s1, s2}) sel := NewSharePointRestore([]string{s1, s2})
sel.Exclude(sel.WebURL([]string{s1, s2})) sel.Exclude(sel.WebURL([]string{s1, s2}))
scopes := sel.Excludes scopes := sel.Excludes
require.Len(t, scopes, 2) require.Len(t, scopes, 3)
for _, sc := range scopes { for _, sc := range scopes {
scopeMustHave( scopeMustHave(