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:
parent
f1f6c06ba0
commit
1cbb34e403
@ -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)
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user