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" mssites "github.com/microsoftgraph/msgraph-sdk-go/sites"
msusers "github.com/microsoftgraph/msgraph-sdk-go/users" 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"
"github.com/alcionai/corso/src/internal/connector/graph/api" "github.com/alcionai/corso/src/internal/connector/graph/api"
) )
@ -137,6 +138,11 @@ type siteDrivePager struct {
options *mssites.ItemDrivesRequestBuilderGetRequestConfiguration 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( func NewSiteDrivePager(
gs graph.Servicer, gs graph.Servicer,
siteID string, siteID string,
@ -178,3 +184,72 @@ func (p *siteDrivePager) SetNext(link string) {
func (p *siteDrivePager) ValuesIn(l api.PageLinker) ([]models.Driveable, error) { func (p *siteDrivePager) ValuesIn(l api.PageLinker) ([]models.Driveable, error) {
return getValues[models.Driveable](l) 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 = 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() defer closer()
runAndCheckBackup(t, ctx, &bo, mb) 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/require"
"github.com/stretchr/testify/suite" "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/exchange"
"github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/mockconnector" "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/connector/support"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/events" "github.com/alcionai/corso/src/internal/events"
@ -133,12 +137,14 @@ func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
type RestoreOpIntegrationSuite struct { type RestoreOpIntegrationSuite struct {
tester.Suite tester.Suite
backupID model.StableID backupID model.StableID
numItems int sharepointID model.StableID
kopiaCloser func(ctx context.Context) shareItems int
kw *kopia.Wrapper numItems int
sw *store.Wrapper kopiaCloser func(ctx context.Context)
ms *kopia.ModelStore kw *kopia.Wrapper
sw *store.Wrapper
ms *kopia.ModelStore
} }
func TestRestoreOpIntegrationSuite(t *testing.T) { func TestRestoreOpIntegrationSuite(t *testing.T) {
@ -208,6 +214,30 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() {
// Discount metadata files (3 paths, 3 deltas) as // Discount metadata files (3 paths, 3 deltas) as
// they are not part of the data restored. // they are not part of the data restored.
suite.numItems = bo.Results.ItemsWritten - 6 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() { func (suite *RestoreOpIntegrationSuite) TearDownSuite() {
@ -266,49 +296,106 @@ func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
} }
} }
//nolint:lll
func (suite *RestoreOpIntegrationSuite) TestRestore_Run() { func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
t := suite.T() tables := []struct {
users := []string{tester.M365UserID(t)} 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) return rsel.Selector
rsel.Include(rsel.AllData()) },
},
{
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() return bsel.Selector
mb := evmock.NewBus() },
cleanup: func(t *testing.T, dest string) {
act := tester.NewM365Account(t)
m365, err := act.M365Config()
require.NoError(t, err)
ro, err := NewRestoreOperation( adpt, err := graph.CreateAdapter(m365.AzureTenantID, m365.AzureClientID, m365.AzureClientSecret)
ctx, require.NoError(t, err)
control.Options{}, service := graph.NewService(adpt)
suite.kw, pager := api.NewSiteDrivePager(service, tester.M365SiteID(t), []string{"id", "name"})
suite.sw, driveID, err := pager.GetDriveIDByName(ctx, "Documents")
tester.NewM365Account(t), require.NoError(t, err)
suite.backupID, require.NotEmpty(t, driveID)
rsel.Selector,
dest,
mb)
require.NoError(t, err)
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()") err = onedrive.DeleteItem(ctx, service, driveID, folderID)
require.NotEmpty(t, ro.Results, "restoreOp results") assert.NoError(t, err, "failed to delete restore folder: operations_SharePoint_Restore")
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") for _, test := range tables {
assert.Less(t, int64(0), ro.Results.BytesRead, "bytes read") suite.T().Run(test.name, func(t *testing.T) {
assert.Equal(t, 1, ro.Results.ResourceOwners, "resource Owners") mb := evmock.NewBus()
assert.NoError(t, ro.Errors.Failure(), "non-recoverable error") ro, err := NewRestoreOperation(
assert.Empty(t, ro.Errors.Recovered(), "recoverable errors") ctx,
assert.NoError(t, ro.Results.ReadErrors, "errors while reading restore data") control.Options{FailFast: true},
assert.NoError(t, ro.Results.WriteErrors, "errors while writing restore data") suite.kw,
assert.Equal(t, suite.numItems, ro.Results.ItemsWritten, "backup and restore wrote the same num of items") suite.sw,
assert.Equal(t, 1, mb.TimesCalled[events.RestoreStart], "restore-start events") tester.NewM365Account(t),
assert.Equal(t, 1, mb.TimesCalled[events.RestoreEnd], "restore-end events") 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() { func (suite *RestoreOpIntegrationSuite) TestRestore_Run_ErrorNoResults() {