OneDrive Items API for mocking (#2322)

## Description

Create a pager for drive items that allows for better testing via mocking. Increased testing will come in later PRs

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

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No 

## Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Test
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

## Issue(s)

* #2264

## Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
ashmrtn 2023-02-01 09:03:23 -08:00 committed by GitHub
parent 3fd3da7caf
commit 1594a86c22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 137 additions and 57 deletions

View File

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
msdrives "github.com/microsoftgraph/msgraph-sdk-go/drives"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
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"
@ -12,6 +13,65 @@ import (
"github.com/alcionai/corso/src/internal/connector/graph/api" "github.com/alcionai/corso/src/internal/connector/graph/api"
) )
func getValues[T any](l api.PageLinker) ([]T, error) {
page, ok := l.(interface{ GetValue() []T })
if !ok {
return nil, errors.Errorf(
"response of type [%T] does not comply with GetValue() interface",
l,
)
}
return page.GetValue(), nil
}
// max we can do is 999
const pageSize = int32(999)
type driveItemPager struct {
gs graph.Servicer
builder *msdrives.ItemRootDeltaRequestBuilder
options *msdrives.ItemRootDeltaRequestBuilderGetRequestConfiguration
}
func NewItemPager(
gs graph.Servicer,
driveID, link string,
fields []string,
) *driveItemPager {
pageCount := pageSize
requestConfig := &msdrives.ItemRootDeltaRequestBuilderGetRequestConfiguration{
QueryParameters: &msdrives.ItemRootDeltaRequestBuilderGetQueryParameters{
Top: &pageCount,
Select: fields,
},
}
res := &driveItemPager{
gs: gs,
options: requestConfig,
builder: gs.Client().DrivesById(driveID).Root().Delta(),
}
if len(link) > 0 {
res.builder = msdrives.NewItemRootDeltaRequestBuilder(link, gs.Adapter())
}
return res
}
func (p *driveItemPager) GetPage(ctx context.Context) (api.DeltaPageLinker, error) {
return p.builder.Get(ctx, p.options)
}
func (p *driveItemPager) SetNext(link string) {
p.builder = msdrives.NewItemRootDeltaRequestBuilder(link, p.gs.Adapter())
}
func (p *driveItemPager) ValuesIn(l api.DeltaPageLinker) ([]models.DriveItemable, error) {
return getValues[models.DriveItemable](l)
}
type userDrivePager struct { type userDrivePager struct {
gs graph.Servicer gs graph.Servicer
builder *msusers.ItemDrivesRequestBuilder builder *msusers.ItemDrivesRequestBuilder
@ -47,15 +107,7 @@ func (p *userDrivePager) SetNext(link string) {
} }
func (p *userDrivePager) ValuesIn(l api.PageLinker) ([]models.Driveable, error) { func (p *userDrivePager) ValuesIn(l api.PageLinker) ([]models.Driveable, error) {
page, ok := l.(interface{ GetValue() []models.Driveable }) return getValues[models.Driveable](l)
if !ok {
return nil, errors.Errorf(
"response of type [%T] does not comply with GetValue() interface",
l,
)
}
return page.GetValue(), nil
} }
type siteDrivePager struct { type siteDrivePager struct {
@ -93,13 +145,5 @@ 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) {
page, ok := l.(interface{ GetValue() []models.Driveable }) return getValues[models.Driveable](l)
if !ok {
return nil, errors.Errorf(
"response of type [%T] does not comply with GetValue() interface",
l,
)
}
return page.GetValue(), nil
} }

View File

@ -65,6 +65,13 @@ type Collections struct {
// for a OneDrive folder // for a OneDrive folder
CollectionMap map[string]data.Collection CollectionMap map[string]data.Collection
// Not the most ideal, but allows us to change the pager function for testing
// as needed. This will allow us to mock out some scenarios during testing.
itemPagerFunc func(
servicer graph.Servicer,
driveID, link string,
) itemPager
// Track stats from drive enumeration. Represents the items backed up. // Track stats from drive enumeration. Represents the items backed up.
NumItems int NumItems int
NumFiles int NumFiles int
@ -88,6 +95,7 @@ func NewCollections(
source: source, source: source,
matcher: matcher, matcher: matcher,
CollectionMap: map[string]data.Collection{}, CollectionMap: map[string]data.Collection{},
itemPagerFunc: defaultItemPager,
service: service, service: service,
statusUpdater: statusUpdater, statusUpdater: statusUpdater,
ctrl: ctrlOpts, ctrl: ctrlOpts,
@ -266,7 +274,11 @@ func (c *Collections) Get(
delta, paths, excluded, err := collectItems( delta, paths, excluded, err := collectItems(
ctx, ctx,
c.service, c.itemPagerFunc(
c.service,
driveID,
"",
),
driveID, driveID,
driveName, driveName,
c.UpdateCollections, c.UpdateCollections,

View File

@ -7,7 +7,6 @@ import (
"time" "time"
msdrive "github.com/microsoftgraph/msgraph-sdk-go/drive" msdrive "github.com/microsoftgraph/msgraph-sdk-go/drive"
msdrives "github.com/microsoftgraph/msgraph-sdk-go/drives"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -135,11 +134,42 @@ type itemCollector func(
excluded map[string]struct{}, excluded map[string]struct{},
) error ) error
type itemPager interface {
GetPage(context.Context) (gapi.DeltaPageLinker, error)
SetNext(nextLink string)
ValuesIn(gapi.DeltaPageLinker) ([]models.DriveItemable, error)
}
func defaultItemPager(
servicer graph.Servicer,
driveID, link string,
) itemPager {
return api.NewItemPager(
servicer,
driveID,
link,
[]string{
"content.downloadUrl",
"createdBy",
"createdDateTime",
"file",
"folder",
"id",
"lastModifiedDateTime",
"name",
"package",
"parentReference",
"root",
"size",
},
)
}
// collectItems will enumerate all items in the specified drive and hand them to the // collectItems will enumerate all items in the specified drive and hand them to the
// provided `collector` method // provided `collector` method
func collectItems( func collectItems(
ctx context.Context, ctx context.Context,
service graph.Servicer, pager itemPager,
driveID, driveName string, driveID, driveName string,
collector itemCollector, collector itemCollector,
) (string, map[string]string, map[string]struct{}, error) { ) (string, map[string]string, map[string]struct{}, error) {
@ -154,34 +184,8 @@ func collectItems(
maps.Copy(newPaths, oldPaths) maps.Copy(newPaths, oldPaths)
// TODO: Specify a timestamp in the delta query
// https://docs.microsoft.com/en-us/graph/api/driveitem-delta?
// view=graph-rest-1.0&tabs=http#example-4-retrieving-delta-results-using-a-timestamp
builder := service.Client().DrivesById(driveID).Root().Delta()
pageCount := int32(999) // max we can do is 999
requestFields := []string{
"content.downloadUrl",
"createdBy",
"createdDateTime",
"file",
"folder",
"id",
"lastModifiedDateTime",
"name",
"package",
"parentReference",
"root",
"size",
}
requestConfig := &msdrives.ItemRootDeltaRequestBuilderGetRequestConfiguration{
QueryParameters: &msdrives.ItemRootDeltaRequestBuilderGetQueryParameters{
Top: &pageCount,
Select: requestFields,
},
}
for { for {
r, err := builder.Get(ctx, requestConfig) page, err := pager.GetPage(ctx)
if err != nil { if err != nil {
return "", nil, nil, errors.Wrapf( return "", nil, nil, errors.Wrapf(
err, err,
@ -190,23 +194,29 @@ func collectItems(
) )
} }
err = collector(ctx, driveID, driveName, r.GetValue(), oldPaths, newPaths, excluded) vals, err := pager.ValuesIn(page)
if err != nil {
return "", nil, nil, errors.Wrap(err, "extracting items from response")
}
err = collector(ctx, driveID, driveName, vals, oldPaths, newPaths, excluded)
if err != nil { if err != nil {
return "", nil, nil, err return "", nil, nil, err
} }
if r.GetOdataDeltaLink() != nil && len(*r.GetOdataDeltaLink()) > 0 { nextLink, deltaLink := gapi.NextAndDeltaLink(page)
newDeltaURL = *r.GetOdataDeltaLink()
if len(deltaLink) > 0 {
newDeltaURL = deltaLink
} }
// Check if there are more items // Check if there are more items
nextLink := r.GetOdataNextLink() if len(nextLink) == 0 {
if nextLink == nil {
break break
} }
logger.Ctx(ctx).Debugf("Found %s nextLink", *nextLink) logger.Ctx(ctx).Debugw("Found nextLink", "link", nextLink)
builder = msdrives.NewItemRootDeltaRequestBuilder(*nextLink, service.Adapter()) pager.SetNext(nextLink)
} }
return newDeltaURL, newPaths, excluded, nil return newDeltaURL, newPaths, excluded, nil
@ -318,7 +328,11 @@ func GetAllFolders(
for _, d := range drives { for _, d := range drives {
_, _, _, err = collectItems( _, _, _, err = collectItems(
ctx, ctx,
gs, defaultItemPager(
gs,
*d.GetId(),
"",
),
*d.GetId(), *d.GetId(),
*d.GetName(), *d.GetName(),
func( func(

View File

@ -115,7 +115,17 @@ func (suite *ItemIntegrationSuite) TestItemReader_oneDrive() {
return nil return nil
} }
_, _, _, err := collectItems(ctx, suite, suite.userDriveID, "General", itemCollector) _, _, _, err := collectItems(
ctx,
defaultItemPager(
suite,
suite.userDriveID,
"",
),
suite.userDriveID,
"General",
itemCollector,
)
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
// Test Requirement 2: Need a file // Test Requirement 2: Need a file