move getDrives to onedrive/api, add mock (#2941)
Moves the drive fetcher and pagers into the onedrive/api package so that it can be easily re- used by other packages. Also moves the mocks out from local testing and into a mock package nested in the api pkg. first of a couple PRs fixing up sharepoint testing issues. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🤖 Supportability/Tests #### Test Plan - [x] 💚 E2E
This commit is contained in:
parent
ddeaaf2ff2
commit
17cb3db524
@ -4,8 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/graph/api"
|
||||||
abstractions "github.com/microsoft/kiota-abstractions-go"
|
abstractions "github.com/microsoft/kiota-abstractions-go"
|
||||||
msdrives "github.com/microsoftgraph/msgraph-sdk-go/drives"
|
msdrives "github.com/microsoftgraph/msgraph-sdk-go/drives"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
@ -14,7 +16,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"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/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getValues[T any](l api.PageLinker) ([]T, error) {
|
func getValues[T any](l api.PageLinker) ([]T, error) {
|
||||||
@ -201,71 +203,75 @@ 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.
|
// Drive Paging
|
||||||
// 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
|
|
||||||
|
|
||||||
|
// DrivePager pages through different types of drive owners
|
||||||
|
type DrivePager interface {
|
||||||
|
GetPage(context.Context) (api.PageLinker, error)
|
||||||
|
SetNext(nextLink string)
|
||||||
|
ValuesIn(api.PageLinker) ([]models.Driveable, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllDrives fetches all drives for the given pager
|
||||||
|
func GetAllDrives(
|
||||||
|
ctx context.Context,
|
||||||
|
pager DrivePager,
|
||||||
|
retry bool,
|
||||||
|
maxRetryCount int,
|
||||||
|
) ([]models.Driveable, error) {
|
||||||
|
drives := []models.Driveable{}
|
||||||
|
|
||||||
|
if !retry {
|
||||||
|
maxRetryCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through all pages returned by Graph API.
|
||||||
for {
|
for {
|
||||||
resp, err := p.builder.Get(ctx, p.options)
|
var (
|
||||||
if err != nil {
|
err error
|
||||||
return empty, graph.Stack(ctx, err)
|
page api.PageLinker
|
||||||
}
|
)
|
||||||
|
|
||||||
for _, entry := range resp.GetValue() {
|
// Retry Loop for Drive retrieval. Request can timeout
|
||||||
if ptr.Val(entry.GetName()) == driveName {
|
for i := 0; i <= maxRetryCount; i++ {
|
||||||
return ptr.Val(entry.GetId()), nil
|
page, err = pager.GetPage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if clues.HasLabel(err, graph.LabelsMysiteNotFound) {
|
||||||
|
logger.Ctx(ctx).Infof("resource owner does not have a drive")
|
||||||
|
return make([]models.Driveable, 0), nil // no license or drives.
|
||||||
|
}
|
||||||
|
|
||||||
|
if graph.IsErrTimeout(err) && i < maxRetryCount {
|
||||||
|
time.Sleep(time.Duration(3*(i+1)) * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, graph.Wrap(ctx, err, "retrieving drives")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
link, ok := ptr.ValOK(resp.GetOdataNextLink())
|
// No error encountered, break the retry loop so we can extract results
|
||||||
if !ok {
|
// and see if there's another page to fetch.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
p.builder = mssites.NewItemDrivesRequestBuilder(link, p.gs.Adapter())
|
tmp, err := pager.ValuesIn(page)
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
return empty, graph.Stack(ctx, err)
|
return nil, graph.Wrap(ctx, err, "extracting drives from response")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, entry := range resp.GetValue() {
|
drives = append(drives, tmp...)
|
||||||
if entry.GetFolder() == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ptr.Val(entry.GetName()) == folderName {
|
nextLink := ptr.Val(page.GetOdataNextLink())
|
||||||
return ptr.Val(entry.GetId()), nil
|
if len(nextLink) == 0 {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
link, ok := ptr.ValOK(resp.GetOdataNextLink())
|
|
||||||
if !ok {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
builder = msdrives.NewItemRootChildrenRequestBuilder(link, p.gs.Adapter())
|
pager.SetNext(nextLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
return empty, nil
|
logger.Ctx(ctx).Debugf("retrieved %d valid drives", len(drives))
|
||||||
|
|
||||||
|
return drives, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,30 +55,3 @@ func (suite *OneDriveAPISuite) TestCreatePagerAndGetPage() {
|
|||||||
assert.NoError(t, err, clues.ToCore(err))
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
assert.NotNil(t, a)
|
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, clues.ToCore(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, clues.ToCore(err))
|
|
||||||
require.NotEmpty(t, id)
|
|
||||||
|
|
||||||
_, err = pager.GetFolderIDByName(ctx, id, "folder")
|
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
|
||||||
}
|
|
||||||
|
|||||||
56
src/internal/connector/onedrive/api/mock/drive.go
Normal file
56
src/internal/connector/onedrive/api/mock/drive.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/graph/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PageLinker struct {
|
||||||
|
Link *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl *PageLinker) GetOdataNextLink() *string {
|
||||||
|
return pl.Link
|
||||||
|
}
|
||||||
|
|
||||||
|
type PagerResult struct {
|
||||||
|
Drives []models.Driveable
|
||||||
|
NextLink *string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DrivePager struct {
|
||||||
|
ToReturn []PagerResult
|
||||||
|
GetIdx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DrivePager) GetPage(context.Context) (api.PageLinker, error) {
|
||||||
|
if len(p.ToReturn) <= p.GetIdx {
|
||||||
|
return nil, clues.New("ToReturn index out of bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := p.GetIdx
|
||||||
|
p.GetIdx++
|
||||||
|
|
||||||
|
return &PageLinker{p.ToReturn[idx].NextLink}, p.ToReturn[idx].Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DrivePager) SetNext(string) {}
|
||||||
|
|
||||||
|
func (p *DrivePager) ValuesIn(api.PageLinker) ([]models.Driveable, error) {
|
||||||
|
idx := p.GetIdx
|
||||||
|
if idx > 0 {
|
||||||
|
// Return values lag by one since we increment in GetPage().
|
||||||
|
idx--
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.ToReturn) <= idx {
|
||||||
|
return nil, clues.New("ToReturn index out of bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.ToReturn[idx].Drives, nil
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"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/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/observe"
|
"github.com/alcionai/corso/src/internal/observe"
|
||||||
@ -80,7 +81,7 @@ type Collections struct {
|
|||||||
servicer graph.Servicer,
|
servicer graph.Servicer,
|
||||||
resourceOwner string,
|
resourceOwner string,
|
||||||
fields []string,
|
fields []string,
|
||||||
) (drivePager, error)
|
) (api.DrivePager, error)
|
||||||
itemPagerFunc func(
|
itemPagerFunc func(
|
||||||
servicer graph.Servicer,
|
servicer graph.Servicer,
|
||||||
driveID, link string,
|
driveID, link string,
|
||||||
@ -273,7 +274,7 @@ func (c *Collections) Get(
|
|||||||
return nil, nil, graph.Stack(ctx, err)
|
return nil, nil, graph.Stack(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
drives, err := drives(ctx, pager, true)
|
drives, err := api.GetAllDrives(ctx, pager, true, maxDrivesRetries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,8 @@ import (
|
|||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
gapi "github.com/alcionai/corso/src/internal/connector/graph/api"
|
gapi "github.com/alcionai/corso/src/internal/connector/graph/api"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/api"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/api/mock"
|
||||||
"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/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
@ -2208,11 +2210,11 @@ func (suite *OneDriveCollectionsUnitSuite) TestGet() {
|
|||||||
servicer graph.Servicer,
|
servicer graph.Servicer,
|
||||||
resourceOwner string,
|
resourceOwner string,
|
||||||
fields []string,
|
fields []string,
|
||||||
) (drivePager, error) {
|
) (api.DrivePager, error) {
|
||||||
return &mockDrivePager{
|
return &mock.DrivePager{
|
||||||
toReturn: []pagerResult{
|
ToReturn: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: test.drives,
|
Drives: test.drives,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
msdrive "github.com/microsoftgraph/msgraph-sdk-go/drive"
|
msdrive "github.com/microsoftgraph/msgraph-sdk-go/drive"
|
||||||
@ -23,7 +22,7 @@ import (
|
|||||||
var errFolderNotFound = clues.New("folder not found")
|
var errFolderNotFound = clues.New("folder not found")
|
||||||
|
|
||||||
const (
|
const (
|
||||||
getDrivesRetries = 3
|
maxDrivesRetries = 3
|
||||||
|
|
||||||
// nextLinkKey is used to find the next link in a paged
|
// nextLinkKey is used to find the next link in a paged
|
||||||
// graph response
|
// graph response
|
||||||
@ -44,18 +43,12 @@ type DeltaUpdate struct {
|
|||||||
Reset bool
|
Reset bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type drivePager interface {
|
|
||||||
GetPage(context.Context) (gapi.PageLinker, error)
|
|
||||||
SetNext(nextLink string)
|
|
||||||
ValuesIn(gapi.PageLinker) ([]models.Driveable, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PagerForSource(
|
func PagerForSource(
|
||||||
source driveSource,
|
source driveSource,
|
||||||
servicer graph.Servicer,
|
servicer graph.Servicer,
|
||||||
resourceOwner string,
|
resourceOwner string,
|
||||||
fields []string,
|
fields []string,
|
||||||
) (drivePager, error) {
|
) (api.DrivePager, error) {
|
||||||
switch source {
|
switch source {
|
||||||
case OneDriveSource:
|
case OneDriveSource:
|
||||||
return api.NewUserDrivePager(servicer, resourceOwner, fields), nil
|
return api.NewUserDrivePager(servicer, resourceOwner, fields), nil
|
||||||
@ -66,69 +59,6 @@ func PagerForSource(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func drives(
|
|
||||||
ctx context.Context,
|
|
||||||
pager drivePager,
|
|
||||||
retry bool,
|
|
||||||
) ([]models.Driveable, error) {
|
|
||||||
var (
|
|
||||||
numberOfRetries = getDrivesRetries
|
|
||||||
drives = []models.Driveable{}
|
|
||||||
)
|
|
||||||
|
|
||||||
if !retry {
|
|
||||||
numberOfRetries = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop through all pages returned by Graph API.
|
|
||||||
for {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
page gapi.PageLinker
|
|
||||||
)
|
|
||||||
|
|
||||||
// Retry Loop for Drive retrieval. Request can timeout
|
|
||||||
for i := 0; i <= numberOfRetries; i++ {
|
|
||||||
page, err = pager.GetPage(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if clues.HasLabel(err, graph.LabelsMysiteNotFound) {
|
|
||||||
logger.Ctx(ctx).Infof("resource owner does not have a drive")
|
|
||||||
return make([]models.Driveable, 0), nil // no license or drives.
|
|
||||||
}
|
|
||||||
|
|
||||||
if graph.IsErrTimeout(err) && i < numberOfRetries {
|
|
||||||
time.Sleep(time.Duration(3*(i+1)) * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, graph.Wrap(ctx, err, "retrieving drives")
|
|
||||||
}
|
|
||||||
|
|
||||||
// No error encountered, break the retry loop so we can extract results
|
|
||||||
// and see if there's another page to fetch.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp, err := pager.ValuesIn(page)
|
|
||||||
if err != nil {
|
|
||||||
return nil, graph.Wrap(ctx, err, "extracting drives from response")
|
|
||||||
}
|
|
||||||
|
|
||||||
drives = append(drives, tmp...)
|
|
||||||
|
|
||||||
nextLink := ptr.Val(page.GetOdataNextLink())
|
|
||||||
if len(nextLink) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
pager.SetNext(nextLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Ctx(ctx).Debugf("retrieved %d valid drives", len(drives))
|
|
||||||
|
|
||||||
return drives, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// itemCollector functions collect the items found in a drive
|
// itemCollector functions collect the items found in a drive
|
||||||
type itemCollector func(
|
type itemCollector func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@ -350,11 +280,11 @@ func (op *Displayable) GetDisplayName() *string {
|
|||||||
func GetAllFolders(
|
func GetAllFolders(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
gs graph.Servicer,
|
gs graph.Servicer,
|
||||||
pager drivePager,
|
pager api.DrivePager,
|
||||||
prefix string,
|
prefix string,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
) ([]*Displayable, error) {
|
) ([]*Displayable, error) {
|
||||||
drives, err := drives(ctx, pager, true)
|
drives, err := api.GetAllDrives(ctx, pager, true, maxDrivesRetries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "getting OneDrive folders")
|
return nil, errors.Wrap(err, "getting OneDrive folders")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,8 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"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/onedrive/api"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/onedrive/api/mock"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
@ -24,52 +25,6 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockPageLinker struct {
|
|
||||||
link *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pl *mockPageLinker) GetOdataNextLink() *string {
|
|
||||||
return pl.link
|
|
||||||
}
|
|
||||||
|
|
||||||
type pagerResult struct {
|
|
||||||
drives []models.Driveable
|
|
||||||
nextLink *string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockDrivePager struct {
|
|
||||||
toReturn []pagerResult
|
|
||||||
getIdx int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *mockDrivePager) GetPage(context.Context) (api.PageLinker, error) {
|
|
||||||
if len(p.toReturn) <= p.getIdx {
|
|
||||||
return nil, assert.AnError
|
|
||||||
}
|
|
||||||
|
|
||||||
idx := p.getIdx
|
|
||||||
p.getIdx++
|
|
||||||
|
|
||||||
return &mockPageLinker{p.toReturn[idx].nextLink}, p.toReturn[idx].err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *mockDrivePager) SetNext(string) {}
|
|
||||||
|
|
||||||
func (p *mockDrivePager) ValuesIn(api.PageLinker) ([]models.Driveable, error) {
|
|
||||||
idx := p.getIdx
|
|
||||||
if idx > 0 {
|
|
||||||
// Return values lag by one since we increment in GetPage().
|
|
||||||
idx--
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.toReturn) <= idx {
|
|
||||||
return nil, assert.AnError
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.toReturn[idx].drives, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unit tests
|
// Unit tests
|
||||||
type OneDriveUnitSuite struct {
|
type OneDriveUnitSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
@ -117,28 +72,28 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
resultDrives = append(resultDrives, d)
|
resultDrives = append(resultDrives, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
tooManyRetries := make([]pagerResult, 0, getDrivesRetries+1)
|
tooManyRetries := make([]mock.PagerResult, 0, maxDrivesRetries+1)
|
||||||
|
|
||||||
for i := 0; i < getDrivesRetries+1; i++ {
|
for i := 0; i < maxDrivesRetries+1; i++ {
|
||||||
tooManyRetries = append(tooManyRetries, pagerResult{
|
tooManyRetries = append(tooManyRetries, mock.PagerResult{
|
||||||
err: context.DeadlineExceeded,
|
Err: context.DeadlineExceeded,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
pagerResults []pagerResult
|
pagerResults []mock.PagerResult
|
||||||
retry bool
|
retry bool
|
||||||
expectedErr assert.ErrorAssertionFunc
|
expectedErr assert.ErrorAssertionFunc
|
||||||
expectedResults []models.Driveable
|
expectedResults []models.Driveable
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "AllOneResultNilNextLink",
|
name: "AllOneResultNilNextLink",
|
||||||
pagerResults: []pagerResult{
|
pagerResults: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: resultDrives,
|
Drives: resultDrives,
|
||||||
nextLink: nil,
|
NextLink: nil,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
@ -147,11 +102,11 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AllOneResultEmptyNextLink",
|
name: "AllOneResultEmptyNextLink",
|
||||||
pagerResults: []pagerResult{
|
pagerResults: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: resultDrives,
|
Drives: resultDrives,
|
||||||
nextLink: &emptyLink,
|
NextLink: &emptyLink,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
@ -160,16 +115,16 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "SplitResultsNilNextLink",
|
name: "SplitResultsNilNextLink",
|
||||||
pagerResults: []pagerResult{
|
pagerResults: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: resultDrives[:numDriveResults/2],
|
Drives: resultDrives[:numDriveResults/2],
|
||||||
nextLink: &link,
|
NextLink: &link,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
drives: resultDrives[numDriveResults/2:],
|
Drives: resultDrives[numDriveResults/2:],
|
||||||
nextLink: nil,
|
NextLink: nil,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
@ -178,16 +133,16 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "SplitResultsEmptyNextLink",
|
name: "SplitResultsEmptyNextLink",
|
||||||
pagerResults: []pagerResult{
|
pagerResults: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: resultDrives[:numDriveResults/2],
|
Drives: resultDrives[:numDriveResults/2],
|
||||||
nextLink: &link,
|
NextLink: &link,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
drives: resultDrives[numDriveResults/2:],
|
Drives: resultDrives[numDriveResults/2:],
|
||||||
nextLink: &emptyLink,
|
NextLink: &emptyLink,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
@ -196,16 +151,16 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "NonRetryableError",
|
name: "NonRetryableError",
|
||||||
pagerResults: []pagerResult{
|
pagerResults: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: resultDrives,
|
Drives: resultDrives,
|
||||||
nextLink: &link,
|
NextLink: &link,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
drives: nil,
|
Drives: nil,
|
||||||
nextLink: nil,
|
NextLink: nil,
|
||||||
err: assert.AnError,
|
Err: assert.AnError,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
retry: true,
|
retry: true,
|
||||||
@ -214,11 +169,11 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MySiteURLNotFound",
|
name: "MySiteURLNotFound",
|
||||||
pagerResults: []pagerResult{
|
pagerResults: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: nil,
|
Drives: nil,
|
||||||
nextLink: nil,
|
NextLink: nil,
|
||||||
err: graph.Stack(ctx, mySiteURLNotFound),
|
Err: graph.Stack(ctx, mySiteURLNotFound),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
retry: true,
|
retry: true,
|
||||||
@ -227,11 +182,11 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MySiteNotFound",
|
name: "MySiteNotFound",
|
||||||
pagerResults: []pagerResult{
|
pagerResults: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: nil,
|
Drives: nil,
|
||||||
nextLink: nil,
|
NextLink: nil,
|
||||||
err: graph.Stack(ctx, mySiteNotFound),
|
Err: graph.Stack(ctx, mySiteNotFound),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
retry: true,
|
retry: true,
|
||||||
@ -240,21 +195,21 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "SplitResultsContextTimeoutWithRetries",
|
name: "SplitResultsContextTimeoutWithRetries",
|
||||||
pagerResults: []pagerResult{
|
pagerResults: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: resultDrives[:numDriveResults/2],
|
Drives: resultDrives[:numDriveResults/2],
|
||||||
nextLink: &link,
|
NextLink: &link,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
drives: nil,
|
Drives: nil,
|
||||||
nextLink: nil,
|
NextLink: nil,
|
||||||
err: context.DeadlineExceeded,
|
Err: context.DeadlineExceeded,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
drives: resultDrives[numDriveResults/2:],
|
Drives: resultDrives[numDriveResults/2:],
|
||||||
nextLink: &emptyLink,
|
NextLink: &emptyLink,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
retry: true,
|
retry: true,
|
||||||
@ -263,21 +218,21 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "SplitResultsContextTimeoutNoRetries",
|
name: "SplitResultsContextTimeoutNoRetries",
|
||||||
pagerResults: []pagerResult{
|
pagerResults: []mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: resultDrives[:numDriveResults/2],
|
Drives: resultDrives[:numDriveResults/2],
|
||||||
nextLink: &link,
|
NextLink: &link,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
drives: nil,
|
Drives: nil,
|
||||||
nextLink: nil,
|
NextLink: nil,
|
||||||
err: context.DeadlineExceeded,
|
Err: context.DeadlineExceeded,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
drives: resultDrives[numDriveResults/2:],
|
Drives: resultDrives[numDriveResults/2:],
|
||||||
nextLink: &emptyLink,
|
NextLink: &emptyLink,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
@ -287,11 +242,11 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
{
|
{
|
||||||
name: "TooManyRetries",
|
name: "TooManyRetries",
|
||||||
pagerResults: append(
|
pagerResults: append(
|
||||||
[]pagerResult{
|
[]mock.PagerResult{
|
||||||
{
|
{
|
||||||
drives: resultDrives[:numDriveResults/2],
|
Drives: resultDrives[:numDriveResults/2],
|
||||||
nextLink: &link,
|
NextLink: &link,
|
||||||
err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tooManyRetries...,
|
tooManyRetries...,
|
||||||
@ -308,11 +263,11 @@ func (suite *OneDriveUnitSuite) TestDrives() {
|
|||||||
ctx, flush := tester.NewContext()
|
ctx, flush := tester.NewContext()
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
pager := &mockDrivePager{
|
pager := &mock.DrivePager{
|
||||||
toReturn: test.pagerResults,
|
ToReturn: test.pagerResults,
|
||||||
}
|
}
|
||||||
|
|
||||||
drives, err := drives(ctx, pager, test.retry)
|
drives, err := api.GetAllDrives(ctx, pager, test.retry, maxDrivesRetries)
|
||||||
test.expectedErr(t, err, clues.ToCore(err))
|
test.expectedErr(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
assert.ElementsMatch(t, test.expectedResults, drives)
|
assert.ElementsMatch(t, test.expectedResults, drives)
|
||||||
@ -353,7 +308,7 @@ func (suite *OneDriveSuite) TestCreateGetDeleteFolder() {
|
|||||||
pager, err := PagerForSource(OneDriveSource, gs, suite.userID, nil)
|
pager, err := PagerForSource(OneDriveSource, gs, suite.userID, nil)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
drives, err := drives(ctx, pager, true)
|
drives, err := api.GetAllDrives(ctx, pager, true, maxDrivesRetries)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
require.NotEmpty(t, drives)
|
require.NotEmpty(t, drives)
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"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/onedrive/api"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
)
|
)
|
||||||
@ -47,7 +48,7 @@ func (suite *ItemIntegrationSuite) SetupSuite() {
|
|||||||
pager, err := PagerForSource(OneDriveSource, suite.service, suite.user, nil)
|
pager, err := PagerForSource(OneDriveSource, suite.service, suite.user, nil)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
odDrives, err := drives(ctx, pager, true)
|
odDrives, err := api.GetAllDrives(ctx, pager, true, maxDrivesRetries)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
// Test Requirement 1: Need a drive
|
// Test Requirement 1: Need a drive
|
||||||
require.Greaterf(t, len(odDrives), 0, "user %s does not have a drive", suite.user)
|
require.Greaterf(t, len(odDrives), 0, "user %s does not have a drive", suite.user)
|
||||||
|
|||||||
@ -140,8 +140,8 @@ func RestoreCollection(
|
|||||||
metrics = support.CollectionMetrics{}
|
metrics = support.CollectionMetrics{}
|
||||||
copyBuffer = make([]byte, copyBufferSize)
|
copyBuffer = make([]byte, copyBufferSize)
|
||||||
directory = dc.FullPath()
|
directory = dc.FullPath()
|
||||||
itemInfo details.ItemInfo
|
|
||||||
folderMetas = map[string]Metadata{}
|
folderMetas = map[string]Metadata{}
|
||||||
|
el = errs.Local()
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx, end := D.Span(ctx, "gc:oneDrive:restoreCollection", D.Label("path", directory))
|
ctx, end := D.Span(ctx, "gc:oneDrive:restoreCollection", D.Label("path", directory))
|
||||||
@ -186,16 +186,12 @@ func RestoreCollection(
|
|||||||
drivePath,
|
drivePath,
|
||||||
restoreFolderElements,
|
restoreFolderElements,
|
||||||
colMeta,
|
colMeta,
|
||||||
permissionIDMappings,
|
permissionIDMappings)
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return metrics, folderMetas, permissionIDMappings, clues.Wrap(err, "creating folders for restore")
|
return metrics, folderMetas, permissionIDMappings, clues.Wrap(err, "creating folders for restore")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
items := dc.Items(ctx, errs)
|
||||||
el = errs.Local()
|
|
||||||
items = dc.Items(ctx, errs)
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if el.Failure() != nil {
|
if el.Failure() != nil {
|
||||||
@ -217,129 +213,188 @@ func RestoreCollection(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if backupVersion >= version.OneDrive1DataAndMetaFiles {
|
itemInfo, skipped, err := restoreItem(
|
||||||
name := itemData.UUID()
|
ctx,
|
||||||
|
dc,
|
||||||
|
backupVersion,
|
||||||
|
source,
|
||||||
|
service,
|
||||||
|
drivePath,
|
||||||
|
restoreFolderID,
|
||||||
|
copyBuffer,
|
||||||
|
folderMetas,
|
||||||
|
permissionIDMappings,
|
||||||
|
restorePerms,
|
||||||
|
itemData,
|
||||||
|
itemPath)
|
||||||
|
|
||||||
if strings.HasSuffix(name, DataFileSuffix) {
|
// skipped items don't get counted, but they can error
|
||||||
metrics.Objects++
|
if !skipped {
|
||||||
metrics.Bytes += int64(len(copyBuffer))
|
|
||||||
|
|
||||||
var (
|
|
||||||
itemInfo details.ItemInfo
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if backupVersion < version.OneDrive6NameInMeta {
|
|
||||||
itemInfo, err = restoreV1File(
|
|
||||||
ctx,
|
|
||||||
source,
|
|
||||||
service,
|
|
||||||
drivePath,
|
|
||||||
dc,
|
|
||||||
restoreFolderID,
|
|
||||||
copyBuffer,
|
|
||||||
permissionIDMappings,
|
|
||||||
restorePerms,
|
|
||||||
itemData,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
itemInfo, err = restoreV2File(
|
|
||||||
ctx,
|
|
||||||
source,
|
|
||||||
service,
|
|
||||||
drivePath,
|
|
||||||
dc,
|
|
||||||
restoreFolderID,
|
|
||||||
copyBuffer,
|
|
||||||
permissionIDMappings,
|
|
||||||
restorePerms,
|
|
||||||
itemData,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
el.AddRecoverable(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = deets.Add(
|
|
||||||
itemPath.String(),
|
|
||||||
itemPath.ShortRef(),
|
|
||||||
"",
|
|
||||||
"", // TODO: implement locationRef
|
|
||||||
true,
|
|
||||||
itemInfo)
|
|
||||||
if err != nil {
|
|
||||||
// Not critical enough to need to stop restore operation.
|
|
||||||
logger.Ctx(ctx).Infow("accounting for restored item", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics.Successes++
|
|
||||||
} else if strings.HasSuffix(name, MetaFileSuffix) {
|
|
||||||
// Just skip this for the moment since we moved the code to the above
|
|
||||||
// item restore path. We haven't yet stopped fetching these items in
|
|
||||||
// RestoreOp, so we still need to handle them in some way.
|
|
||||||
continue
|
|
||||||
} else if strings.HasSuffix(name, DirMetaFileSuffix) {
|
|
||||||
// Only the version.OneDrive1DataAndMetaFiles needed to deserialize the
|
|
||||||
// permission for child folders here. Later versions can request
|
|
||||||
// permissions inline when processing the collection.
|
|
||||||
if !restorePerms || backupVersion >= version.OneDrive4DirIncludesPermissions {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
metaReader := itemData.ToReader()
|
|
||||||
defer metaReader.Close()
|
|
||||||
|
|
||||||
meta, err := getMetadata(metaReader)
|
|
||||||
if err != nil {
|
|
||||||
el.AddRecoverable(clues.Wrap(err, "getting directory metadata").WithClues(ctx))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
trimmedPath := strings.TrimSuffix(itemPath.String(), DirMetaFileSuffix)
|
|
||||||
folderMetas[trimmedPath] = meta
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
metrics.Objects++
|
metrics.Objects++
|
||||||
metrics.Bytes += int64(len(copyBuffer))
|
metrics.Bytes += int64(len(copyBuffer))
|
||||||
|
|
||||||
// No permissions stored at the moment for SharePoint
|
|
||||||
_, itemInfo, err = restoreData(
|
|
||||||
ctx,
|
|
||||||
service,
|
|
||||||
itemData.UUID(),
|
|
||||||
itemData,
|
|
||||||
drivePath.DriveID,
|
|
||||||
restoreFolderID,
|
|
||||||
copyBuffer,
|
|
||||||
source)
|
|
||||||
if err != nil {
|
|
||||||
el.AddRecoverable(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = deets.Add(
|
|
||||||
itemPath.String(),
|
|
||||||
itemPath.ShortRef(),
|
|
||||||
"",
|
|
||||||
"", // TODO: implement locationRef
|
|
||||||
true,
|
|
||||||
itemInfo)
|
|
||||||
if err != nil {
|
|
||||||
// Not critical enough to need to stop restore operation.
|
|
||||||
logger.Ctx(ctx).Infow("accounting for restored item", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics.Successes++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
el.AddRecoverable(clues.Wrap(err, "restoring item"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if skipped {
|
||||||
|
logger.Ctx(ctx).With("item_path", itemPath).Debug("did not restore item")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = deets.Add(
|
||||||
|
itemPath.String(),
|
||||||
|
itemPath.ShortRef(),
|
||||||
|
"",
|
||||||
|
"", // TODO: implement locationRef
|
||||||
|
true,
|
||||||
|
itemInfo)
|
||||||
|
if err != nil {
|
||||||
|
// Not critical enough to need to stop restore operation.
|
||||||
|
logger.CtxErr(ctx, err).Infow("adding restored item to details")
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics.Successes++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return metrics, folderMetas, permissionIDMappings, el.Failure()
|
return metrics, folderMetas, permissionIDMappings, el.Failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restores an item, according to correct backup version behavior.
|
||||||
|
// returns the item info, a bool (true = restore was skipped), and an error
|
||||||
|
func restoreItem(
|
||||||
|
ctx context.Context,
|
||||||
|
dc data.RestoreCollection,
|
||||||
|
backupVersion int,
|
||||||
|
source driveSource,
|
||||||
|
service graph.Servicer,
|
||||||
|
drivePath *path.DrivePath,
|
||||||
|
restoreFolderID string,
|
||||||
|
copyBuffer []byte,
|
||||||
|
folderMetas map[string]Metadata,
|
||||||
|
permissionIDMappings map[string]string,
|
||||||
|
restorePerms bool,
|
||||||
|
itemData data.Stream,
|
||||||
|
itemPath path.Path,
|
||||||
|
) (details.ItemInfo, bool, error) {
|
||||||
|
itemUUID := itemData.UUID()
|
||||||
|
|
||||||
|
if backupVersion < version.OneDrive1DataAndMetaFiles {
|
||||||
|
itemInfo, err := restoreV0File(
|
||||||
|
ctx,
|
||||||
|
source,
|
||||||
|
service,
|
||||||
|
drivePath,
|
||||||
|
restoreFolderID,
|
||||||
|
copyBuffer,
|
||||||
|
itemData)
|
||||||
|
if err != nil {
|
||||||
|
return details.ItemInfo{}, false, clues.Wrap(err, "v0 restore")
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemInfo, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// only v1+ backups from this point on
|
||||||
|
|
||||||
|
if strings.HasSuffix(itemUUID, MetaFileSuffix) {
|
||||||
|
// Just skip this for the moment since we moved the code to the above
|
||||||
|
// item restore path. We haven't yet stopped fetching these items in
|
||||||
|
// RestoreOp, so we still need to handle them in some way.
|
||||||
|
return details.ItemInfo{}, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(itemUUID, DirMetaFileSuffix) {
|
||||||
|
// Only the version.OneDrive1DataAndMetaFiles needed to deserialize the
|
||||||
|
// permission for child folders here. Later versions can request
|
||||||
|
// permissions inline when processing the collection.
|
||||||
|
if !restorePerms || backupVersion >= version.OneDrive4DirIncludesPermissions {
|
||||||
|
return details.ItemInfo{}, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
metaReader := itemData.ToReader()
|
||||||
|
defer metaReader.Close()
|
||||||
|
|
||||||
|
meta, err := getMetadata(metaReader)
|
||||||
|
if err != nil {
|
||||||
|
return details.ItemInfo{}, true, clues.Wrap(err, "getting directory metadata").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmedPath := strings.TrimSuffix(itemPath.String(), DirMetaFileSuffix)
|
||||||
|
folderMetas[trimmedPath] = meta
|
||||||
|
|
||||||
|
return details.ItemInfo{}, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// only items with DataFileSuffix from this point on
|
||||||
|
|
||||||
|
if backupVersion < version.OneDrive6NameInMeta {
|
||||||
|
itemInfo, err := restoreV1File(
|
||||||
|
ctx,
|
||||||
|
source,
|
||||||
|
service,
|
||||||
|
drivePath,
|
||||||
|
dc,
|
||||||
|
restoreFolderID,
|
||||||
|
copyBuffer,
|
||||||
|
permissionIDMappings,
|
||||||
|
restorePerms,
|
||||||
|
itemData)
|
||||||
|
if err != nil {
|
||||||
|
return details.ItemInfo{}, false, clues.Wrap(err, "v1 restore")
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemInfo, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// only v6+ backups from this point on
|
||||||
|
|
||||||
|
itemInfo, err := restoreV6File(
|
||||||
|
ctx,
|
||||||
|
source,
|
||||||
|
service,
|
||||||
|
drivePath,
|
||||||
|
dc,
|
||||||
|
restoreFolderID,
|
||||||
|
copyBuffer,
|
||||||
|
permissionIDMappings,
|
||||||
|
restorePerms,
|
||||||
|
itemData)
|
||||||
|
if err != nil {
|
||||||
|
return details.ItemInfo{}, false, clues.Wrap(err, "v6 restore")
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemInfo, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreV0File(
|
||||||
|
ctx context.Context,
|
||||||
|
source driveSource,
|
||||||
|
service graph.Servicer,
|
||||||
|
drivePath *path.DrivePath,
|
||||||
|
restoreFolderID string,
|
||||||
|
copyBuffer []byte,
|
||||||
|
itemData data.Stream,
|
||||||
|
) (details.ItemInfo, error) {
|
||||||
|
_, itemInfo, err := restoreData(
|
||||||
|
ctx,
|
||||||
|
service,
|
||||||
|
itemData.UUID(),
|
||||||
|
itemData,
|
||||||
|
drivePath.DriveID,
|
||||||
|
restoreFolderID,
|
||||||
|
copyBuffer,
|
||||||
|
source)
|
||||||
|
if err != nil {
|
||||||
|
return itemInfo, clues.Wrap(err, "restoring file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
type fileFetcher interface {
|
type fileFetcher interface {
|
||||||
Fetch(ctx context.Context, name string) (data.Stream, error)
|
Fetch(ctx context.Context, name string) (data.Stream, error)
|
||||||
}
|
}
|
||||||
@ -391,8 +446,7 @@ func restoreV1File(
|
|||||||
drivePath.DriveID,
|
drivePath.DriveID,
|
||||||
itemID,
|
itemID,
|
||||||
meta,
|
meta,
|
||||||
permissionIDMappings,
|
permissionIDMappings)
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return details.ItemInfo{}, clues.Wrap(err, "restoring item permissions")
|
return details.ItemInfo{}, clues.Wrap(err, "restoring item permissions")
|
||||||
}
|
}
|
||||||
@ -400,7 +454,7 @@ func restoreV1File(
|
|||||||
return itemInfo, nil
|
return itemInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreV2File(
|
func restoreV6File(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
source driveSource,
|
source driveSource,
|
||||||
service graph.Servicer,
|
service graph.Servicer,
|
||||||
|
|||||||
@ -334,7 +334,7 @@ func getItemStream(
|
|||||||
bcounter ByteCounter,
|
bcounter ByteCounter,
|
||||||
) (data.Stream, error) {
|
) (data.Stream, error) {
|
||||||
if itemPath == nil {
|
if itemPath == nil {
|
||||||
return nil, clues.Stack(errNoRestorePath).WithClues(ctx)
|
return nil, clues.Wrap(errNoRestorePath, "getting item stream").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNestedEntry handles nil properly.
|
// GetNestedEntry handles nil properly.
|
||||||
|
|||||||
@ -197,7 +197,8 @@ func (op *RestoreOperation) do(
|
|||||||
ctx,
|
ctx,
|
||||||
"resource_owner", bup.Selector.DiscreteOwner,
|
"resource_owner", bup.Selector.DiscreteOwner,
|
||||||
"details_paths", len(paths),
|
"details_paths", len(paths),
|
||||||
"backup_snapshot_id", bup.SnapshotID)
|
"backup_snapshot_id", bup.SnapshotID,
|
||||||
|
"backup_version", bup.Version)
|
||||||
|
|
||||||
op.bus.Event(
|
op.bus.Event(
|
||||||
ctx,
|
ctx,
|
||||||
@ -218,7 +219,7 @@ func (op *RestoreOperation) do(
|
|||||||
|
|
||||||
dcs, err := op.kopia.RestoreMultipleItems(ctx, bup.SnapshotID, paths, opStats.bytesRead, op.Errors)
|
dcs, err := op.kopia.RestoreMultipleItems(ctx, bup.SnapshotID, paths, opStats.bytesRead, op.Errors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "retrieving collections from repository")
|
return nil, errors.Wrap(err, "producing collections to restore")
|
||||||
}
|
}
|
||||||
|
|
||||||
kopiaComplete <- struct{}{}
|
kopiaComplete <- struct{}{}
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import (
|
|||||||
"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/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/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"
|
||||||
@ -137,8 +136,10 @@ func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
type bupResults struct {
|
type bupResults struct {
|
||||||
backupID model.StableID
|
selectorResourceOwners []string
|
||||||
items int
|
resourceOwner string
|
||||||
|
backupID model.StableID
|
||||||
|
items int
|
||||||
}
|
}
|
||||||
|
|
||||||
type RestoreOpIntegrationSuite struct {
|
type RestoreOpIntegrationSuite struct {
|
||||||
@ -165,12 +166,13 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext()
|
ctx, flush := tester.NewContext()
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
t := suite.T()
|
var (
|
||||||
m365UserID := tester.M365UserID(t)
|
t = suite.T()
|
||||||
acct := tester.NewM365Account(t)
|
m365UserID = tester.M365UserID(t)
|
||||||
// need to initialize the repository before we can test connecting to it.
|
acct = tester.NewM365Account(t)
|
||||||
st := tester.NewPrefixedS3Storage(t)
|
st = tester.NewPrefixedS3Storage(t)
|
||||||
k := kopia.NewConn(st)
|
k = kopia.NewConn(st)
|
||||||
|
)
|
||||||
|
|
||||||
err := k.Initialize(ctx)
|
err := k.Initialize(ctx)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
@ -192,65 +194,104 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() {
|
|||||||
sw := store.NewKopiaStore(ms)
|
sw := store.NewKopiaStore(ms)
|
||||||
suite.sw = sw
|
suite.sw = sw
|
||||||
|
|
||||||
users := []string{m365UserID}
|
suite.Run("exchange_setup", func() {
|
||||||
|
var (
|
||||||
|
t = suite.T()
|
||||||
|
users = []string{m365UserID}
|
||||||
|
bsel = selectors.NewExchangeBackup(users)
|
||||||
|
)
|
||||||
|
|
||||||
bsel := selectors.NewExchangeBackup(users)
|
bsel.DiscreteOwner = m365UserID
|
||||||
bsel.DiscreteOwner = m365UserID
|
bsel.Include(
|
||||||
bsel.Include(
|
bsel.MailFolders([]string{exchange.DefaultMailFolder}, selectors.PrefixMatch()),
|
||||||
bsel.MailFolders([]string{exchange.DefaultMailFolder}, selectors.PrefixMatch()),
|
bsel.ContactFolders([]string{exchange.DefaultContactFolder}, selectors.PrefixMatch()),
|
||||||
bsel.ContactFolders([]string{exchange.DefaultContactFolder}, selectors.PrefixMatch()),
|
bsel.EventCalendars([]string{exchange.DefaultCalendar}, selectors.PrefixMatch()),
|
||||||
bsel.EventCalendars([]string{exchange.DefaultCalendar}, selectors.PrefixMatch()),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
bo, err := NewBackupOperation(
|
bo, err := NewBackupOperation(
|
||||||
ctx,
|
ctx,
|
||||||
control.Options{},
|
control.Options{},
|
||||||
kw,
|
kw,
|
||||||
sw,
|
sw,
|
||||||
acct,
|
acct,
|
||||||
bsel.Selector,
|
bsel.Selector,
|
||||||
bsel.Selector.DiscreteOwner,
|
bsel.Selector.DiscreteOwner,
|
||||||
evmock.NewBus())
|
evmock.NewBus())
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
err = bo.Run(ctx)
|
err = bo.Run(ctx)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
require.NotEmpty(t, bo.Results.BackupID)
|
require.NotEmpty(t, bo.Results.BackupID)
|
||||||
|
|
||||||
suite.exchange = bupResults{
|
suite.exchange = bupResults{
|
||||||
backupID: bo.Results.BackupID,
|
selectorResourceOwners: users,
|
||||||
// Discount metadata files (3 paths, 3 deltas) as
|
resourceOwner: m365UserID,
|
||||||
// they are not part of the data restored.
|
backupID: bo.Results.BackupID,
|
||||||
items: bo.Results.ItemsWritten - 6,
|
// Discount metadata collection files (1 delta and one prev path for each category).
|
||||||
}
|
// These meta files are used to aid restore, but are not themselves
|
||||||
|
// restored (ie: counted as writes).
|
||||||
|
items: bo.Results.ItemsWritten - 6,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
siteID := tester.M365SiteID(t)
|
suite.Run("sharepoint_setup", func() {
|
||||||
sites := []string{siteID}
|
var (
|
||||||
csel := selectors.NewSharePointBackup(sites)
|
t = suite.T()
|
||||||
csel.DiscreteOwner = siteID
|
siteID = tester.M365SiteID(t)
|
||||||
csel.Include(csel.LibraryFolders(selectors.Any()))
|
sites = []string{siteID}
|
||||||
|
spsel = selectors.NewSharePointBackup(sites)
|
||||||
|
)
|
||||||
|
|
||||||
bo, err = NewBackupOperation(
|
spsel.DiscreteOwner = siteID
|
||||||
ctx,
|
// assume a folder name "test" exists in the drive.
|
||||||
control.Options{},
|
// this is brittle, and requires us to backfill anytime
|
||||||
kw,
|
// the site under test changes, but also prevents explosive
|
||||||
sw,
|
// growth from re-backup/restore of restored files.
|
||||||
acct,
|
spsel.Include(spsel.LibraryFolders([]string{"test"}, selectors.PrefixMatch()))
|
||||||
csel.Selector,
|
|
||||||
csel.Selector.DiscreteOwner,
|
|
||||||
evmock.NewBus())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
err = bo.Run(ctx)
|
bo, err := NewBackupOperation(
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
ctx,
|
||||||
require.NotEmpty(t, bo.Results.BackupID)
|
control.Options{},
|
||||||
|
kw,
|
||||||
|
sw,
|
||||||
|
acct,
|
||||||
|
spsel.Selector,
|
||||||
|
spsel.Selector.DiscreteOwner,
|
||||||
|
evmock.NewBus())
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
suite.sharepoint = bupResults{
|
// get the count of drives
|
||||||
backupID: bo.Results.BackupID,
|
m365, err := acct.M365Config()
|
||||||
// Discount metadata files (2 paths, 2 deltas) as
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
// they are not part of the data restored.
|
|
||||||
items: bo.Results.ItemsWritten - 4,
|
adpt, err := graph.CreateAdapter(
|
||||||
}
|
m365.AzureTenantID,
|
||||||
|
m365.AzureClientID,
|
||||||
|
m365.AzureClientSecret)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
service := graph.NewService(adpt)
|
||||||
|
spPgr := api.NewSiteDrivePager(service, siteID, []string{"id", "name"})
|
||||||
|
|
||||||
|
drives, err := api.GetAllDrives(ctx, spPgr, true, 3)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
err = bo.Run(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
require.NotEmpty(t, bo.Results.BackupID)
|
||||||
|
|
||||||
|
suite.sharepoint = bupResults{
|
||||||
|
selectorResourceOwners: sites,
|
||||||
|
resourceOwner: siteID,
|
||||||
|
backupID: bo.Results.BackupID,
|
||||||
|
// Discount metadata files (1 delta, 1 prev path)
|
||||||
|
// assume only one folder, and therefore 1 dirmeta per drive
|
||||||
|
// assume only one file in each folder, and therefore 1 meta per drive.
|
||||||
|
// These meta files are used to aid restore, but are not themselves
|
||||||
|
// restored (ie: counted as writes).
|
||||||
|
items: bo.Results.ItemsWritten - 2 - len(drives) - len(drives),
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *RestoreOpIntegrationSuite) TearDownSuite() {
|
func (suite *RestoreOpIntegrationSuite) TearDownSuite() {
|
||||||
@ -328,8 +369,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
|||||||
expectedItems: suite.exchange.items,
|
expectedItems: suite.exchange.items,
|
||||||
dest: tester.DefaultTestRestoreDestination(),
|
dest: tester.DefaultTestRestoreDestination(),
|
||||||
getSelector: func(t *testing.T) selectors.Selector {
|
getSelector: func(t *testing.T) selectors.Selector {
|
||||||
users := []string{tester.M365UserID(t)}
|
rsel := selectors.NewExchangeRestore(suite.exchange.selectorResourceOwners)
|
||||||
rsel := selectors.NewExchangeRestore(users)
|
|
||||||
rsel.Include(rsel.AllData())
|
rsel.Include(rsel.AllData())
|
||||||
|
|
||||||
return rsel.Selector
|
return rsel.Selector
|
||||||
@ -341,46 +381,19 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
|||||||
expectedItems: suite.sharepoint.items,
|
expectedItems: suite.sharepoint.items,
|
||||||
dest: control.DefaultRestoreDestination(common.SimpleDateTimeOneDrive),
|
dest: control.DefaultRestoreDestination(common.SimpleDateTimeOneDrive),
|
||||||
getSelector: func(t *testing.T) selectors.Selector {
|
getSelector: func(t *testing.T) selectors.Selector {
|
||||||
bsel := selectors.NewSharePointRestore([]string{tester.M365SiteID(t)})
|
rsel := selectors.NewSharePointRestore(suite.sharepoint.selectorResourceOwners)
|
||||||
bsel.Include(bsel.AllData())
|
rsel.Include(rsel.AllData())
|
||||||
|
|
||||||
return bsel.Selector
|
return rsel.Selector
|
||||||
},
|
|
||||||
cleanup: func(t *testing.T, dest string) {
|
|
||||||
ctx, flush := tester.NewContext()
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
act := tester.NewM365Account(t)
|
|
||||||
|
|
||||||
m365, err := act.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
adpt, err := graph.CreateAdapter(
|
|
||||||
m365.AzureTenantID,
|
|
||||||
m365.AzureClientID,
|
|
||||||
m365.AzureClientSecret)
|
|
||||||
require.NoError(t, err, clues.ToCore(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, clues.ToCore(err))
|
|
||||||
require.NotEmpty(t, driveID)
|
|
||||||
|
|
||||||
folderID, err := pager.GetFolderIDByName(ctx, driveID, dest)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
require.NotEmpty(t, folderID)
|
|
||||||
|
|
||||||
err = onedrive.DeleteItem(ctx, service, driveID, folderID)
|
|
||||||
assert.NoError(t, err, "deleting restore folder", clues.ToCore(err))
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tables {
|
for _, test := range tables {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
mb := evmock.NewBus()
|
mb := evmock.NewBus()
|
||||||
|
|
||||||
ro, err := NewRestoreOperation(
|
ro, err := NewRestoreOperation(
|
||||||
ctx,
|
ctx,
|
||||||
control.Options{FailFast: true},
|
control.Options{FailFast: true},
|
||||||
@ -408,11 +421,6 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
|||||||
assert.Equal(t, test.expectedItems, ro.Results.ItemsWritten, "backup and restore wrote the same num of items")
|
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.RestoreStart], "restore-start events")
|
||||||
assert.Equal(t, 1, mb.TimesCalled[events.RestoreEnd], "restore-end events")
|
assert.Equal(t, 1, mb.TimesCalled[events.RestoreEnd], "restore-end events")
|
||||||
|
|
||||||
// clean up
|
|
||||||
if test.cleanup != nil {
|
|
||||||
test.cleanup(t, test.dest.ContainerName)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -112,36 +112,31 @@ func readTestConfig() (map[string]string, error) {
|
|||||||
TestCfgUserID,
|
TestCfgUserID,
|
||||||
os.Getenv(EnvCorsoM365TestUserID),
|
os.Getenv(EnvCorsoM365TestUserID),
|
||||||
vpr.GetString(TestCfgUserID),
|
vpr.GetString(TestCfgUserID),
|
||||||
"LynneR@10rqc2.onmicrosoft.com",
|
"LynneR@10rqc2.onmicrosoft.com")
|
||||||
)
|
|
||||||
fallbackTo(
|
fallbackTo(
|
||||||
testEnv,
|
testEnv,
|
||||||
TestCfgSecondaryUserID,
|
TestCfgSecondaryUserID,
|
||||||
os.Getenv(EnvCorsoSecondaryM365TestUserID),
|
os.Getenv(EnvCorsoSecondaryM365TestUserID),
|
||||||
vpr.GetString(TestCfgSecondaryUserID),
|
vpr.GetString(TestCfgSecondaryUserID),
|
||||||
"AdeleV@10rqc2.onmicrosoft.com",
|
"AdeleV@10rqc2.onmicrosoft.com")
|
||||||
)
|
|
||||||
fallbackTo(
|
fallbackTo(
|
||||||
testEnv,
|
testEnv,
|
||||||
TestCfgLoadTestUserID,
|
TestCfgLoadTestUserID,
|
||||||
os.Getenv(EnvCorsoM365LoadTestUserID),
|
os.Getenv(EnvCorsoM365LoadTestUserID),
|
||||||
vpr.GetString(TestCfgLoadTestUserID),
|
vpr.GetString(TestCfgLoadTestUserID),
|
||||||
"leeg@10rqc2.onmicrosoft.com",
|
"leeg@10rqc2.onmicrosoft.com")
|
||||||
)
|
|
||||||
fallbackTo(
|
fallbackTo(
|
||||||
testEnv,
|
testEnv,
|
||||||
TestCfgLoadTestOrgUsers,
|
TestCfgLoadTestOrgUsers,
|
||||||
os.Getenv(EnvCorsoM365LoadTestOrgUsers),
|
os.Getenv(EnvCorsoM365LoadTestOrgUsers),
|
||||||
vpr.GetString(TestCfgLoadTestOrgUsers),
|
vpr.GetString(TestCfgLoadTestOrgUsers),
|
||||||
"AdeleV@10rqc2.onmicrosoft.com,LynneR@10rqc2.onmicrosoft.com",
|
"AdeleV@10rqc2.onmicrosoft.com,LynneR@10rqc2.onmicrosoft.com")
|
||||||
)
|
|
||||||
fallbackTo(
|
fallbackTo(
|
||||||
testEnv,
|
testEnv,
|
||||||
TestCfgSiteID,
|
TestCfgSiteID,
|
||||||
os.Getenv(EnvCorsoM365TestSiteID),
|
os.Getenv(EnvCorsoM365TestSiteID),
|
||||||
vpr.GetString(TestCfgSiteID),
|
vpr.GetString(TestCfgSiteID),
|
||||||
"10rqc2.sharepoint.com,bb5d5b4a-e089-4e66-9868-9e263ecc635d,4fa3a2c0-fa81-4e6f-8e8b-1479a8927bc6",
|
"10rqc2.sharepoint.com,4892edf5-2ebf-46be-a6e5-a40b2cbf1c1a,38ab6d06-fc82-4417-af93-22d8733c22be")
|
||||||
)
|
|
||||||
|
|
||||||
testEnv[EnvCorsoTestConfigFilePath] = os.Getenv(EnvCorsoTestConfigFilePath)
|
testEnv[EnvCorsoTestConfigFilePath] = os.Getenv(EnvCorsoTestConfigFilePath)
|
||||||
testConfig = testEnv
|
testConfig = testEnv
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user