Scope OneDrive backup - for testing (#1109)

## Description

<!-- Insert PR description-->

## Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Test
- [ ] 💻 CI/Deployment
- [ ] 🐹 Trivial/Minor

## Issue(s)

* closes #1108 
* closes #1137 

## Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
Vaibhav Kamra 2022-10-13 10:24:42 -07:00 committed by GitHub
parent f1f6c06ba0
commit 1cbb34e403
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 92 deletions

View File

@ -522,6 +522,7 @@ func (gc *GraphConnector) OneDriveDataCollections(
odcs, err := onedrive.NewCollections( odcs, err := onedrive.NewCollections(
gc.credentials.TenantID, gc.credentials.TenantID,
user, user,
scope,
&gc.graphService, &gc.graphService,
gc.UpdateStatus, gc.UpdateStatus,
).Get(ctx) ).Get(ctx)

View File

@ -117,7 +117,7 @@ func (suite *OneDriveCollectionSuite) TestOneDriveCollectionReadError() {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(1) wg.Add(1)
folderPath, err := getCanonicalPath("folderPath", "a-tenant", "a-user") folderPath, err := getCanonicalPath("drive/driveID1/root:/folderPath", "a-tenant", "a-user")
require.NoError(t, err) require.NoError(t, err)
coll := NewCollection(folderPath, "fakeDriveID", suite, suite.testStatusUpdater(&wg, &collStatus)) coll := NewCollection(folderPath, "fakeDriveID", suite, suite.testStatusUpdater(&wg, &collStatus))

View File

@ -2,7 +2,6 @@ package onedrive
import ( import (
"context" "context"
stdpath "path"
"strings" "strings"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
@ -11,7 +10,9 @@ import (
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/selectors"
) )
// Collections is used to retrieve OneDrive data for a // Collections is used to retrieve OneDrive data for a
@ -19,28 +20,30 @@ import (
type Collections struct { type Collections struct {
tenant string tenant string
user string user string
scope selectors.OneDriveScope
// collectionMap allows lookup of the data.Collection // collectionMap allows lookup of the data.Collection
// for a OneDrive folder // for a OneDrive folder
collectionMap map[string]data.Collection collectionMap map[string]data.Collection
service graph.Service service graph.Service
statusUpdater support.StatusUpdater statusUpdater support.StatusUpdater
// Track stats from drive enumeration // Track stats from drive enumeration. Represents the items backed up.
numItems int numItems int
numDirs int numFiles int
numFiles int numContainers int
numPackages int
} }
func NewCollections( func NewCollections(
tenant string, tenant string,
user string, user string,
scope selectors.OneDriveScope,
service graph.Service, service graph.Service,
statusUpdater support.StatusUpdater, statusUpdater support.StatusUpdater,
) *Collections { ) *Collections {
return &Collections{ return &Collections{
tenant: tenant, tenant: tenant,
user: user, user: user,
scope: scope,
collectionMap: map[string]data.Collection{}, collectionMap: map[string]data.Collection{},
service: service, service: service,
statusUpdater: statusUpdater, statusUpdater: statusUpdater,
@ -96,11 +99,6 @@ func getDriveFolderPath(p path.Path) (string, error) {
// A new collection is created for every OneDrive folder (or package) // A new collection is created for every OneDrive folder (or package)
func (c *Collections) updateCollections(ctx context.Context, driveID string, items []models.DriveItemable) error { func (c *Collections) updateCollections(ctx context.Context, driveID string, items []models.DriveItemable) error {
for _, item := range items { for _, item := range items {
err := c.stats(item)
if err != nil {
return err
}
if item.GetRoot() != nil { if item.GetRoot() != nil {
// Skip the root item // Skip the root item
continue continue
@ -120,43 +118,38 @@ func (c *Collections) updateCollections(ctx context.Context, driveID string, ite
return err return err
} }
if _, found := c.collectionMap[collectionPath.String()]; !found { // Skip items that don't match the folder selectors we were given.
c.collectionMap[collectionPath.String()] = NewCollection( if !includePath(ctx, c.scope, collectionPath) {
collectionPath, logger.Ctx(ctx).Infof("Skipping path %s", collectionPath.String())
driveID, continue
c.service,
c.statusUpdater,
)
} }
switch { switch {
case item.GetFolder() != nil, item.GetPackage() != nil: case item.GetFolder() != nil, item.GetPackage() != nil:
// For folders and packages we also create a collection to represent those // Leave this here so we don't fall into the default case.
// TODO: This is where we might create a "special file" to represent these in the backup repository // TODO: This is where we might create a "special file" to represent these in the backup repository
// e.g. a ".folderMetadataFile" // e.g. a ".folderMetadataFile"
itemPath, err := getCanonicalPath(
stdpath.Join(
*item.GetParentReference().GetPath(),
*item.GetName(),
),
c.tenant,
c.user,
)
if err != nil {
return err
}
if _, found := c.collectionMap[itemPath.String()]; !found { case item.GetFile() != nil:
c.collectionMap[itemPath.String()] = NewCollection( col, found := c.collectionMap[collectionPath.String()]
itemPath, if !found {
col = NewCollection(
collectionPath,
driveID, driveID,
c.service, c.service,
c.statusUpdater, c.statusUpdater,
) )
c.collectionMap[collectionPath.String()] = col
c.numContainers++
c.numItems++
} }
case item.GetFile() != nil:
collection := c.collectionMap[collectionPath.String()].(*Collection) collection := col.(*Collection)
collection.Add(*item.GetId()) collection.Add(*item.GetId())
c.numFiles++
c.numItems++
default: default:
return errors.Errorf("item type not supported. item name : %s", *item.GetName()) return errors.Errorf("item type not supported. item name : %s", *item.GetName())
} }
@ -165,19 +158,21 @@ func (c *Collections) updateCollections(ctx context.Context, driveID string, ite
return nil return nil
} }
func (c *Collections) stats(item models.DriveItemable) error { func includePath(ctx context.Context, scope selectors.OneDriveScope, folderPath path.Path) bool {
switch { // Check if the folder is allowed by the scope.
case item.GetFolder() != nil: folderPathString, err := getDriveFolderPath(folderPath)
c.numDirs++ if err != nil {
case item.GetPackage() != nil: logger.Ctx(ctx).Error(err)
c.numPackages++ return true
case item.GetFile() != nil:
c.numFiles++
default:
return errors.Errorf("item type not supported. item name : %s", *item.GetName())
} }
c.numItems++ // Hack for the edge case where we're looking at the root folder and can
// select any folder. Right now the root folder has an empty folder path.
if len(folderPathString) == 0 && scope.IsAny(selectors.OneDriveFolder) {
return true
}
return nil logger.Ctx(ctx).Infof("Checking path %q", folderPathString)
return scope.Matches(selectors.OneDriveFolder, folderPathString)
} }

View File

@ -10,6 +10,11 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/selectors"
)
const (
testBaseDrivePath = "drive/driveID1/root:"
) )
func expectedPathAsSlice(t *testing.T, tenant, user string, rest ...string) []string { func expectedPathAsSlice(t *testing.T, tenant, user string, rest ...string) []string {
@ -34,94 +39,132 @@ func TestOneDriveCollectionsSuite(t *testing.T) {
} }
func (suite *OneDriveCollectionsSuite) TestUpdateCollections() { func (suite *OneDriveCollectionsSuite) TestUpdateCollections() {
anyFolder := (&selectors.OneDriveBackup{}).Folders(selectors.Any(), selectors.Any())[0]
tenant := "tenant" tenant := "tenant"
user := "user" user := "user"
tests := []struct { tests := []struct {
testCase string testCase string
items []models.DriveItemable items []models.DriveItemable
scope selectors.OneDriveScope
expect assert.ErrorAssertionFunc expect assert.ErrorAssertionFunc
expectedCollectionPaths []string expectedCollectionPaths []string
expectedItemCount int expectedItemCount int
expectedFolderCount int expectedContainerCount int
expectedPackageCount int
expectedFileCount int expectedFileCount int
}{ }{
{ {
testCase: "Invalid item", testCase: "Invalid item",
items: []models.DriveItemable{ items: []models.DriveItemable{
driveItem("item", "/root", false, false, false), driveItem("item", testBaseDrivePath, false, false, false),
}, },
scope: anyFolder,
expect: assert.Error, expect: assert.Error,
}, },
{ {
testCase: "Single File", testCase: "Single File",
items: []models.DriveItemable{ items: []models.DriveItemable{
driveItem("file", "/root", true, false, false), driveItem("file", testBaseDrivePath, true, false, false),
}, },
scope: anyFolder,
expect: assert.NoError, expect: assert.NoError,
expectedCollectionPaths: expectedPathAsSlice( expectedCollectionPaths: expectedPathAsSlice(
suite.T(), suite.T(),
tenant, tenant,
user, user,
"root", testBaseDrivePath,
), ),
expectedItemCount: 1, expectedItemCount: 2,
expectedFileCount: 1, expectedFileCount: 1,
expectedContainerCount: 1,
}, },
{ {
testCase: "Single Folder", testCase: "Single Folder",
items: []models.DriveItemable{ items: []models.DriveItemable{
driveItem("folder", "/root", false, true, false), driveItem("folder", testBaseDrivePath, false, true, false),
}, },
expect: assert.NoError, scope: anyFolder,
expectedCollectionPaths: expectedPathAsSlice( expect: assert.NoError,
suite.T(), expectedCollectionPaths: []string{},
tenant,
user,
"/root",
"/root/folder",
),
expectedItemCount: 1,
expectedFolderCount: 1,
}, },
{ {
testCase: "Single Package", testCase: "Single Package",
items: []models.DriveItemable{ items: []models.DriveItemable{
driveItem("package", "/root", false, false, true), driveItem("package", testBaseDrivePath, false, false, true),
}, },
expect: assert.NoError, scope: anyFolder,
expectedCollectionPaths: expectedPathAsSlice( expect: assert.NoError,
suite.T(), expectedCollectionPaths: []string{},
tenant,
user,
"/root",
"/root/package",
),
expectedItemCount: 1,
expectedPackageCount: 1,
}, },
{ {
testCase: "1 root file, 1 folder, 1 package, 2 files, 3 collections", testCase: "1 root file, 1 folder, 1 package, 2 files, 3 collections",
items: []models.DriveItemable{ items: []models.DriveItemable{
driveItem("fileInRoot", "/root", true, false, false), driveItem("fileInRoot", testBaseDrivePath, true, false, false),
driveItem("folder", "/root", false, true, false), driveItem("folder", testBaseDrivePath, false, true, false),
driveItem("package", "/root", false, false, true), driveItem("package", testBaseDrivePath, false, false, true),
driveItem("fileInFolder", "/root/folder", true, false, false), driveItem("fileInFolder", testBaseDrivePath+"/folder", true, false, false),
driveItem("fileInPackage", "/root/package", true, false, false), driveItem("fileInPackage", testBaseDrivePath+"/package", true, false, false),
}, },
scope: anyFolder,
expect: assert.NoError, expect: assert.NoError,
expectedCollectionPaths: expectedPathAsSlice( expectedCollectionPaths: expectedPathAsSlice(
suite.T(), suite.T(),
tenant, tenant,
user, user,
"/root", testBaseDrivePath,
"/root/folder", testBaseDrivePath+"/folder",
"/root/package", testBaseDrivePath+"/package",
), ),
expectedItemCount: 5, expectedItemCount: 6,
expectedFileCount: 3, expectedFileCount: 3,
expectedFolderCount: 1, expectedContainerCount: 3,
expectedPackageCount: 1, },
{
testCase: "match folder selector",
items: []models.DriveItemable{
driveItem("fileInRoot", testBaseDrivePath, true, false, false),
driveItem("folder", testBaseDrivePath, false, true, false),
driveItem("subfolder", testBaseDrivePath+"/folder", false, true, false),
driveItem("folder", testBaseDrivePath+"/folder/subfolder", false, true, false),
driveItem("package", testBaseDrivePath, false, false, true),
driveItem("fileInFolder", testBaseDrivePath+"/folder", true, false, false),
driveItem("fileInFolder2", testBaseDrivePath+"/folder/subfolder/folder", true, false, false),
driveItem("fileInPackage", testBaseDrivePath+"/package", true, false, false),
},
scope: (&selectors.OneDriveBackup{}).Folders(selectors.Any(), []string{"folder"})[0],
expect: assert.NoError,
expectedCollectionPaths: expectedPathAsSlice(
suite.T(),
tenant,
user,
testBaseDrivePath+"/folder",
),
expectedItemCount: 2,
expectedFileCount: 1,
expectedContainerCount: 1,
},
{
testCase: "match subfolder selector",
items: []models.DriveItemable{
driveItem("fileInRoot", testBaseDrivePath, true, false, false),
driveItem("folder", testBaseDrivePath, false, true, false),
driveItem("subfolder", testBaseDrivePath+"/folder", false, true, false),
driveItem("package", testBaseDrivePath, false, false, true),
driveItem("fileInFolder", testBaseDrivePath+"/folder", true, false, false),
driveItem("fileInSubfolder", testBaseDrivePath+"/folder/subfolder", true, false, false),
driveItem("fileInPackage", testBaseDrivePath+"/package", true, false, false),
},
scope: (&selectors.OneDriveBackup{}).Folders(selectors.Any(), []string{"folder/subfolder"})[0],
expect: assert.NoError,
expectedCollectionPaths: expectedPathAsSlice(
suite.T(),
tenant,
user,
testBaseDrivePath+"/folder/subfolder",
),
expectedItemCount: 2,
expectedFileCount: 1,
expectedContainerCount: 1,
}, },
} }
@ -130,14 +173,13 @@ func (suite *OneDriveCollectionsSuite) TestUpdateCollections() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
c := NewCollections(tenant, user, &MockGraphService{}, nil) c := NewCollections(tenant, user, tt.scope, &MockGraphService{}, nil)
err := c.updateCollections(ctx, "driveID", tt.items) err := c.updateCollections(ctx, "driveID", tt.items)
tt.expect(t, err) tt.expect(t, err)
assert.Equal(t, len(tt.expectedCollectionPaths), len(c.collectionMap)) assert.Equal(t, len(tt.expectedCollectionPaths), len(c.collectionMap))
assert.Equal(t, tt.expectedItemCount, c.numItems) assert.Equal(t, tt.expectedItemCount, c.numItems)
assert.Equal(t, tt.expectedFileCount, c.numFiles) assert.Equal(t, tt.expectedFileCount, c.numFiles)
assert.Equal(t, tt.expectedFolderCount, c.numDirs) assert.Equal(t, tt.expectedContainerCount, c.numContainers)
assert.Equal(t, tt.expectedPackageCount, c.numPackages)
for _, collPath := range tt.expectedCollectionPaths { for _, collPath := range tt.expectedCollectionPaths {
assert.Contains(t, c.collectionMap, collPath) assert.Contains(t, c.collectionMap, collPath)
} }