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

View File

@ -17,6 +17,8 @@ func TestSharePointUtilsSuite(t *testing.T) {
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() {
var (
empty = []string{}
@ -34,13 +36,8 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
}{
{
name: "no inputs",
opts: utils.SharePointOpts{
LibraryItems: empty,
LibraryPaths: empty,
Sites: empty,
WebURLs: empty,
},
expectIncludeLen: 2,
opts: utils.SharePointOpts{},
expectIncludeLen: 3,
},
{
name: "single inputs",
@ -50,7 +47,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: single,
WebURLs: single,
},
expectIncludeLen: 3,
expectIncludeLen: 4,
},
{
name: "single extended",
@ -62,7 +59,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: single,
WebURLs: single,
},
expectIncludeLen: 4,
expectIncludeLen: 5,
},
{
name: "multi inputs",
@ -72,7 +69,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: multi,
WebURLs: multi,
},
expectIncludeLen: 3,
expectIncludeLen: 4,
},
{
name: "library contains",
@ -138,7 +135,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty,
WebURLs: containsOnly,
},
expectIncludeLen: 2,
expectIncludeLen: 3,
},
{
name: "library suffixes",
@ -148,7 +145,7 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty,
WebURLs: prefixOnly, // prefix pattern matches suffix pattern
},
expectIncludeLen: 2,
expectIncludeLen: 3,
},
{
name: "library suffixes and contains",
@ -158,7 +155,29 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
Sites: empty,
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 {

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,
urlSuffixes,
pathFilterFactory(opts...)),
makeFilterScope[SharePointScope](
SharePointPage,
SharePointWebURL,
urlSuffixes,
pathFilterFactory(opts...)),
)
return scopes
@ -219,6 +224,7 @@ func (s *sharePoint) AllData() []SharePointScope {
scopes,
makeScope[SharePointScope](SharePointLibrary, Any()),
makeScope[SharePointScope](SharePointList, Any()),
makeScope[SharePointScope](SharePointPage, Any()),
)
return scopes
@ -291,6 +297,38 @@ func (s *sharePoint) LibraryItems(libraries, items []string, opts ...option) []S
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
@ -315,6 +353,8 @@ const (
SharePointListItem sharePointCategory = "SharePointListItem"
SharePointLibrary sharePointCategory = "SharePointLibrary"
SharePointLibraryItem sharePointCategory = "SharePointLibraryItem"
SharePointPageFolder sharePointCategory = "SharePointPageFolder"
SharePointPage sharePointCategory = "SharePointPage"
// filterable topics identified by SharePoint
)
@ -325,14 +365,18 @@ var sharePointLeafProperties = map[categorizer]leafProperty{
pathKeys: []categorizer{SharePointLibrary, SharePointLibraryItem},
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
pathKeys: []categorizer{SharePointSite},
pathType: path.UnknownCategory,
},
SharePointListItem: {
pathKeys: []categorizer{SharePointSite, SharePointList, SharePointListItem},
pathType: path.ListsCategory,
},
}
func (c sharePointCategory) String() string {
@ -350,6 +394,8 @@ func (c sharePointCategory) leafCat() categorizer {
return SharePointLibraryItem
case SharePointList, SharePointListItem:
return SharePointListItem
case SharePointPage, SharePointPageFolder:
return SharePointPage
}
return c
@ -389,6 +435,10 @@ func (c sharePointCategory) pathValues(p path.Path) map[categorizer]string {
folderCat, itemCat = SharePointLibrary, SharePointLibraryItem
case SharePointList, SharePointListItem:
folderCat, itemCat = SharePointList, SharePointListItem
case SharePointPage, SharePointPageFolder:
folderCat, itemCat = SharePointPageFolder, SharePointPage
default:
return map[categorizer]string{}
}
return map[categorizer]string{
@ -429,6 +479,12 @@ func (s SharePointScope) categorizer() categorizer {
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.
// If the scope is not a filter type, returns SharePointUnknownCategory.
func (s SharePointScope) FilterCategory() sharePointCategory {
@ -443,12 +499,6 @@ func (s SharePointScope) IncludesCategory(cat sharePointCategory) bool {
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,
// and the value is set to Any().
func (s SharePointScope) IsAny(cat sharePointCategory) bool {
@ -467,7 +517,7 @@ func (s SharePointScope) set(cat sharePointCategory, v []string, opts ...option)
os := []option{}
switch cat {
case SharePointLibrary, SharePointList:
case SharePointLibrary, SharePointList, SharePointPage:
os = append(os, pathComparator())
}
@ -482,10 +532,14 @@ func (s SharePointScope) setDefaults() {
s[SharePointLibraryItem.String()] = passAny
s[SharePointList.String()] = passAny
s[SharePointListItem.String()] = passAny
s[SharePointPageFolder.String()] = passAny
s[SharePointPage.String()] = passAny
case SharePointLibrary:
s[SharePointLibraryItem.String()] = passAny
case SharePointList:
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{
path.LibrariesCategory: SharePointLibraryItem,
path.ListsCategory: SharePointListItem,
path.PagesCategory: SharePointPage,
},
)
}

View File

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