GC: e2e: SharePoint.Libraries (#2666)

<!-- Insert PR description-->
E2E Testing for GC: `SharePoint.Libraries`. Covers Restore and Backup tests within the operations package
---

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

- [x]  No

#### Type of change

- [x] 🤖 Test
- [x] 💻 CI/Deployment

#### Issue(s)

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

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Danny 2023-02-28 17:44:21 -05:00 committed by GitHub
parent aef90f8b86
commit 5c65638721
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 286 additions and 42 deletions

View File

@ -10,6 +10,7 @@ import (
mssites "github.com/microsoftgraph/msgraph-sdk-go/sites"
msusers "github.com/microsoftgraph/msgraph-sdk-go/users"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/graph/api"
)
@ -137,6 +138,11 @@ type siteDrivePager struct {
options *mssites.ItemDrivesRequestBuilderGetRequestConfiguration
}
// NewSiteDrivePager is a constructor for creating a siteDrivePager
// fields are the associated site drive fields that are desired to be returned
// in a query. NOTE: Fields are case-sensitive. Incorrect field settings will
// cause errors during later paging.
// Available fields: https://learn.microsoft.com/en-us/graph/api/resources/drive?view=graph-rest-1.0
func NewSiteDrivePager(
gs graph.Servicer,
siteID string,
@ -178,3 +184,72 @@ func (p *siteDrivePager) SetNext(link string) {
func (p *siteDrivePager) ValuesIn(l api.PageLinker) ([]models.Driveable, error) {
return getValues[models.Driveable](l)
}
// GetDriveIDByName is a helper function to retrieve the M365ID of a site drive.
// Returns "" if the folder is not within the drive.
// Dependency: Requires "name" and "id" to be part of the given options
func (p *siteDrivePager) GetDriveIDByName(ctx context.Context, driveName string) (string, error) {
var empty string
for {
resp, err := p.builder.Get(ctx, p.options)
if err != nil {
return empty, clues.Stack(err).WithClues(ctx).With(graph.ErrData(err)...)
}
for _, entry := range resp.GetValue() {
if ptr.Val(entry.GetName()) == driveName {
return ptr.Val(entry.GetId()), nil
}
}
link, ok := ptr.ValOK(resp.GetOdataNextLink())
if !ok {
break
}
p.builder = mssites.NewItemDrivesRequestBuilder(link, p.gs.Adapter())
}
return empty, nil
}
// GetFolderIDByName is a helper function to retrieve the M365ID of a folder within a site document library.
// Returns "" if the folder is not within the drive
func (p *siteDrivePager) GetFolderIDByName(ctx context.Context, driveID, folderName string) (string, error) {
var empty string
// *msdrives.ItemRootChildrenRequestBuilder
builder := p.gs.Client().DrivesById(driveID).Root().Children()
option := &msdrives.ItemRootChildrenRequestBuilderGetRequestConfiguration{
QueryParameters: &msdrives.ItemRootChildrenRequestBuilderGetQueryParameters{
Select: []string{"id", "name", "folder"},
},
}
for {
resp, err := builder.Get(ctx, option)
if err != nil {
return empty, clues.Stack(err).WithClues(ctx).With(graph.ErrData(err)...)
}
for _, entry := range resp.GetValue() {
if entry.GetFolder() == nil {
continue
}
if ptr.Val(entry.GetName()) == folderName {
return ptr.Val(entry.GetId()), nil
}
}
link, ok := ptr.ValOK(resp.GetOdataNextLink())
if !ok {
break
}
builder = msdrives.NewItemRootChildrenRequestBuilder(link, p.gs.Adapter())
}
return empty, nil
}

View File

@ -0,0 +1,81 @@
package api_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/onedrive/api"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account"
)
type OneDriveAPISuite struct {
tester.Suite
creds account.M365Config
service graph.Servicer
}
func (suite *OneDriveAPISuite) SetupSuite() {
t := suite.T()
a := tester.NewM365Account(t)
m365, err := a.M365Config()
require.NoError(t, err)
suite.creds = m365
adpt, err := graph.CreateAdapter(m365.AzureTenantID, m365.AzureClientID, m365.AzureClientSecret)
require.NoError(t, err)
suite.service = graph.NewService(adpt)
}
func TestOneDriveAPIs(t *testing.T) {
suite.Run(t, &OneDriveAPISuite{
Suite: tester.NewIntegrationSuite(
t,
[][]string{tester.M365AcctCredEnvs},
tester.CorsoGraphConnectorOneDriveTests),
})
}
func (suite *OneDriveAPISuite) TestCreatePagerAndGetPage() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
siteID := tester.M365SiteID(t)
pager := api.NewSiteDrivePager(suite.service, siteID, []string{"name"})
a, err := pager.GetPage(ctx)
assert.NoError(t, err)
assert.NotNil(t, a)
}
func (suite *OneDriveAPISuite) TestGetDriveIDByName() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
siteID := tester.M365SiteID(t)
pager := api.NewSiteDrivePager(suite.service, siteID, []string{"id", "name"})
id, err := pager.GetDriveIDByName(ctx, "Documents")
assert.NoError(t, err)
assert.NotEmpty(t, id)
}
func (suite *OneDriveAPISuite) TestGetDriveFolderByName() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
siteID := tester.M365SiteID(t)
pager := api.NewSiteDrivePager(suite.service, siteID, []string{"id", "name"})
id, err := pager.GetDriveIDByName(ctx, "Documents")
require.NoError(t, err)
require.NotEmpty(t, id)
_, err = pager.GetFolderIDByName(ctx, id, "folder")
assert.NoError(t, err)
}

