Adds the foundation of sharepoint data coll (#1508)

## Description

boilerplate to graph connector.  To minimize
changes, some dependencies or half-baked
solutions have been listed as TODOs for follow-
up.

## Type of change

- [x] 🌻 Feature

## Issue(s)

* #1506

## Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2022-11-17 14:49:45 -07:00 committed by GitHub
parent 2c913f9ef7
commit f1de0eb2b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 552 additions and 120 deletions

View File

@ -11,6 +11,7 @@ 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/onedrive" "github.com/alcionai/corso/src/internal/connector/onedrive"
"github.com/alcionai/corso/src/internal/connector/sharepoint"
"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"
D "github.com/alcionai/corso/src/internal/diagnostics" D "github.com/alcionai/corso/src/internal/diagnostics"
@ -38,6 +39,8 @@ func (gc *GraphConnector) DataCollections(ctx context.Context, sels selectors.Se
return gc.ExchangeDataCollection(ctx, sels) return gc.ExchangeDataCollection(ctx, sels)
case selectors.ServiceOneDrive: case selectors.ServiceOneDrive:
return gc.OneDriveDataCollections(ctx, sels) return gc.OneDriveDataCollections(ctx, sels)
case selectors.ServiceSharePoint:
return gc.SharePointDataCollections(ctx, sels)
default: default:
return nil, errors.Errorf("service %s not supported", sels) return nil, errors.Errorf("service %s not supported", sels)
} }
@ -89,18 +92,23 @@ func verifyBackupInputs(sel selectors.Selector, mapOfUsers map[string]string) er
return nil return nil
} }
// createCollections - utility function that retrieves M365 // ---------------------------------------------------------------------------
// Exchange
// ---------------------------------------------------------------------------
// createExchangeCollections - utility function that retrieves M365
// IDs through Microsoft Graph API. The selectors.ExchangeScope // IDs through Microsoft Graph API. The selectors.ExchangeScope
// determines the type of collections that are stored. // determines the type of collections that are retrieved.
// to the GraphConnector struct. func (gc *GraphConnector) createExchangeCollections(
func (gc *GraphConnector) createCollections(
ctx context.Context, ctx context.Context,
scope selectors.ExchangeScope, scope selectors.ExchangeScope,
) ([]*exchange.Collection, error) { ) ([]*exchange.Collection, error) {
var errs *multierror.Error var (
errs *multierror.Error
users = scope.Get(selectors.ExchangeUser)
allCollections = make([]*exchange.Collection, 0)
)
users := scope.Get(selectors.ExchangeUser)
allCollections := make([]*exchange.Collection, 0)
// Create collection of ExchangeDataCollection // Create collection of ExchangeDataCollection
for _, user := range users { for _, user := range users {
collections := make(map[string]*exchange.Collection) collections := make(map[string]*exchange.Collection)
@ -172,7 +180,7 @@ func (gc *GraphConnector) ExchangeDataCollection(
for _, scope := range scopes { for _, scope := range scopes {
// Creates a map of collections based on scope // Creates a map of collections based on scope
dcs, err := gc.createCollections(ctx, scope) dcs, err := gc.createExchangeCollections(ctx, scope)
if err != nil { if err != nil {
user := scope.Get(selectors.ExchangeUser) user := scope.Get(selectors.ExchangeUser)
return nil, support.WrapAndAppend(user[0], err, errs) return nil, support.WrapAndAppend(user[0], err, errs)
@ -186,6 +194,10 @@ func (gc *GraphConnector) ExchangeDataCollection(
return collections, errs return collections, errs
} }
// ---------------------------------------------------------------------------
// OneDrive
// ---------------------------------------------------------------------------
// OneDriveDataCollections returns a set of DataCollection which represents the OneDrive data // OneDriveDataCollections returns a set of DataCollection which represents the OneDrive data
// for the specified user // for the specified user
func (gc *GraphConnector) OneDriveDataCollections( func (gc *GraphConnector) OneDriveDataCollections(
@ -197,11 +209,11 @@ func (gc *GraphConnector) OneDriveDataCollections(
return nil, errors.Wrap(err, "oneDriveDataCollection: parsing selector") return nil, errors.Wrap(err, "oneDriveDataCollection: parsing selector")
} }
collections := []data.Collection{} var (
scopes = odb.DiscreteScopes(gc.GetUsers())
scopes := odb.DiscreteScopes(gc.GetUsers()) collections = []data.Collection{}
errs error
var errs error )
// for each scope that includes oneDrive items, get all // for each scope that includes oneDrive items, get all
for _, scope := range scopes { for _, scope := range scopes {
@ -229,3 +241,109 @@ func (gc *GraphConnector) OneDriveDataCollections(
return collections, errs return collections, errs
} }
// ---------------------------------------------------------------------------
// SharePoint
// ---------------------------------------------------------------------------
// createSharePointCollections - utility function that retrieves M365
// IDs through Microsoft Graph API. The selectors.SharePointScope
// determines the type of collections that are retrieved.
func (gc *GraphConnector) createSharePointCollections(
ctx context.Context,
scope selectors.SharePointScope,
) ([]*sharepoint.Collection, error) {
var (
errs *multierror.Error
sites = scope.Get(selectors.SharePointSite)
colls = make([]*sharepoint.Collection, 0)
)
// Create collection of ExchangeDataCollection
for _, site := range sites {
collections := make(map[string]*sharepoint.Collection)
qp := graph.QueryParams{
// TODO: Resource owner, not user/site.
User: site,
// TODO: generic scope handling in query params.
// - or, break scope out of QP.
// Scope: scope,
FailFast: gc.failFast,
Credentials: gc.credentials,
}
itemCategory := qp.Scope.Category().PathType()
foldersComplete, closer := observe.MessageWithCompletion(fmt.Sprintf("∙ %s - %s:", itemCategory.String(), site))
defer closer()
defer close(foldersComplete)
// resolver, err := exchange.PopulateExchangeContainerResolver(
// ctx,
// qp,
// qp.Scope.Category().PathType(),
// )
// if err != nil {
// return nil, errors.Wrap(err, "getting folder cache")
// }
// err = sharepoint.FilterContainersAndFillCollections(
// ctx,
// qp,
// collections,
// gc.UpdateStatus,
// resolver)
// if err != nil {
// return nil, errors.Wrap(err, "filling collections")
// }
foldersComplete <- struct{}{}
for _, collection := range collections {
gc.incrementAwaitingMessages()
colls = append(colls, collection)
}
}
return colls, errs.ErrorOrNil()
}
// SharePointDataCollections returns a set of DataCollection which represents the SharePoint data
// for the specified user
func (gc *GraphConnector) SharePointDataCollections(
ctx context.Context,
selector selectors.Selector,
) ([]data.Collection, error) {
b, err := selector.ToSharePointBackup()
if err != nil {
return nil, errors.Wrap(err, "sharePointDataCollection: parsing selector")
}
var (
scopes = b.DiscreteScopes(gc.GetSites())
collections = []data.Collection{}
errs error
)
// for each scope that includes oneDrive items, get all
for _, scope := range scopes {
// Creates a map of collections based on scope
dcs, err := gc.createSharePointCollections(ctx, scope)
if err != nil {
return nil, support.WrapAndAppend(scope.Get(selectors.SharePointSite)[0], err, errs)
}
for _, collection := range dcs {
collections = append(collections, collection)
}
}
for range collections {
gc.incrementAwaitingMessages()
}
return collections, errs
}

View File

@ -10,7 +10,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/support" "github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
) )
@ -23,6 +22,7 @@ type ConnectorDataCollectionIntegrationSuite struct {
suite.Suite suite.Suite
connector *GraphConnector connector *GraphConnector
user string user string
site string
} }
func TestConnectorDataCollectionIntegrationSuite(t *testing.T) { func TestConnectorDataCollectionIntegrationSuite(t *testing.T) {
@ -44,6 +44,7 @@ func (suite *ConnectorDataCollectionIntegrationSuite) SetupSuite() {
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
suite.connector = loadConnector(ctx, suite.T()) suite.connector = loadConnector(ctx, suite.T())
suite.user = tester.M365UserID(suite.T()) suite.user = tester.M365UserID(suite.T())
suite.site = tester.M365SiteID(suite.T())
tester.LogTimeOfTest(suite.T()) tester.LogTimeOfTest(suite.T())
} }
@ -154,82 +155,76 @@ func (suite *ConnectorDataCollectionIntegrationSuite) TestInvalidUserForDataColl
} }
} }
func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() { // TestSharePointDataCollection verifies interface between operation and
dest := tester.DefaultTestRestoreDestination() // GraphConnector remains stable to receive a non-zero amount of Collections
table := []struct { // for the SharePoint Package.
func (suite *ConnectorDataCollectionIntegrationSuite) TestSharePointDataCollection() {
ctx, flush := tester.NewContext()
defer flush()
connector := loadConnector(ctx, suite.T())
tests := []struct {
name string name string
col []data.Collection getSelector func(t *testing.T) selectors.Selector
sel selectors.Selector
}{ }{
{ {
name: "ExchangeNil", name: "Items - TODO: actual sharepoint categories",
col: nil, getSelector: func(t *testing.T) selectors.Selector {
sel: selectors.Selector{ sel := selectors.NewSharePointBackup()
Service: selectors.ServiceExchange, sel.Include(sel.Folders([]string{suite.site}, selectors.Any()))
},
}, return sel.Selector
{
name: "ExchangeEmpty",
col: []data.Collection{},
sel: selectors.Selector{
Service: selectors.ServiceExchange,
},
},
{
name: "OneDriveNil",
col: nil,
sel: selectors.Selector{
Service: selectors.ServiceOneDrive,
},
},
{
name: "OneDriveEmpty",
col: []data.Collection{},
sel: selectors.Selector{
Service: selectors.ServiceOneDrive,
}, },
}, },
} }
for _, test := range table { for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
ctx, flush := tester.NewContext() _, err := connector.SharePointDataCollections(ctx, test.getSelector(t))
defer flush()
deets, err := suite.connector.RestoreDataCollections(ctx, test.sel, dest, test.col)
require.NoError(t, err) require.NoError(t, err)
assert.NotNil(t, deets)
stats := suite.connector.AwaitStatus() // TODO: Implementation
assert.Zero(t, stats.ObjectCount) // assert.Equal(t, len(collection), 1)
assert.Zero(t, stats.FolderCount)
assert.Zero(t, stats.Successful) // channel := collection[0].Items()
// for object := range channel {
// buf := &bytes.Buffer{}
// _, err := buf.ReadFrom(object.ToReader())
// assert.NoError(t, err, "received a buf.Read error")
// }
// status := connector.AwaitStatus()
// assert.NotZero(t, status.Successful)
// t.Log(status.String())
}) })
} }
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// CreateCollection tests // CreateExchangeCollection tests
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
type ConnectorCreateCollectionIntegrationSuite struct { type ConnectorCreateExchangeCollectionIntegrationSuite struct {
suite.Suite suite.Suite
connector *GraphConnector connector *GraphConnector
user string user string
site string
} }
func TestConnectorCreateCollectionIntegrationSuite(t *testing.T) { func TestConnectorCreateExchangeCollectionIntegrationSuite(t *testing.T) {
if err := tester.RunOnAny( if err := tester.RunOnAny(
tester.CorsoCITests, tester.CorsoCITests,
tester.CorsoConnectorCreateCollectionTests, tester.CorsoConnectorCreateExchangeCollectionTests,
); err != nil { ); err != nil {
t.Skip(err) t.Skip(err)
} }
suite.Run(t, new(ConnectorCreateCollectionIntegrationSuite)) suite.Run(t, new(ConnectorCreateExchangeCollectionIntegrationSuite))
} }
func (suite *ConnectorCreateCollectionIntegrationSuite) SetupSuite() { func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) SetupSuite() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
@ -237,13 +232,62 @@ func (suite *ConnectorCreateCollectionIntegrationSuite) SetupSuite() {
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
suite.connector = loadConnector(ctx, suite.T()) suite.connector = loadConnector(ctx, suite.T())
suite.user = tester.M365UserID(suite.T()) suite.user = tester.M365UserID(suite.T())
suite.site = tester.M365SiteID(suite.T())
tester.LogTimeOfTest(suite.T()) tester.LogTimeOfTest(suite.T())
} }
func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestMailFetch() {
ctx, flush := tester.NewContext()
defer flush()
var (
t = suite.T()
userID = tester.M365UserID(t)
)
tests := []struct {
name string
scope selectors.ExchangeScope
folderNames map[string]struct{}
}{
{
name: "Folder Iterative Check Mail",
scope: selectors.NewExchangeBackup().MailFolders(
[]string{userID},
[]string{exchange.DefaultMailFolder},
selectors.PrefixMatch(),
)[0],
folderNames: map[string]struct{}{
exchange.DefaultMailFolder: {},
},
},
}
gc := loadConnector(ctx, t)
for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) {
collections, err := gc.createExchangeCollections(ctx, test.scope)
require.NoError(t, err)
for _, c := range collections {
require.NotEmpty(t, c.FullPath().Folder())
folder := c.FullPath().Folder()
if _, ok := test.folderNames[folder]; ok {
delete(test.folderNames, folder)
}
}
assert.Empty(t, test.folderNames)
})
}
}
// TestMailSerializationRegression verifies that all mail data stored in the // TestMailSerializationRegression verifies that all mail data stored in the
// test account can be successfully downloaded into bytes and restored into // test account can be successfully downloaded into bytes and restored into
// M365 mail objects // M365 mail objects
func (suite *ConnectorCreateCollectionIntegrationSuite) TestMailSerializationRegression() { func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestMailSerializationRegression() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
@ -251,7 +295,7 @@ func (suite *ConnectorCreateCollectionIntegrationSuite) TestMailSerializationReg
connector := loadConnector(ctx, t) connector := loadConnector(ctx, t)
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
sel.Include(sel.MailFolders([]string{suite.user}, []string{exchange.DefaultMailFolder}, selectors.PrefixMatch())) sel.Include(sel.MailFolders([]string{suite.user}, []string{exchange.DefaultMailFolder}, selectors.PrefixMatch()))
collection, err := connector.createCollections(ctx, sel.Scopes()[0]) collection, err := connector.createExchangeCollections(ctx, sel.Scopes()[0])
require.NoError(t, err) require.NoError(t, err)
for _, edc := range collection { for _, edc := range collection {
@ -278,7 +322,7 @@ func (suite *ConnectorCreateCollectionIntegrationSuite) TestMailSerializationReg
// TestContactSerializationRegression verifies ability to query contact items // TestContactSerializationRegression verifies ability to query contact items
// and to store contact within Collection. Downloaded contacts are run through // and to store contact within Collection. Downloaded contacts are run through
// a regression test to ensure that downloaded items can be uploaded. // a regression test to ensure that downloaded items can be uploaded.
func (suite *ConnectorCreateCollectionIntegrationSuite) TestContactSerializationRegression() { func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestContactSerializationRegression() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
@ -294,7 +338,7 @@ func (suite *ConnectorCreateCollectionIntegrationSuite) TestContactSerialization
scope := selectors. scope := selectors.
NewExchangeBackup(). NewExchangeBackup().
ContactFolders([]string{suite.user}, []string{exchange.DefaultContactFolder}, selectors.PrefixMatch())[0] ContactFolders([]string{suite.user}, []string{exchange.DefaultContactFolder}, selectors.PrefixMatch())[0]
collections, err := connector.createCollections(ctx, scope) collections, err := connector.createExchangeCollections(ctx, scope)
require.NoError(t, err) require.NoError(t, err)
return collections return collections
@ -331,7 +375,7 @@ func (suite *ConnectorCreateCollectionIntegrationSuite) TestContactSerialization
// TestEventsSerializationRegression ensures functionality of createCollections // TestEventsSerializationRegression ensures functionality of createCollections
// to be able to successfully query, download and restore event objects // to be able to successfully query, download and restore event objects
func (suite *ConnectorCreateCollectionIntegrationSuite) TestEventsSerializationRegression() { func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestEventsSerializationRegression() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
@ -347,7 +391,7 @@ func (suite *ConnectorCreateCollectionIntegrationSuite) TestEventsSerializationR
getCollection: func(t *testing.T) []*exchange.Collection { getCollection: func(t *testing.T) []*exchange.Collection {
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
sel.Include(sel.EventCalendars([]string{suite.user}, []string{exchange.DefaultCalendar}, selectors.PrefixMatch())) sel.Include(sel.EventCalendars([]string{suite.user}, []string{exchange.DefaultCalendar}, selectors.PrefixMatch()))
collections, err := connector.createCollections(ctx, sel.Scopes()[0]) collections, err := connector.createExchangeCollections(ctx, sel.Scopes()[0])
require.NoError(t, err) require.NoError(t, err)
return collections return collections
@ -359,7 +403,7 @@ func (suite *ConnectorCreateCollectionIntegrationSuite) TestEventsSerializationR
getCollection: func(t *testing.T) []*exchange.Collection { getCollection: func(t *testing.T) []*exchange.Collection {
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
sel.Include(sel.EventCalendars([]string{suite.user}, []string{"Birthdays"}, selectors.PrefixMatch())) sel.Include(sel.EventCalendars([]string{suite.user}, []string{"Birthdays"}, selectors.PrefixMatch()))
collections, err := connector.createCollections(ctx, sel.Scopes()[0]) collections, err := connector.createExchangeCollections(ctx, sel.Scopes()[0])
require.NoError(t, err) require.NoError(t, err)
return collections return collections
@ -398,7 +442,7 @@ func (suite *ConnectorCreateCollectionIntegrationSuite) TestEventsSerializationR
// messages in their inbox will have a collection returned. // messages in their inbox will have a collection returned.
// The final test insures that more than a 75% of the user collections are // The final test insures that more than a 75% of the user collections are
// returned. If an error was experienced, the test will fail overall // returned. If an error was experienced, the test will fail overall
func (suite *ConnectorCreateCollectionIntegrationSuite) TestAccessOfInboxAllUsers() { func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestAccessOfInboxAllUsers() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
@ -411,8 +455,60 @@ func (suite *ConnectorCreateCollectionIntegrationSuite) TestAccessOfInboxAllUser
for _, scope := range scopes { for _, scope := range scopes {
users := scope.Get(selectors.ExchangeUser) users := scope.Get(selectors.ExchangeUser)
standard := (len(users) / 4) * 3 standard := (len(users) / 4) * 3
collections, err := connector.createCollections(ctx, scope) collections, err := connector.createExchangeCollections(ctx, scope)
require.NoError(t, err) require.NoError(t, err)
suite.Greater(len(collections), standard) suite.Greater(len(collections), standard)
} }
} }
// ---------------------------------------------------------------------------
// CreateSharePointCollection tests
// ---------------------------------------------------------------------------
type ConnectorCreateSharePointCollectionIntegrationSuite struct {
suite.Suite
connector *GraphConnector
user string
}
func TestConnectorCreateSharePointCollectionIntegrationSuite(t *testing.T) {
if err := tester.RunOnAny(
tester.CorsoCITests,
tester.CorsoConnectorCreateSharePointCollectionTests,
); err != nil {
t.Skip(err)
}
suite.Run(t, new(ConnectorCreateSharePointCollectionIntegrationSuite))
}
func (suite *ConnectorCreateSharePointCollectionIntegrationSuite) SetupSuite() {
ctx, flush := tester.NewContext()
defer flush()
_, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...)
require.NoError(suite.T(), err)
suite.connector = loadConnector(ctx, suite.T())
suite.user = tester.M365UserID(suite.T())
tester.LogTimeOfTest(suite.T())
}
func (suite *ConnectorCreateSharePointCollectionIntegrationSuite) TestCreateSharePointCollection() {
ctx, flush := tester.NewContext()
defer flush()
var (
t = suite.T()
userID = tester.M365UserID(t)
)
gc := loadConnector(ctx, t)
scope := selectors.NewSharePointBackup().Folders(
[]string{userID},
[]string{"foo"},
selectors.PrefixMatch(),
)[0]
_, err := gc.createSharePointCollections(ctx, scope)
require.NoError(t, err)
}

View File

@ -36,6 +36,7 @@ type GraphConnector struct {
graphService graphService
tenant string tenant string
Users map[string]string // key<email> value<id> Users map[string]string // key<email> value<id>
Sites map[string]string // key<???> value<???>
credentials account.M365Config credentials account.M365Config
// wg is used to track completion of GC tasks // wg is used to track completion of GC tasks
@ -97,6 +98,12 @@ func NewGraphConnector(ctx context.Context, acct account.Account) (*GraphConnect
return nil, errors.Wrap(err, "retrieving tenant user list") return nil, errors.Wrap(err, "retrieving tenant user list")
} }
// TODO: users or sites, one or the other, not both.
err = gc.setTenantSites(ctx)
if err != nil {
return nil, errors.Wrap(err, "retrieveing tenant site list")
}
return &gc, nil return &gc, nil
} }
@ -126,7 +133,7 @@ func (gs *graphService) EnableFailFast() {
// setTenantUsers queries the M365 to identify the users in the // setTenantUsers queries the M365 to identify the users in the
// workspace. The users field is updated during this method // workspace. The users field is updated during this method
// iff the return value is true // iff the return value is nil
func (gc *GraphConnector) setTenantUsers(ctx context.Context) error { func (gc *GraphConnector) setTenantUsers(ctx context.Context) error {
ctx, end := D.Span(ctx, "gc:setTenantUsers") ctx, end := D.Span(ctx, "gc:setTenantUsers")
defer end() defer end()
@ -191,6 +198,79 @@ func (gc *GraphConnector) GetUsersIds() []string {
return buildFromMap(false, gc.Users) return buildFromMap(false, gc.Users)
} }
// setTenantSites queries the M365 to identify the sites in the
// workspace. The sitets field is updated during this method
// iff the return value is nil
func (gc *GraphConnector) setTenantSites(ctx context.Context) error {
// TODO
gc.Sites = map[string]string{}
// ctx, end := D.Span(ctx, "gc:setTenantSites")
// defer end()
// response, err := exchange.GetAllUsersForTenant(ctx, gc.graphService, "")
// if err != nil {
// return errors.Wrapf(
// err,
// "tenant %s M365 query: %s",
// gc.tenant,
// support.ConnectorStackErrorTrace(err),
// )
// }
// userIterator, err := msgraphgocore.NewPageIterator(
// response,
// &gc.graphService.adapter,
// models.CreateUserCollectionResponseFromDiscriminatorValue,
// )
// if err != nil {
// return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
// }
// callbackFunc := func(userItem interface{}) bool {
// user, ok := userItem.(models.Userable)
// if !ok {
// err = support.WrapAndAppend(gc.graphService.adapter.GetBaseUrl(),
// errors.New("received non-User on iteration"), err)
// return true
// }
// if user.GetUserPrincipalName() == nil {
// err = support.WrapAndAppend(
// gc.graphService.adapter.GetBaseUrl(),
// fmt.Errorf("no email address for User: %s", *user.GetId()),
// err,
// )
// return true
// }
// // *user.GetId() is populated for every M365 entityable object by M365 backstore
// gc.Users[*user.GetUserPrincipalName()] = *user.GetId()
// return true
// }
// iterateError := userIterator.Iterate(ctx, callbackFunc)
// if iterateError != nil {
// err = support.WrapAndAppend(gc.graphService.adapter.GetBaseUrl(), iterateError, err)
// }
// return err
return nil
}
// GetSites returns the siteIDs of sharepoint sites within tenant.
func (gc *GraphConnector) GetSites() []string {
return buildFromMap(true, gc.Sites)
}
// GetSiteIds returns the M365 id for the user
func (gc *GraphConnector) GetSiteIds() []string {
return buildFromMap(false, gc.Sites)
}
// buildFromMap helper function for returning []string from map. // buildFromMap helper function for returning []string from map.
// Returns list of keys iff true; otherwise returns a list of values // Returns list of keys iff true; otherwise returns a list of values
func buildFromMap(isKey bool, mapping map[string]string) []string { func buildFromMap(isKey bool, mapping map[string]string) []string {

View File

@ -8,7 +8,6 @@ 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/connector/exchange"
"github.com/alcionai/corso/src/internal/connector/mockconnector" "github.com/alcionai/corso/src/internal/connector/mockconnector"
"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"
@ -69,50 +68,82 @@ func (suite *GraphConnectorIntegrationSuite) TestSetTenantUsers() {
suite.Greater(len(newConnector.Users), 0) suite.Greater(len(newConnector.Users), 0)
} }
func (suite *GraphConnectorIntegrationSuite) TestMailFetch() { // TestSetTenantUsers verifies GraphConnector's ability to query
// the sites associated with the credentials
func (suite *GraphConnectorIntegrationSuite) TestSetTenantSites() {
newConnector := GraphConnector{
tenant: "test_tenant",
Sites: make(map[string]string, 0),
credentials: suite.connector.credentials,
}
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
var ( service, err := newConnector.createService(false)
t = suite.T() require.NoError(suite.T(), err)
userID = tester.M365UserID(t)
)
tests := []struct { newConnector.graphService = *service
suite.Equal(0, len(newConnector.Sites))
err = newConnector.setTenantSites(ctx)
assert.NoError(suite.T(), err)
// TODO: should be non-zero once implemented.
// suite.Greater(len(newConnector.Users), 0)
suite.Equal(0, len(newConnector.Sites))
}
func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() {
dest := tester.DefaultTestRestoreDestination()
table := []struct {
name string name string
scope selectors.ExchangeScope col []data.Collection
folderNames map[string]struct{} sel selectors.Selector
}{ }{
{ {
name: "Folder Iterative Check Mail", name: "ExchangeNil",
scope: selectors.NewExchangeBackup().MailFolders( col: nil,
[]string{userID}, sel: selectors.Selector{
[]string{exchange.DefaultMailFolder}, Service: selectors.ServiceExchange,
selectors.PrefixMatch(),
)[0],
folderNames: map[string]struct{}{
exchange.DefaultMailFolder: {},
}, },
}, },
{
name: "ExchangeEmpty",
col: []data.Collection{},
sel: selectors.Selector{
Service: selectors.ServiceExchange,
},
},
{
name: "OneDriveNil",
col: nil,
sel: selectors.Selector{
Service: selectors.ServiceOneDrive,
},
},
{
name: "OneDriveEmpty",
col: []data.Collection{},
sel: selectors.Selector{
Service: selectors.ServiceOneDrive,
},
},
// TODO: SharePoint
} }
gc := loadConnector(ctx, t) for _, test := range table {
for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
collections, err := gc.createCollections(ctx, test.scope) ctx, flush := tester.NewContext()
defer flush()
deets, err := suite.connector.RestoreDataCollections(ctx, test.sel, dest, test.col)
require.NoError(t, err) require.NoError(t, err)
assert.NotNil(t, deets)
for _, c := range collections { stats := suite.connector.AwaitStatus()
require.NotEmpty(t, c.FullPath().Folder()) assert.Zero(t, stats.ObjectCount)
folder := c.FullPath().Folder() assert.Zero(t, stats.FolderCount)
assert.Zero(t, stats.Successful)
if _, ok := test.folderNames[folder]; ok {
delete(test.folderNames, folder)
}
}
assert.Empty(t, test.folderNames)
}) })
} }
} }

View File

@ -0,0 +1,86 @@
package sharepoint
import (
"context"
"github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/support"
)
// FilterContainersAndFillCollections is a utility function
// that places the M365 object ids belonging to specific directories
// into a Collection. Items outside of those directories are omitted.
// @param collection is filled with during this function.
func FilterContainersAndFillCollections(
ctx context.Context,
qp graph.QueryParams,
collections map[string]*Collection,
statusUpdater support.StatusUpdater,
resolver graph.ContainerResolver,
) error {
return nil
}
// code previously within the function, moved here to make the linter happy
// var (
// category = qp.Scope.Category().PathType()
// collectionType = CategoryToOptionIdentifier(category)
// errs error
// )
// for _, c := range resolver.Items() {
// dirPath, ok := pathAndMatch(qp, category, c)
// if ok {
// // Create only those that match
// service, err := createService(qp.Credentials, qp.FailFast)
// if err != nil {
// errs = support.WrapAndAppend(
// qp.User+" FilterContainerAndFillCollection",
// err,
// errs)
// if qp.FailFast {
// return errs
// }
// }
// edc := NewCollection(
// qp.User,
// dirPath,
// collectionType,
// service,
// statusUpdater,
// )
// collections[*c.GetId()] = &edc
// }
// }
// for directoryID, col := range collections {
// fetchFunc, err := getFetchIDFunc(category)
// if err != nil {
// errs = support.WrapAndAppend(
// qp.User,
// err,
// errs)
// if qp.FailFast {
// return errs
// }
// continue
// }
// jobs, err := fetchFunc(ctx, col.service, qp.User, directoryID)
// if err != nil {
// errs = support.WrapAndAppend(
// qp.User,
// err,
// errs,
// )
// }
// col.jobs = append(col.jobs, jobs...)
// }
// return errs

View File

@ -21,6 +21,7 @@ const (
// M365 config // M365 config
TestCfgAzureTenantID = "azure_tenantid" TestCfgAzureTenantID = "azure_tenantid"
TestCfgSiteID = "m365siteid"
TestCfgUserID = "m365userid" TestCfgUserID = "m365userid"
TestCfgSecondaryUserID = "secondarym365userid" TestCfgSecondaryUserID = "secondarym365userid"
TestCfgLoadTestUserID = "loadtestm365userid" TestCfgLoadTestUserID = "loadtestm365userid"
@ -30,6 +31,7 @@ const (
// test specific env vars // test specific env vars
const ( const (
EnvCorsoM365TestSiteID = "CORSO_M365_TEST_SITE_ID"
EnvCorsoM365TestUserID = "CORSO_M365_TEST_USER_ID" EnvCorsoM365TestUserID = "CORSO_M365_TEST_USER_ID"
EnvCorsoSecondaryM365TestUserID = "CORSO_SECONDARY_M365_TEST_USER_ID" EnvCorsoSecondaryM365TestUserID = "CORSO_SECONDARY_M365_TEST_USER_ID"
EnvCorsoM365LoadTestUserID = "CORSO_M365_LOAD_TEST_USER_ID" EnvCorsoM365LoadTestUserID = "CORSO_M365_LOAD_TEST_USER_ID"
@ -137,6 +139,13 @@ func readTestConfig() (map[string]string, error) {
vpr.GetString(TestCfgLoadTestOrgUsers), vpr.GetString(TestCfgLoadTestOrgUsers),
"lidiah@8qzvrj.onmicrosoft.com,lynner@8qzvrj.onmicrosoft.com", "lidiah@8qzvrj.onmicrosoft.com,lynner@8qzvrj.onmicrosoft.com",
) )
fallbackTo(
testEnv,
TestCfgSiteID,
os.Getenv(EnvCorsoM365TestSiteID),
vpr.GetString(TestCfgSiteID),
"8qzvrj.sharepoint.com,1c9ef309-f47c-4e69-832b-a83edd69fa7f,c57f6e0e-3e4b-472c-b528-b56a2ccd0507",
)
testEnv[EnvCorsoTestConfigFilePath] = os.Getenv(EnvCorsoTestConfigFilePath) testEnv[EnvCorsoTestConfigFilePath] = os.Getenv(EnvCorsoTestConfigFilePath)
testConfig = testEnv testConfig = testEnv

View File

@ -16,7 +16,8 @@ const (
CorsoCLIRepoTests = "CORSO_COMMAND_LINE_REPO_TESTS" CorsoCLIRepoTests = "CORSO_COMMAND_LINE_REPO_TESTS"
CorsoCLIRestoreTests = "CORSO_COMMAND_LINE_RESTORE_TESTS" CorsoCLIRestoreTests = "CORSO_COMMAND_LINE_RESTORE_TESTS"
CorsoCLITests = "CORSO_COMMAND_LINE_TESTS" CorsoCLITests = "CORSO_COMMAND_LINE_TESTS"
CorsoConnectorCreateCollectionTests = "CORSO_CONNECTOR_CREATE_COLLECTION_TESTS" CorsoConnectorCreateExchangeCollectionTests = "CORSO_CONNECTOR_CREATE_EXCHANGE_COLLECTION_TESTS"
CorsoConnectorCreateSharePointCollectionTests = "CORSO_CONNECTOR_CREATE_SHAREPOINT_COLLECTION_TESTS"
CorsoConnectorDataCollectionTests = "CORSO_CONNECTOR_DATA_COLLECTION_TESTS" CorsoConnectorDataCollectionTests = "CORSO_CONNECTOR_DATA_COLLECTION_TESTS"
CorsoGraphConnectorTests = "CORSO_GRAPH_CONNECTOR_TESTS" CorsoGraphConnectorTests = "CORSO_GRAPH_CONNECTOR_TESTS"
CorsoGraphConnectorExchangeTests = "CORSO_GRAPH_CONNECTOR_EXCHANGE_TESTS" CorsoGraphConnectorExchangeTests = "CORSO_GRAPH_CONNECTOR_EXCHANGE_TESTS"

View File

@ -57,3 +57,14 @@ func LoadTestM365OrgUsers(t *testing.T) []string {
return strings.Split(users, ",") return strings.Split(users, ",")
} }
// M365SiteID returns a siteID string representing the m365SiteID described
// by either the env var CORSO_M365_TEST_SITE_ID, the corso_test.toml config
// file or the default value (in that order of priority). The default is a
// last-attempt fallback that will only work on alcion's testing org.
func M365SiteID(t *testing.T) string {
cfg, err := readTestConfig()
require.NoError(t, err, "retrieving m365 site id from test configuration")
return cfg[TestCfgSiteID]
}