package selectors import ( "strings" "testing" "time" "github.com/alcionai/clues" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "golang.org/x/exp/slices" "github.com/alcionai/corso/src/internal/common/dttm" odConsts "github.com/alcionai/corso/src/internal/m365/onedrive/consts" "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" ) type SharePointSelectorSuite struct { tester.Suite } func TestSharePointSelectorSuite(t *testing.T) { suite.Run(t, &SharePointSelectorSuite{Suite: tester.NewUnitSuite(t)}) } func (suite *SharePointSelectorSuite) TestNewSharePointBackup() { t := suite.T() ob := NewSharePointBackup(nil) assert.Equal(t, ob.Service, ServiceSharePoint) assert.NotZero(t, ob.Scopes()) } func (suite *SharePointSelectorSuite) TestToSharePointBackup() { t := suite.T() ob := NewSharePointBackup(nil) s := ob.Selector ob, err := s.ToSharePointBackup() require.NoError(t, err, clues.ToCore(err)) assert.Equal(t, ob.Service, ServiceSharePoint) assert.NotZero(t, ob.Scopes()) } func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs() { t := suite.T() const ( s1 = "s1" s2 = "s2" ) s12 := []string{s1, s2} sel := NewSharePointRestore(s12) sel.Include(sel.WebURL(s12)) scopes := sel.Includes require.Len(t, scopes, 3) for _, sc := range scopes { scopeMustHave( t, SharePointScope(sc), map[categorizer][]string{SharePointWebURL: s12}, ) } } func (suite *SharePointSelectorSuite) TestSharePointSelector_Include_WebURLs_anyNone() { table := []struct { name string in []string expect []string }{ { name: "any", in: []string{AnyTgt}, expect: Any(), }, { name: "none", in: []string{NoneTgt}, expect: None(), }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() sel := NewSharePointRestore(Any()) sel.Include(sel.WebURL(test.in)) scopes := sel.Includes require.Len(t, scopes, 3) for _, sc := range scopes { scopeMustHave( t, SharePointScope(sc), map[categorizer][]string{SharePointWebURL: test.expect}, ) } }) } } func (suite *SharePointSelectorSuite) TestSharePointSelector_Exclude_WebURLs() { t := suite.T() const ( s1 = "s1" s2 = "s2" ) s12 := []string{s1, s2} sel := NewSharePointRestore(s12) sel.Exclude(sel.WebURL(s12)) scopes := sel.Excludes require.Len(t, scopes, 3) for _, sc := range scopes { scopeMustHave( t, SharePointScope(sc), map[categorizer][]string{SharePointWebURL: s12}, ) } } func (suite *SharePointSelectorSuite) TestNewSharePointRestore() { t := suite.T() or := NewSharePointRestore(nil) assert.Equal(t, or.Service, ServiceSharePoint) assert.NotZero(t, or.Scopes()) } func (suite *SharePointSelectorSuite) TestToSharePointRestore() { t := suite.T() eb := NewSharePointRestore(nil) s := eb.Selector or, err := s.ToSharePointRestore() require.NoError(t, err, clues.ToCore(err)) assert.Equal(t, or.Service, ServiceSharePoint) assert.NotZero(t, or.Scopes()) } func (suite *SharePointSelectorSuite) TestSharePointRestore_Reduce() { toRR := func(cat path.CategoryType, siteID string, folders []string, item string) string { folderElems := make([]string, 0, len(folders)) for _, f := range folders { folderElems = append(folderElems, f+".d") } return stubRepoRef( path.SharePointService, cat, siteID, strings.Join(folderElems, "/"), item) } var ( prefixElems = []string{ odConsts.DrivesPathDir, "drive!id", odConsts.RootPathDir, } itemElems1 = []string{"folderA", "folderB"} itemElems2 = []string{"folderA", "folderC"} itemElems3 = []string{"folderD", "folderE"} pairAC = "folderA/folderC" pairGH = "folderG/folderH" item = toRR( path.LibrariesCategory, "sid", append(slices.Clone(prefixElems), itemElems1...), "item") item2 = toRR( path.LibrariesCategory, "sid", append(slices.Clone(prefixElems), itemElems2...), "item2") item3 = toRR( path.LibrariesCategory, "sid", append(slices.Clone(prefixElems), itemElems3...), "item3") item4 = stubRepoRef(path.SharePointService, path.PagesCategory, "sid", pairGH, "item4") item5 = stubRepoRef(path.SharePointService, path.PagesCategory, "sid", pairGH, "item5") ) deets := &details.Details{ DetailsModel: details.DetailsModel{ Entries: []details.Entry{ { RepoRef: item, ItemRef: "item", LocationRef: strings.Join(append([]string{odConsts.RootPathDir}, itemElems1...), "/"), ItemInfo: details.ItemInfo{ SharePoint: &details.SharePointInfo{ ItemType: details.SharePointLibrary, ItemName: "itemName", ParentPath: strings.Join(itemElems1, "/"), }, }, }, { RepoRef: item2, LocationRef: strings.Join(append([]string{odConsts.RootPathDir}, itemElems2...), "/"), // ItemRef intentionally blank to test fallback case ItemInfo: details.ItemInfo{ SharePoint: &details.SharePointInfo{ ItemType: details.SharePointLibrary, ItemName: "itemName2", ParentPath: strings.Join(itemElems2, "/"), }, }, }, { RepoRef: item3, ItemRef: "item3", LocationRef: strings.Join(append([]string{odConsts.RootPathDir}, itemElems3...), "/"), ItemInfo: details.ItemInfo{ SharePoint: &details.SharePointInfo{ ItemType: details.SharePointLibrary, ItemName: "itemName3", ParentPath: strings.Join(itemElems3, "/"), }, }, }, { RepoRef: item4, LocationRef: pairGH, ItemRef: "item4", ItemInfo: details.ItemInfo{ SharePoint: &details.SharePointInfo{ ItemType: details.SharePointPage, ItemName: "itemName4", ParentPath: pairGH, }, }, }, { RepoRef: item5, LocationRef: pairGH, // ItemRef intentionally blank to test fallback case ItemInfo: details.ItemInfo{ SharePoint: &details.SharePointInfo{ ItemType: details.SharePointPage, ItemName: "itemName5", ParentPath: pairGH, }, }, }, }, }, } arr := func(s ...string) []string { return s } table := []struct { name string makeSelector func() *SharePointRestore expect []string cfg Config }{ { name: "all", makeSelector: func() *SharePointRestore { odr := NewSharePointRestore(Any()) odr.Include(odr.AllData()) return odr }, expect: arr(item, item2, item3, item4, item5), }, { name: "only match item", makeSelector: func() *SharePointRestore { odr := NewSharePointRestore(Any()) odr.Include(odr.LibraryItems(Any(), []string{"item2"})) return odr }, expect: arr(item2), }, { name: "id doesn't match name", makeSelector: func() *SharePointRestore { odr := NewSharePointRestore(Any()) odr.Include(odr.LibraryItems(Any(), []string{"item2"})) return odr }, expect: []string{}, cfg: Config{OnlyMatchItemNames: true}, }, { name: "only match item name", makeSelector: func() *SharePointRestore { odr := NewSharePointRestore(Any()) odr.Include(odr.LibraryItems(Any(), []string{"itemName2"})) return odr }, expect: arr(item2), cfg: Config{OnlyMatchItemNames: true}, }, { name: "name doesn't match", makeSelector: func() *SharePointRestore { odr := NewSharePointRestore(Any()) odr.Include(odr.LibraryItems(Any(), []string{"itemName2"})) return odr }, expect: []string{}, }, { name: "only match folder", makeSelector: func() *SharePointRestore { odr := NewSharePointRestore([]string{"sid"}) odr.Include(odr.LibraryFolders([]string{"folderA/folderB", pairAC})) return odr }, expect: arr(item, item2), }, { name: "pages match folder", makeSelector: func() *SharePointRestore { odr := NewSharePointRestore([]string{"sid"}) odr.Include(odr.Pages([]string{pairGH, pairAC})) return odr }, expect: arr(item4, item5), }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() sel := test.makeSelector() sel.Configure(test.cfg) results := sel.Reduce(ctx, deets, fault.New(true)) paths := results.Paths() assert.Equal(t, test.expect, paths) }) } } func (suite *SharePointSelectorSuite) TestSharePointCategory_PathValues() { var ( itemName = "item" itemID = "item-id" shortRef = "short" driveElems = []string{ odConsts.DrivesPathDir, "drive!id", odConsts.RootPathDir + ".d", "dir1.d", "dir2.d", itemID, } elems = []string{"dir1", "dir2", itemID} ) table := []struct { name string sc sharePointCategory pathElems []string locRef string parentPath string expected map[categorizer][]string cfg Config }{ { name: "SharePoint Libraries", sc: SharePointLibraryItem, pathElems: driveElems, locRef: "root:/dir1/dir2", parentPath: "dir1/dir2", expected: map[categorizer][]string{ SharePointLibraryFolder: {"dir1/dir2"}, SharePointLibraryItem: {itemID, shortRef}, }, cfg: Config{}, }, { name: "SharePoint Libraries w/ name", sc: SharePointLibraryItem, pathElems: driveElems, locRef: "root:/dir1/dir2", parentPath: "dir1/dir2", expected: map[categorizer][]string{ SharePointLibraryFolder: {"dir1/dir2"}, SharePointLibraryItem: {itemName, shortRef}, }, cfg: Config{OnlyMatchItemNames: true}, }, { name: "SharePoint Lists", sc: SharePointListItem, pathElems: elems, locRef: "dir1/dir2", expected: map[categorizer][]string{ SharePointList: {"dir1/dir2"}, SharePointListItem: {itemID, shortRef}, }, cfg: Config{}, }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() itemPath, err := path.Build( "tenant", "site", path.SharePointService, test.sc.PathType(), true, test.pathElems...) require.NoError(t, err, clues.ToCore(err)) ent := details.Entry{ RepoRef: itemPath.String(), ShortRef: shortRef, ItemRef: itemPath.Item(), LocationRef: test.locRef, ItemInfo: details.ItemInfo{ SharePoint: &details.SharePointInfo{ ItemName: itemName, ParentPath: test.parentPath, }, }, } pv, err := test.sc.pathValues(itemPath, ent, test.cfg) require.NoError(t, err) assert.Equal(t, test.expected, pv) }) } } func (suite *SharePointSelectorSuite) TestSharePointScope_MatchesInfo() { var ( sel = NewSharePointRestore(Any()) host = "www.website.com" pth = "/foo" url = host + pth epoch = time.Time{} now = time.Now() modification = now.Add(15 * time.Minute) future = now.Add(45 * time.Minute) ) table := []struct { name string infoURL string scope []SharePointScope expect assert.BoolAssertionFunc }{ {"host match", host, sel.WebURL([]string{host}), assert.True}, {"url match", url, sel.WebURL([]string{url}), assert.True}, {"host suffixes host", host, sel.WebURL([]string{host}, SuffixMatch()), assert.True}, {"url does not suffix host", url, sel.WebURL([]string{host}, SuffixMatch()), assert.False}, {"url has path suffix", url, sel.WebURL([]string{pth}, SuffixMatch()), assert.True}, {"host does not contain substring", host, sel.WebURL([]string{"website"}), assert.False}, {"url does not suffix substring", url, sel.WebURL([]string{"oo"}, SuffixMatch()), assert.False}, {"host mismatch", host, sel.WebURL([]string{"www.google.com"}), assert.False}, {"file create after the epoch", host, sel.CreatedAfter(dttm.Format(epoch)), assert.True}, {"file create after now", host, sel.CreatedAfter(dttm.Format(now)), assert.False}, {"file create after later", url, sel.CreatedAfter(dttm.Format(future)), assert.False}, {"file create before future", host, sel.CreatedBefore(dttm.Format(future)), assert.True}, {"file create before now", host, sel.CreatedBefore(dttm.Format(now)), assert.False}, {"file create before modification", host, sel.CreatedBefore(dttm.Format(modification)), assert.True}, {"file create before epoch", host, sel.CreatedBefore(dttm.Format(now)), assert.False}, {"file modified after the epoch", host, sel.ModifiedAfter(dttm.Format(epoch)), assert.True}, {"file modified after now", host, sel.ModifiedAfter(dttm.Format(now)), assert.True}, {"file modified after later", host, sel.ModifiedAfter(dttm.Format(future)), assert.False}, {"file modified before future", host, sel.ModifiedBefore(dttm.Format(future)), assert.True}, {"file modified before now", host, sel.ModifiedBefore(dttm.Format(now)), assert.False}, {"file modified before epoch", host, sel.ModifiedBefore(dttm.Format(now)), assert.False}, {"in library", host, sel.Library("included-library"), assert.True}, {"not in library", host, sel.Library("not-included-library"), assert.False}, {"library id", host, sel.Library("1234"), assert.True}, {"not library id", host, sel.Library("abcd"), assert.False}, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() itemInfo := details.ItemInfo{ SharePoint: &details.SharePointInfo{ ItemType: details.SharePointPage, WebURL: test.infoURL, Created: now, Modified: modification, DriveName: "included-library", DriveID: "1234", }, } scopes := setScopesToDefault(test.scope) for _, scope := range scopes { test.expect(t, scope.matchesInfo(itemInfo)) } }) } } func (suite *SharePointSelectorSuite) TestCategory_PathType() { table := []struct { cat sharePointCategory pathType path.CategoryType }{ {SharePointCategoryUnknown, path.UnknownCategory}, {SharePointWebURL, path.UnknownCategory}, {SharePointSite, path.UnknownCategory}, {SharePointLibraryFolder, path.LibrariesCategory}, {SharePointLibraryItem, path.LibrariesCategory}, {SharePointList, path.ListsCategory}, } for _, test := range table { suite.Run(test.cat.String(), func() { assert.Equal(suite.T(), test.pathType, test.cat.PathType()) }) } }