View File

@ -1107,10 +1107,11 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_sharePoint() {
sel = selectors.NewSharePointBackup([]string{suite.site})
)
sel.Include(sel.AllData())
sel.Include(sel.Libraries(selectors.Any()))
bo, _, _, _, closer := prepNewTestBackupOp(t, ctx, mb, sel.Selector, control.Toggles{})
bo, _, kw, _, closer := prepNewTestBackupOp(t, ctx, mb, sel.Selector, control.Toggles{})
defer closer()
runAndCheckBackup(t, ctx, &bo, mb)
checkBackupIsInManifests(t, ctx, kw, &bo, sel.Selector, suite.site, path.LibrariesCategory)
}

View File

@ -9,8 +9,12 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/connector/exchange"
"github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/mockconnector"
"github.com/alcionai/corso/src/internal/connector/onedrive"
"github.com/alcionai/corso/src/internal/connector/onedrive/api"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/events"
@ -133,12 +137,14 @@ func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
type RestoreOpIntegrationSuite struct {
tester.Suite
backupID model.StableID
numItems int
kopiaCloser func(ctx context.Context)
kw *kopia.Wrapper
sw *store.Wrapper
ms *kopia.ModelStore
backupID model.StableID
sharepointID model.StableID
shareItems int
numItems int
kopiaCloser func(ctx context.Context)
kw *kopia.Wrapper
sw *store.Wrapper
ms *kopia.ModelStore
}
func TestRestoreOpIntegrationSuite(t *testing.T) {
@ -208,6 +214,30 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() {
// Discount metadata files (3 paths, 3 deltas) as
// they are not part of the data restored.
suite.numItems = bo.Results.ItemsWritten - 6
siteID := tester.M365SiteID(t)
sites := []string{siteID}
csel := selectors.NewSharePointBackup(sites)
csel.DiscreteOwner = siteID
csel.Include(
csel.Libraries(selectors.Any()),
)
bo, err = NewBackupOperation(
ctx,
control.Options{},
kw,
sw,
acct,
csel.Selector,
evmock.NewBus(),
)
require.NoError(t, err)
require.NoError(t, bo.Run(ctx))
require.NotEmpty(t, bo.Results.BackupID)
suite.sharepointID = bo.Results.BackupID
// Discount MetaData files (1 path, 1 delta)
suite.shareItems = bo.Results.ItemsWritten - 2
}
func (suite *RestoreOpIntegrationSuite) TearDownSuite() {
@ -266,49 +296,106 @@ func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
}
}
//nolint:lll
func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
users := []string{tester.M365UserID(t)}
tables := []struct {
name string
bID model.StableID
expectedItems int
dest control.RestoreDestination
getSelector func(t *testing.T) selectors.Selector
cleanup func(t *testing.T, dest string)
}{
{
name: "Exchange_Restore",
bID: suite.backupID,
expectedItems: suite.numItems,
dest: tester.DefaultTestRestoreDestination(),
getSelector: func(t *testing.T) selectors.Selector {
users := []string{tester.M365UserID(t)}
rsel := selectors.NewExchangeRestore(users)
rsel.Include(rsel.AllData())
rsel := selectors.NewExchangeRestore(users)
rsel.Include(rsel.AllData())
return rsel.Selector
},
},
{
name: "SharePoint_Restore",
bID: suite.sharepointID,
expectedItems: suite.shareItems,
dest: control.DefaultRestoreDestination(common.SimpleDateTimeOneDrive),
getSelector: func(t *testing.T) selectors.Selector {
bsel := selectors.NewSharePointRestore([]string{tester.M365SiteID(t)})
bsel.Include(bsel.AllData())
dest := tester.DefaultTestRestoreDestination()
mb := evmock.NewBus()
return bsel.Selector
},
cleanup: func(t *testing.T, dest string) {
act := tester.NewM365Account(t)
m365, err := act.M365Config()
require.NoError(t, err)
ro, err := NewRestoreOperation(
ctx,
control.Options{},
suite.kw,
suite.sw,
tester.NewM365Account(t),
suite.backupID,
rsel.Selector,
dest,
mb)
require.NoError(t, err)
adpt, err := graph.CreateAdapter(m365.AzureTenantID, m365.AzureClientID, m365.AzureClientSecret)
require.NoError(t, err)
service := graph.NewService(adpt)
pager := api.NewSiteDrivePager(service, tester.M365SiteID(t), []string{"id", "name"})
driveID, err := pager.GetDriveIDByName(ctx, "Documents")
require.NoError(t, err)
require.NotEmpty(t, driveID)
ds, err := ro.Run(ctx)
folderID, err := pager.GetFolderIDByName(ctx, driveID, dest)
require.NoError(t, err)
require.NotEmpty(t, folderID)
require.NoError(t, err, "restoreOp.Run()")
require.NotEmpty(t, ro.Results, "restoreOp results")
require.NotNil(t, ds, "restored details")
assert.Equal(t, ro.Status, Completed, "restoreOp status")
assert.Equal(t, ro.Results.ItemsWritten, len(ds.Entries), "count of items written matches restored entries in details")
assert.Less(t, 0, ro.Results.ItemsRead, "restore items read")
assert.Less(t, 0, ro.Results.ItemsWritten, "restored items written")
assert.Less(t, int64(0), ro.Results.BytesRead, "bytes read")
assert.Equal(t, 1, ro.Results.ResourceOwners, "resource Owners")
assert.NoError(t, ro.Errors.Failure(), "non-recoverable error")
assert.Empty(t, ro.Errors.Recovered(), "recoverable errors")
assert.NoError(t, ro.Results.ReadErrors, "errors while reading restore data")
assert.NoError(t, ro.Results.WriteErrors, "errors while writing restore data")
assert.Equal(t, suite.numItems, ro.Results.ItemsWritten, "backup and restore wrote the same num of items")
assert.Equal(t, 1, mb.TimesCalled[events.RestoreStart], "restore-start events")
assert.Equal(t, 1, mb.TimesCalled[events.RestoreEnd], "restore-end events")
err = onedrive.DeleteItem(ctx, service, driveID, folderID)
assert.NoError(t, err, "failed to delete restore folder: operations_SharePoint_Restore")
},
},
}
for _, test := range tables {
suite.T().Run(test.name, func(t *testing.T) {
mb := evmock.NewBus()
ro, err := NewRestoreOperation(
ctx,
control.Options{FailFast: true},
suite.kw,
suite.sw,
tester.NewM365Account(t),
test.bID,
test.getSelector(t),
test.dest,
mb)
require.NoError(t, err)
ds, err := ro.Run(ctx)
require.NoError(t, err, "restoreOp.Run()")
require.NotEmpty(t, ro.Results, "restoreOp results")
require.NotNil(t, ds, "restored details")
assert.Equal(t, ro.Status, Completed, "restoreOp status")
assert.Equal(t, ro.Results.ItemsWritten, len(ds.Entries), "count of items written matches restored entries in details")
assert.Less(t, 0, ro.Results.ItemsRead, "restore items read")
assert.Less(t, 0, ro.Results.ItemsWritten, "restored items written")
assert.Less(t, int64(0), ro.Results.BytesRead, "bytes read")
assert.Equal(t, 1, ro.Results.ResourceOwners, "resource Owners")
assert.NoError(t, ro.Errors.Failure(), "non-recoverable error")
assert.Empty(t, ro.Errors.Recovered(), "recoverable errors")
assert.NoError(t, ro.Results.ReadErrors, "errors while reading restore data")
assert.NoError(t, ro.Results.WriteErrors, "errors while writing restore data")
assert.Equal(t, test.expectedItems, ro.Results.ItemsWritten, "backup and restore wrote the same num of items")
assert.Equal(t, 1, mb.TimesCalled[events.RestoreStart], "restore-start events")
assert.Equal(t, 1, mb.TimesCalled[events.RestoreEnd], "restore-end events")
// clean up
if test.cleanup != nil {
test.cleanup(t, test.dest.ContainerName)
}
})
}
}
func (suite *RestoreOpIntegrationSuite) TestRestore_Run_ErrorNoResults() {