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:
parent
aef90f8b86
commit
5c65638721
@ -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
|
||||||
|
}
|
||||||
|
|||||||
81
src/internal/connector/onedrive/api/drive_test.go
Normal file
81
src/internal/connector/onedrive/api/drive_test.go
Normal 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)
|
||||||
|
}
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user