transition api methods to interfaces (#2010)

## Description

replaces the new api client methods with interfaces, to prepare for testing funcions with mocks instead of integration.

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

- [x]  No 

## Type of change

- [x] 🤖 Test

## Issue(s)

* #1967

## Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-01-05 14:03:35 -07:00 committed by GitHub
parent 2e92d10777
commit 70d5a5ab56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 213 additions and 130 deletions

View File

@ -87,6 +87,18 @@ func newService(creds account.M365Config) (*graph.Service, error) {
return graph.NewService(adapter), nil return graph.NewService(adapter), nil
} }
func (c Client) Contacts() Contacts {
return Contacts{c}
}
func (c Client) Events() Events {
return Events{c}
}
func (c Client) Mail() Mail {
return Mail{c}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// helper funcs // helper funcs
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -174,11 +174,11 @@ func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() {
}{ }{
{ {
name: "GraphQuery: Get All ContactFolders", name: "GraphQuery: Get All ContactFolders",
function: c.GetAllContactFolderNamesForUser, function: c.Contacts().GetAllContactFolderNamesForUser,
}, },
{ {
name: "GraphQuery: Get All Calendars for User", name: "GraphQuery: Get All Calendars for User",
function: c.GetAllCalendarNamesForUser, function: c.Events().GetAllCalendarNamesForUser,
}, },
} }

View File

@ -13,9 +13,21 @@ import (
"github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/connector/support"
) )
// ---------------------------------------------------------------------------
// controller
// ---------------------------------------------------------------------------
type Contacts struct {
Client
}
// ---------------------------------------------------------------------------
// methods
// ---------------------------------------------------------------------------
// CreateContactFolder makes a contact folder with the displayName of folderName. // CreateContactFolder makes a contact folder with the displayName of folderName.
// If successful, returns the created folder object. // If successful, returns the created folder object.
func (c Client) CreateContactFolder( func (c Contacts) CreateContactFolder(
ctx context.Context, ctx context.Context,
user, folderName string, user, folderName string,
) (models.ContactFolderable, error) { ) (models.ContactFolderable, error) {
@ -28,7 +40,7 @@ func (c Client) CreateContactFolder(
// DeleteContactFolder deletes the ContactFolder associated with the M365 ID if permissions are valid. // DeleteContactFolder deletes the ContactFolder associated with the M365 ID if permissions are valid.
// Errors returned if the function call was not successful. // Errors returned if the function call was not successful.
func (c Client) DeleteContactFolder( func (c Contacts) DeleteContactFolder(
ctx context.Context, ctx context.Context,
user, folderID string, user, folderID string,
) error { ) error {
@ -36,7 +48,7 @@ func (c Client) DeleteContactFolder(
} }
// RetrieveContactDataForUser is a GraphRetrievalFun that returns all associated fields. // RetrieveContactDataForUser is a GraphRetrievalFun that returns all associated fields.
func (c Client) RetrieveContactDataForUser( func (c Contacts) RetrieveContactDataForUser(
ctx context.Context, ctx context.Context,
user, m365ID string, user, m365ID string,
) (serialization.Parsable, error) { ) (serialization.Parsable, error) {
@ -46,7 +58,7 @@ func (c Client) RetrieveContactDataForUser(
// GetAllContactFolderNamesForUser is a GraphQuery function for getting // GetAllContactFolderNamesForUser is a GraphQuery function for getting
// ContactFolderId and display names for contacts. All other information is omitted. // ContactFolderId and display names for contacts. All other information is omitted.
// Does not return the default Contact Folder // Does not return the default Contact Folder
func (c Client) GetAllContactFolderNamesForUser( func (c Contacts) GetAllContactFolderNamesForUser(
ctx context.Context, ctx context.Context,
user string, user string,
) (serialization.Parsable, error) { ) (serialization.Parsable, error) {
@ -58,16 +70,13 @@ func (c Client) GetAllContactFolderNamesForUser(
return c.stable.Client().UsersById(user).ContactFolders().Get(ctx, options) return c.stable.Client().UsersById(user).ContactFolders().Get(ctx, options)
} }
func (c Client) GetContactFolderByID( func (c Contacts) GetContainerByID(
ctx context.Context, ctx context.Context,
userID, dirID string, userID, dirID string,
optionalFields ...string, ) (graph.Container, error) {
) (models.ContactFolderable, error) { ofcf, err := optionsForContactFolderByID([]string{"displayName", "parentFolderId"})
fields := append([]string{"displayName", "parentFolderId"}, optionalFields...)
ofcf, err := optionsForContactFolderByID(fields)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "options for contact folder: %v", fields) return nil, errors.Wrap(err, "options for contact folder")
} }
return c.stable.Client(). return c.stable.Client().
@ -76,13 +85,13 @@ func (c Client) GetContactFolderByID(
Get(ctx, ofcf) Get(ctx, ofcf)
} }
// EnumerateContactsFolders iterates through all of the users current // EnumerateContainers iterates through all of the users current
// contacts folders, converting each to a graph.CacheFolder, and calling // contacts folders, converting each to a graph.CacheFolder, and calling
// fn(cf) on each one. If fn(cf) errors, the error is aggregated // fn(cf) on each one. If fn(cf) errors, the error is aggregated
// into a multierror that gets returned to the caller. // into a multierror that gets returned to the caller.
// Folder hierarchy is represented in its current state, and does // Folder hierarchy is represented in its current state, and does
// not contain historical data. // not contain historical data.
func (c Client) EnumerateContactsFolders( func (c Contacts) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, baseDirID string, userID, baseDirID string,
fn func(graph.CacheFolder) error, fn func(graph.CacheFolder) error,
@ -138,9 +147,7 @@ func (c Client) EnumerateContactsFolders(
return errs.ErrorOrNil() return errs.ErrorOrNil()
} }
// FetchContactIDsFromDirectory function that returns a list of all the m365IDs of the contacts func (c Contacts) GetAddedAndRemovedItemIDs(
// of the targeted directory
func (c Client) FetchContactIDsFromDirectory(
ctx context.Context, ctx context.Context,
user, directoryID, oldDelta string, user, directoryID, oldDelta string,
) ([]string, []string, DeltaUpdate, error) { ) ([]string, []string, DeltaUpdate, error) {

View File

@ -14,9 +14,21 @@ import (
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
// ---------------------------------------------------------------------------
// controller
// ---------------------------------------------------------------------------
type Events struct {
Client
}
// ---------------------------------------------------------------------------
// methods
// ---------------------------------------------------------------------------
// CreateCalendar makes an event Calendar with the name in the user's M365 exchange account // CreateCalendar makes an event Calendar with the name in the user's M365 exchange account
// Reference: https://docs.microsoft.com/en-us/graph/api/user-post-calendars?view=graph-rest-1.0&tabs=go // Reference: https://docs.microsoft.com/en-us/graph/api/user-post-calendars?view=graph-rest-1.0&tabs=go
func (c Client) CreateCalendar( func (c Events) CreateCalendar(
ctx context.Context, ctx context.Context,
user, calendarName string, user, calendarName string,
) (models.Calendarable, error) { ) (models.Calendarable, error) {
@ -28,7 +40,7 @@ func (c Client) CreateCalendar(
// DeleteCalendar removes calendar from user's M365 account // DeleteCalendar removes calendar from user's M365 account
// Reference: https://docs.microsoft.com/en-us/graph/api/calendar-delete?view=graph-rest-1.0&tabs=go // Reference: https://docs.microsoft.com/en-us/graph/api/calendar-delete?view=graph-rest-1.0&tabs=go
func (c Client) DeleteCalendar( func (c Events) DeleteCalendar(
ctx context.Context, ctx context.Context,
user, calendarID string, user, calendarID string,
) error { ) error {
@ -36,7 +48,7 @@ func (c Client) DeleteCalendar(
} }
// RetrieveEventDataForUser is a GraphRetrievalFunc that returns event data. // RetrieveEventDataForUser is a GraphRetrievalFunc that returns event data.
func (c Client) RetrieveEventDataForUser( func (c Events) RetrieveEventDataForUser(
ctx context.Context, ctx context.Context,
user, m365ID string, user, m365ID string,
) (serialization.Parsable, error) { ) (serialization.Parsable, error) {
@ -55,15 +67,15 @@ func (c Client) GetAllCalendarNamesForUser(
return c.stable.Client().UsersById(user).Calendars().Get(ctx, options) return c.stable.Client().UsersById(user).Calendars().Get(ctx, options)
} }
// EnumerateCalendars iterates through all of the users current // EnumerateContainers iterates through all of the users current
// contacts folders, converting each to a graph.CacheFolder, and // calendars, converting each to a graph.CacheFolder, and
// calling fn(cf) on each one. If fn(cf) errors, the error is // calling fn(cf) on each one. If fn(cf) errors, the error is
// aggregated into a multierror that gets returned to the caller. // aggregated into a multierror that gets returned to the caller.
// Folder hierarchy is represented in its current state, and does // Folder hierarchy is represented in its current state, and does
// not contain historical data. // not contain historical data.
func (c Client) EnumerateCalendars( func (c Events) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID string, userID, baseDirID string,
fn func(graph.CacheFolder) error, fn func(graph.CacheFolder) error,
) error { ) error {
service, err := c.service() service, err := c.service()
@ -112,8 +124,7 @@ func (c Client) EnumerateCalendars(
return errs.ErrorOrNil() return errs.ErrorOrNil()
} }
// FetchEventIDsFromCalendar returns a list of all M365IDs of events of the targeted Calendar. func (c Events) GetAddedAndRemovedItemIDs(
func (c Client) FetchEventIDsFromCalendar(
ctx context.Context, ctx context.Context,
user, calendarID, oldDelta string, user, calendarID, oldDelta string,
) ([]string, []string, DeltaUpdate, error) { ) ([]string, []string, DeltaUpdate, error) {

View File

@ -13,9 +13,21 @@ import (
"github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/connector/support"
) )
// ---------------------------------------------------------------------------
// controller
// ---------------------------------------------------------------------------
type Mail struct {
Client
}
// ---------------------------------------------------------------------------
// methods
// ---------------------------------------------------------------------------
// CreateMailFolder makes a mail folder iff a folder of the same name does not exist // CreateMailFolder makes a mail folder iff a folder of the same name does not exist
// Reference: https://docs.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http // Reference: https://docs.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http
func (c Client) CreateMailFolder( func (c Mail) CreateMailFolder(
ctx context.Context, ctx context.Context,
user, folder string, user, folder string,
) (models.MailFolderable, error) { ) (models.MailFolderable, error) {
@ -27,7 +39,7 @@ func (c Client) CreateMailFolder(
return c.stable.Client().UsersById(user).MailFolders().Post(ctx, requestBody, nil) return c.stable.Client().UsersById(user).MailFolders().Post(ctx, requestBody, nil)
} }
func (c Client) CreateMailFolderWithParent( func (c Mail) CreateMailFolderWithParent(
ctx context.Context, ctx context.Context,
user, folder, parentID string, user, folder, parentID string,
) (models.MailFolderable, error) { ) (models.MailFolderable, error) {
@ -51,30 +63,47 @@ func (c Client) CreateMailFolderWithParent(
// DeleteMailFolder removes a mail folder with the corresponding M365 ID from the user's M365 Exchange account // DeleteMailFolder removes a mail folder with the corresponding M365 ID from the user's M365 Exchange account
// Reference: https://docs.microsoft.com/en-us/graph/api/mailfolder-delete?view=graph-rest-1.0&tabs=http // Reference: https://docs.microsoft.com/en-us/graph/api/mailfolder-delete?view=graph-rest-1.0&tabs=http
func (c Client) DeleteMailFolder( func (c Mail) DeleteMailFolder(
ctx context.Context, ctx context.Context,
user, folderID string, user, folderID string,
) error { ) error {
return c.stable.Client().UsersById(user).MailFoldersById(folderID).Delete(ctx, nil) return c.stable.Client().UsersById(user).MailFoldersById(folderID).Delete(ctx, nil)
} }
func (c Mail) GetContainerByID(
ctx context.Context,
userID, dirID string,
) (graph.Container, error) {
service, err := c.service()
if err != nil {
return nil, err
}
ofmf, err := optionsForMailFoldersItem([]string{"displayName", "parentFolderId"})
if err != nil {
return nil, errors.Wrap(err, "options for mail folder")
}
return service.Client().UsersById(userID).MailFoldersById(dirID).Get(ctx, ofmf)
}
// RetrieveMessageDataForUser is a GraphRetrievalFunc that returns message data. // RetrieveMessageDataForUser is a GraphRetrievalFunc that returns message data.
func (c Client) RetrieveMessageDataForUser( func (c Mail) RetrieveMessageDataForUser(
ctx context.Context, ctx context.Context,
user, m365ID string, user, m365ID string,
) (serialization.Parsable, error) { ) (serialization.Parsable, error) {
return c.stable.Client().UsersById(user).MessagesById(m365ID).Get(ctx, nil) return c.stable.Client().UsersById(user).MessagesById(m365ID).Get(ctx, nil)
} }
// EnumeratetMailFolders iterates through all of the users current // EnumerateContainers iterates through all of the users current
// mail folders, converting each to a graph.CacheFolder, and calling // mail folders, converting each to a graph.CacheFolder, and calling
// fn(cf) on each one. If fn(cf) errors, the error is aggregated // fn(cf) on each one. If fn(cf) errors, the error is aggregated
// into a multierror that gets returned to the caller. // into a multierror that gets returned to the caller.
// Folder hierarchy is represented in its current state, and does // Folder hierarchy is represented in its current state, and does
// not contain historical data. // not contain historical data.
func (c Client) EnumerateMailFolders( func (c Mail) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID string, userID, baseDirID string,
fn func(graph.CacheFolder) error, fn func(graph.CacheFolder) error,
) error { ) error {
service, err := c.service() service, err := c.service()
@ -116,22 +145,7 @@ func (c Client) EnumerateMailFolders(
return errs.ErrorOrNil() return errs.ErrorOrNil()
} }
func (c Client) GetMailFolderByID( func (c Mail) GetAddedAndRemovedItemIDs(
ctx context.Context,
userID, dirID string,
optionalFields ...string,
) (models.MailFolderable, error) {
ofmf, err := optionsForMailFoldersItem(optionalFields)
if err != nil {
return nil, errors.Wrapf(err, "options for mail folder: %v", optionalFields)
}
return c.stable.Client().UsersById(userID).MailFoldersById(dirID).Get(ctx, ofmf)
}
// FetchMessageIDsFromDirectory function that returns a list of all the m365IDs of the exchange.Mail
// of the targeted directory
func (c Client) FetchMessageIDsFromDirectory(
ctx context.Context, ctx context.Context,
user, directoryID, oldDelta string, user, directoryID, oldDelta string,
) ([]string, []string, DeltaUpdate, error) { ) ([]string, []string, DeltaUpdate, error) {

View File

@ -5,7 +5,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/connector/exchange/api"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
@ -15,7 +14,8 @@ var _ graph.ContainerResolver = &contactFolderCache{}
type contactFolderCache struct { type contactFolderCache struct {
*containerResolver *containerResolver
ac api.Client enumer containersEnumerator
getter containerGetter
userID string userID string
} }
@ -24,7 +24,7 @@ func (cfc *contactFolderCache) populateContactRoot(
directoryID string, directoryID string,
baseContainerPath []string, baseContainerPath []string,
) error { ) error {
f, err := cfc.ac.GetContactFolderByID(ctx, cfc.userID, directoryID) f, err := cfc.getter.GetContainerByID(ctx, cfc.userID, directoryID)
if err != nil { if err != nil {
return errors.Wrapf( return errors.Wrapf(
err, err,
@ -53,7 +53,7 @@ func (cfc *contactFolderCache) Populate(
return err return err
} }
err := cfc.ac.EnumerateContactsFolders(ctx, cfc.userID, baseID, cfc.addFolder) err := cfc.enumer.EnumerateContainers(ctx, cfc.userID, baseID, cfc.addFolder)
if err != nil { if err != nil {
return err return err
} }

View File

@ -10,6 +10,29 @@ import (
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
// ---------------------------------------------------------------------------
// common interfaces
// ---------------------------------------------------------------------------
type containerGetter interface {
GetContainerByID(
ctx context.Context,
userID, dirID string,
) (graph.Container, error)
}
type containersEnumerator interface {
EnumerateContainers(
ctx context.Context,
userID, baseDirID string,
fn func(graph.CacheFolder) error,
) error
}
// ---------------------------------------------------------------------------
// controller
// ---------------------------------------------------------------------------
// Exchange has a limit of 300 for folder depth. OneDrive has a limit on path // Exchange has a limit of 300 for folder depth. OneDrive has a limit on path
// length of 400 characters (including separators) which would be roughly 200 // length of 400 characters (including separators) which would be roughly 200
// folders if each folder is only a single character. // folders if each folder is only a single character.

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/connector/exchange/api"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"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"
@ -205,12 +206,25 @@ func DataCollections(
return collections, errs return collections, errs
} }
func getterByType(ac api.Client, category path.CategoryType) (addedAndRemovedItemIDsGetter, error) {
switch category {
case path.EmailCategory:
return ac.Mail(), nil
case path.EventsCategory:
return ac.Events(), nil
case path.ContactsCategory:
return ac.Contacts(), nil
default:
return nil, fmt.Errorf("category %s not supported by getFetchIDFunc", category)
}
}
// createCollections - utility function that retrieves M365 // createCollections - 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 retrieved. // determines the type of collections that are retrieved.
func createCollections( func createCollections(
ctx context.Context, ctx context.Context,
acct account.M365Config, creds account.M365Config,
user string, user string,
scope selectors.ExchangeScope, scope selectors.ExchangeScope,
dps DeltaPaths, dps DeltaPaths,
@ -220,15 +234,22 @@ func createCollections(
var ( var (
errs *multierror.Error errs *multierror.Error
allCollections = make([]data.Collection, 0) allCollections = make([]data.Collection, 0)
ac = api.Client{Credentials: creds}
category = scope.Category().PathType()
) )
getter, err := getterByType(ac, category)
if err != nil {
return nil, err
}
// Create collection of ExchangeDataCollection // Create collection of ExchangeDataCollection
collections := make(map[string]data.Collection) collections := make(map[string]data.Collection)
qp := graph.QueryParams{ qp := graph.QueryParams{
Category: scope.Category().PathType(), Category: category,
ResourceOwner: user, ResourceOwner: user,
Credentials: acct, Credentials: creds,
} }
foldersComplete, closer := observe.MessageWithCompletion(fmt.Sprintf("∙ %s - %s:", qp.Category, user)) foldersComplete, closer := observe.MessageWithCompletion(fmt.Sprintf("∙ %s - %s:", qp.Category, user))
@ -243,6 +264,7 @@ func createCollections(
err = filterContainersAndFillCollections( err = filterContainersAndFillCollections(
ctx, ctx,
qp, qp,
getter,
collections, collections,
su, su,
resolver, resolver,

View File

@ -5,7 +5,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/connector/exchange/api"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -14,7 +13,7 @@ var _ graph.ContainerResolver = &eventCalendarCache{}
type eventCalendarCache struct { type eventCalendarCache struct {
*containerResolver *containerResolver
ac api.Client enumer containersEnumerator
userID string userID string
} }
@ -30,7 +29,7 @@ func (ecc *eventCalendarCache) Populate(
ecc.containerResolver = newContainerResolver() ecc.containerResolver = newContainerResolver()
} }
err := ecc.ac.EnumerateCalendars(ctx, ecc.userID, ecc.addFolder) err := ecc.enumer.EnumerateContainers(ctx, ecc.userID, "", ecc.addFolder)
if err != nil { if err != nil {
return err return err
} }

View File

@ -142,11 +142,11 @@ func (col *Collection) Items() <-chan data.Stream {
func GetQueryAndSerializeFunc(ac api.Client, category path.CategoryType) (api.GraphRetrievalFunc, GraphSerializeFunc) { func GetQueryAndSerializeFunc(ac api.Client, category path.CategoryType) (api.GraphRetrievalFunc, GraphSerializeFunc) {
switch category { switch category {
case path.ContactsCategory: case path.ContactsCategory:
return ac.RetrieveContactDataForUser, serializeAndStreamContact return ac.Contacts().RetrieveContactDataForUser, serializeAndStreamContact
case path.EventsCategory: case path.EventsCategory:
return ac.RetrieveEventDataForUser, serializeAndStreamEvent return ac.Events().RetrieveEventDataForUser, serializeAndStreamEvent
case path.EmailCategory: case path.EmailCategory:
return ac.RetrieveMessageDataForUser, serializeAndStreamMessage return ac.Mail().RetrieveMessageDataForUser, serializeAndStreamMessage
// Unsupported options returns nil, nil // Unsupported options returns nil, nil
default: default:
return nil, nil return nil, nil

View File

@ -50,14 +50,15 @@ func (suite *CacheResolverSuite) TestPopulate() {
eventFunc := func(t *testing.T) graph.ContainerResolver { eventFunc := func(t *testing.T) graph.ContainerResolver {
return &eventCalendarCache{ return &eventCalendarCache{
userID: tester.M365UserID(t), userID: tester.M365UserID(t),
ac: ac, enumer: ac.Events(),
} }
} }
contactFunc := func(t *testing.T) graph.ContainerResolver { contactFunc := func(t *testing.T) graph.ContainerResolver {
return &contactFolderCache{ return &contactFolderCache{
userID: tester.M365UserID(t), userID: tester.M365UserID(t),
ac: ac, enumer: ac.Contacts(),
getter: ac.Contacts(),
} }
} }

View File

@ -5,7 +5,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/connector/exchange/api"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
@ -18,7 +17,8 @@ var _ graph.ContainerResolver = &mailFolderCache{}
// nameLookup map: Key: DisplayName Value: ID // nameLookup map: Key: DisplayName Value: ID
type mailFolderCache struct { type mailFolderCache struct {
*containerResolver *containerResolver
ac api.Client enumer containersEnumerator
getter containerGetter
userID string userID string
} }
@ -33,7 +33,7 @@ func (mc *mailFolderCache) populateMailRoot(
for _, fldr := range []string{rootFolderAlias, DefaultMailFolder} { for _, fldr := range []string{rootFolderAlias, DefaultMailFolder} {
var directory string var directory string
f, err := mc.ac.GetMailFolderByID(ctx, mc.userID, fldr, "displayName", "parentFolderId") f, err := mc.getter.GetContainerByID(ctx, mc.userID, fldr)
if err != nil { if err != nil {
return errors.Wrap(err, "fetching root folder"+support.ConnectorStackErrorTrace(err)) return errors.Wrap(err, "fetching root folder"+support.ConnectorStackErrorTrace(err))
} }
@ -65,7 +65,7 @@ func (mc *mailFolderCache) Populate(
return err return err
} }
err := mc.ac.EnumerateMailFolders(ctx, mc.userID, mc.addFolder) err := mc.enumer.EnumerateContainers(ctx, mc.userID, "", mc.addFolder)
if err != nil { if err != nil {
return err return err
} }

View File

@ -84,9 +84,12 @@ func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() {
ac, err := api.NewClient(suite.credentials) ac, err := api.NewClient(suite.credentials)
require.NoError(t, err) require.NoError(t, err)
acm := ac.Mail()
mfc := mailFolderCache{ mfc := mailFolderCache{
userID: userID, userID: userID,
ac: ac, enumer: acm,
getter: acm,
} }
require.NoError(t, mfc.Populate(ctx, test.root, test.path...)) require.NoError(t, mfc.Populate(ctx, test.root, test.path...))

View File

@ -69,14 +69,14 @@ func (suite *ExchangeRestoreSuite) TestRestoreContact() {
folderName = "TestRestoreContact: " + common.FormatSimpleDateTime(now) folderName = "TestRestoreContact: " + common.FormatSimpleDateTime(now)
) )
aFolder, err := suite.ac.CreateContactFolder(ctx, userID, folderName) aFolder, err := suite.ac.Contacts().CreateContactFolder(ctx, userID, folderName)
require.NoError(t, err) require.NoError(t, err)
folderID := *aFolder.GetId() folderID := *aFolder.GetId()
defer func() { defer func() {
// Remove the folder containing contact prior to exiting test // Remove the folder containing contact prior to exiting test
err = suite.ac.DeleteContactFolder(ctx, userID, folderID) err = suite.ac.Contacts().DeleteContactFolder(ctx, userID, folderID)
assert.NoError(t, err) assert.NoError(t, err)
}() }()
@ -103,14 +103,14 @@ func (suite *ExchangeRestoreSuite) TestRestoreEvent() {
name = "TestRestoreEvent: " + common.FormatSimpleDateTime(time.Now()) name = "TestRestoreEvent: " + common.FormatSimpleDateTime(time.Now())
) )
calendar, err := suite.ac.CreateCalendar(ctx, userID, name) calendar, err := suite.ac.Events().CreateCalendar(ctx, userID, name)
require.NoError(t, err) require.NoError(t, err)
calendarID := *calendar.GetId() calendarID := *calendar.GetId()
defer func() { defer func() {
// Removes calendar containing events created during the test // Removes calendar containing events created during the test
err = suite.ac.DeleteCalendar(ctx, userID, calendarID) err = suite.ac.Events().DeleteCalendar(ctx, userID, calendarID)
assert.NoError(t, err) assert.NoError(t, err)
}() }()
@ -146,10 +146,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
name: "Test Mail", name: "Test Mail",
bytes: mockconnector.GetMockMessageBytes("Restore Exchange Object"), bytes: mockconnector.GetMockMessageBytes("Restore Exchange Object"),
category: path.EmailCategory, category: path.EmailCategory,
cleanupFunc: suite.ac.DeleteMailFolder, cleanupFunc: suite.ac.Mail().DeleteMailFolder,
destination: func(t *testing.T, ctx context.Context) string { destination: func(t *testing.T, ctx context.Context) string {
folderName := "TestRestoreMailObject: " + common.FormatSimpleDateTime(now) folderName := "TestRestoreMailObject: " + common.FormatSimpleDateTime(now)
folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName)
require.NoError(t, err) require.NoError(t, err)
return *folder.GetId() return *folder.GetId()
@ -159,10 +159,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
name: "Test Mail: One Direct Attachment", name: "Test Mail: One Direct Attachment",
bytes: mockconnector.GetMockMessageWithDirectAttachment("Restore 1 Attachment"), bytes: mockconnector.GetMockMessageWithDirectAttachment("Restore 1 Attachment"),
category: path.EmailCategory, category: path.EmailCategory,
cleanupFunc: suite.ac.DeleteMailFolder, cleanupFunc: suite.ac.Mail().DeleteMailFolder,
destination: func(t *testing.T, ctx context.Context) string { destination: func(t *testing.T, ctx context.Context) string {
folderName := "TestRestoreMailwithAttachment: " + common.FormatSimpleDateTime(now) folderName := "TestRestoreMailwithAttachment: " + common.FormatSimpleDateTime(now)
folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName)
require.NoError(t, err) require.NoError(t, err)
return *folder.GetId() return *folder.GetId()
@ -172,10 +172,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
name: "Test Mail: One Large Attachment", name: "Test Mail: One Large Attachment",
bytes: mockconnector.GetMockMessageWithLargeAttachment("Restore Large Attachment"), bytes: mockconnector.GetMockMessageWithLargeAttachment("Restore Large Attachment"),
category: path.EmailCategory, category: path.EmailCategory,
cleanupFunc: suite.ac.DeleteMailFolder, cleanupFunc: suite.ac.Mail().DeleteMailFolder,
destination: func(t *testing.T, ctx context.Context) string { destination: func(t *testing.T, ctx context.Context) string {
folderName := "TestRestoreMailwithLargeAttachment: " + common.FormatSimpleDateTime(now) folderName := "TestRestoreMailwithLargeAttachment: " + common.FormatSimpleDateTime(now)
folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName)
require.NoError(t, err) require.NoError(t, err)
return *folder.GetId() return *folder.GetId()
@ -185,10 +185,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
name: "Test Mail: Two Attachments", name: "Test Mail: Two Attachments",
bytes: mockconnector.GetMockMessageWithTwoAttachments("Restore 2 Attachments"), bytes: mockconnector.GetMockMessageWithTwoAttachments("Restore 2 Attachments"),
category: path.EmailCategory, category: path.EmailCategory,
cleanupFunc: suite.ac.DeleteMailFolder, cleanupFunc: suite.ac.Mail().DeleteMailFolder,
destination: func(t *testing.T, ctx context.Context) string { destination: func(t *testing.T, ctx context.Context) string {
folderName := "TestRestoreMailwithAttachments: " + common.FormatSimpleDateTime(now) folderName := "TestRestoreMailwithAttachments: " + common.FormatSimpleDateTime(now)
folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName)
require.NoError(t, err) require.NoError(t, err)
return *folder.GetId() return *folder.GetId()
@ -198,10 +198,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
name: "Test Mail: Reference(OneDrive) Attachment", name: "Test Mail: Reference(OneDrive) Attachment",
bytes: mockconnector.GetMessageWithOneDriveAttachment("Restore Reference(OneDrive) Attachment"), bytes: mockconnector.GetMessageWithOneDriveAttachment("Restore Reference(OneDrive) Attachment"),
category: path.EmailCategory, category: path.EmailCategory,
cleanupFunc: suite.ac.DeleteMailFolder, cleanupFunc: suite.ac.Mail().DeleteMailFolder,
destination: func(t *testing.T, ctx context.Context) string { destination: func(t *testing.T, ctx context.Context) string {
folderName := "TestRestoreMailwithReferenceAttachment: " + common.FormatSimpleDateTime(now) folderName := "TestRestoreMailwithReferenceAttachment: " + common.FormatSimpleDateTime(now)
folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName)
require.NoError(t, err) require.NoError(t, err)
return *folder.GetId() return *folder.GetId()
@ -212,10 +212,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
name: "Test Contact", name: "Test Contact",
bytes: mockconnector.GetMockContactBytes("Test_Omega"), bytes: mockconnector.GetMockContactBytes("Test_Omega"),
category: path.ContactsCategory, category: path.ContactsCategory,
cleanupFunc: suite.ac.DeleteContactFolder, cleanupFunc: suite.ac.Contacts().DeleteContactFolder,
destination: func(t *testing.T, ctx context.Context) string { destination: func(t *testing.T, ctx context.Context) string {
folderName := "TestRestoreContactObject: " + common.FormatSimpleDateTime(now) folderName := "TestRestoreContactObject: " + common.FormatSimpleDateTime(now)
folder, err := suite.ac.CreateContactFolder(ctx, userID, folderName) folder, err := suite.ac.Contacts().CreateContactFolder(ctx, userID, folderName)
require.NoError(t, err) require.NoError(t, err)
return *folder.GetId() return *folder.GetId()
@ -225,10 +225,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
name: "Test Events", name: "Test Events",
bytes: mockconnector.GetDefaultMockEventBytes("Restored Event Object"), bytes: mockconnector.GetDefaultMockEventBytes("Restored Event Object"),
category: path.EventsCategory, category: path.EventsCategory,
cleanupFunc: suite.ac.DeleteCalendar, cleanupFunc: suite.ac.Events().DeleteCalendar,
destination: func(t *testing.T, ctx context.Context) string { destination: func(t *testing.T, ctx context.Context) string {
calendarName := "TestRestoreEventObject: " + common.FormatSimpleDateTime(now) calendarName := "TestRestoreEventObject: " + common.FormatSimpleDateTime(now)
calendar, err := suite.ac.CreateCalendar(ctx, userID, calendarName) calendar, err := suite.ac.Events().CreateCalendar(ctx, userID, calendarName)
require.NoError(t, err) require.NoError(t, err)
return *calendar.GetId() return *calendar.GetId()
@ -238,10 +238,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
name: "Test Event with Attachment", name: "Test Event with Attachment",
bytes: mockconnector.GetMockEventWithAttachment("Restored Event Attachment"), bytes: mockconnector.GetMockEventWithAttachment("Restored Event Attachment"),
category: path.EventsCategory, category: path.EventsCategory,
cleanupFunc: suite.ac.DeleteCalendar, cleanupFunc: suite.ac.Events().DeleteCalendar,
destination: func(t *testing.T, ctx context.Context) string { destination: func(t *testing.T, ctx context.Context) string {
calendarName := "TestRestoreEventObject_" + common.FormatSimpleDateTime(now) calendarName := "TestRestoreEventObject_" + common.FormatSimpleDateTime(now)
calendar, err := suite.ac.CreateCalendar(ctx, userID, calendarName) calendar, err := suite.ac.Events().CreateCalendar(ctx, userID, calendarName)
require.NoError(t, err) require.NoError(t, err)
return *calendar.GetId() return *calendar.GetId()

View File

@ -48,23 +48,27 @@ func PopulateExchangeContainerResolver(
switch qp.Category { switch qp.Category {
case path.EmailCategory: case path.EmailCategory:
acm := ac.Mail()
res = &mailFolderCache{ res = &mailFolderCache{
userID: qp.ResourceOwner, userID: qp.ResourceOwner,
ac: ac, getter: acm,
enumer: acm,
} }
cacheRoot = rootFolderAlias cacheRoot = rootFolderAlias
case path.ContactsCategory: case path.ContactsCategory:
acc := ac.Contacts()
res = &contactFolderCache{ res = &contactFolderCache{
userID: qp.ResourceOwner, userID: qp.ResourceOwner,
ac: ac, getter: acc,
enumer: acc,
} }
cacheRoot = DefaultContactFolder cacheRoot = DefaultContactFolder
case path.EventsCategory: case path.EventsCategory:
res = &eventCalendarCache{ res = &eventCalendarCache{
userID: qp.ResourceOwner, userID: qp.ResourceOwner,
ac: ac, enumer: ac.Events(),
} }
cacheRoot = DefaultCalendar cacheRoot = DefaultCalendar

View File

@ -2,7 +2,6 @@ package exchange
import ( import (
"context" "context"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -16,6 +15,13 @@ import (
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
) )
type addedAndRemovedItemIDsGetter interface {
GetAddedAndRemovedItemIDs(
ctx context.Context,
user, containerID, oldDeltaToken string,
) ([]string, []string, api.DeltaUpdate, error)
}
// filterContainersAndFillCollections is a utility function // filterContainersAndFillCollections is a utility function
// that places the M365 object ids belonging to specific directories // that places the M365 object ids belonging to specific directories
// into a Collection. Messages outside of those directories are omitted. // into a Collection. Messages outside of those directories are omitted.
@ -24,6 +30,7 @@ import (
func filterContainersAndFillCollections( func filterContainersAndFillCollections(
ctx context.Context, ctx context.Context,
qp graph.QueryParams, qp graph.QueryParams,
getter addedAndRemovedItemIDsGetter,
collections map[string]data.Collection, collections map[string]data.Collection,
statusUpdater support.StatusUpdater, statusUpdater support.StatusUpdater,
resolver graph.ContainerResolver, resolver graph.ContainerResolver,
@ -41,17 +48,14 @@ func filterContainersAndFillCollections(
tombstones = makeTombstones(dps) tombstones = makeTombstones(dps)
) )
// TODO(rkeepers): pass in the api client instead of generating it here. // TODO(rkeepers): this should be passed in from the caller, probably
// as an interface that satisfies the NewCollection requirements.
// But this will work for the short term.
ac, err := api.NewClient(qp.Credentials) ac, err := api.NewClient(qp.Credentials)
if err != nil { if err != nil {
return err return err
} }
getJobs, err := getFetchIDFunc(ac, qp.Category)
if err != nil {
return support.WrapAndAppend(qp.ResourceOwner, err, errs)
}
for _, c := range resolver.Items() { for _, c := range resolver.Items() {
if ctrlOpts.FailFast && errs != nil { if ctrlOpts.FailFast && errs != nil {
return errs return errs
@ -89,7 +93,7 @@ func filterContainersAndFillCollections(
} }
} }
added, removed, newDelta, err := getJobs(ctx, qp.ResourceOwner, cID, prevDelta) added, removed, newDelta, err := getter.GetAddedAndRemovedItemIDs(ctx, qp.ResourceOwner, cID, prevDelta)
if err != nil { if err != nil {
if graph.IsErrDeletedInFlight(err) == nil { if graph.IsErrDeletedInFlight(err) == nil {
errs = support.WrapAndAppend(qp.ResourceOwner, err, errs) errs = support.WrapAndAppend(qp.ResourceOwner, err, errs)
@ -217,24 +221,3 @@ func pathFromPrevString(ps string) (path.Path, error) {
return p, nil return p, nil
} }
// FetchIDFunc collection of helper functions which return a list of all item
// IDs in the given container and a delta token for future requests if the
// container supports fetching delta records.
type FetchIDFunc func(
ctx context.Context,
user, containerID, oldDeltaToken string,
) ([]string, []string, api.DeltaUpdate, error)
func getFetchIDFunc(ac api.Client, category path.CategoryType) (FetchIDFunc, error) {
switch category {
case path.EmailCategory:
return ac.FetchMessageIDsFromDirectory, nil
case path.EventsCategory:
return ac.FetchEventIDsFromCalendar, nil
case path.ContactsCategory:
return ac.FetchContactIDsFromDirectory, nil
default:
return nil, fmt.Errorf("category %s not supported by getFetchIDFunc", category)
}
}

View File

@ -455,9 +455,11 @@ func CreateContainerDestinaion(
switch category { switch category {
case path.EmailCategory: case path.EmailCategory:
if directoryCache == nil { if directoryCache == nil {
acm := ac.Mail()
mfc := &mailFolderCache{ mfc := &mailFolderCache{
userID: user, userID: user,
ac: ac, enumer: acm,
getter: acm,
} }
caches[category] = mfc caches[category] = mfc
@ -475,9 +477,11 @@ func CreateContainerDestinaion(
case path.ContactsCategory: case path.ContactsCategory:
if directoryCache == nil { if directoryCache == nil {
acc := ac.Contacts()
cfc := &contactFolderCache{ cfc := &contactFolderCache{
userID: user, userID: user,
ac: ac, enumer: acc,
getter: acc,
} }
caches[category] = cfc caches[category] = cfc
newCache = true newCache = true
@ -496,7 +500,7 @@ func CreateContainerDestinaion(
if directoryCache == nil { if directoryCache == nil {
ecc := &eventCalendarCache{ ecc := &eventCalendarCache{
userID: user, userID: user,
ac: ac, enumer: ac.Events(),
} }
caches[category] = ecc caches[category] = ecc
newCache = true newCache = true
@ -543,7 +547,7 @@ func establishMailRestoreLocation(
continue continue
} }
temp, err := ac.CreateMailFolderWithParent(ctx, user, folder, folderID) temp, err := ac.Mail().CreateMailFolderWithParent(ctx, user, folder, folderID)
if err != nil { if err != nil {
// Should only error if cache malfunctions or incorrect parameters // Should only error if cache malfunctions or incorrect parameters
return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err))
@ -590,7 +594,7 @@ func establishContactsRestoreLocation(
return cached, nil return cached, nil
} }
temp, err := ac.CreateContactFolder(ctx, user, folders[0]) temp, err := ac.Contacts().CreateContactFolder(ctx, user, folders[0])
if err != nil { if err != nil {
return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err))
} }
@ -623,7 +627,7 @@ func establishEventsRestoreLocation(
return cached, nil return cached, nil
} }
temp, err := ac.CreateCalendar(ctx, user, folders[0]) temp, err := ac.Events().CreateCalendar(ctx, user, folders[0])
if err != nil { if err != nil {
return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err))
} }

View File

@ -942,7 +942,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_exchangeIncrementals() {
switch category { switch category {
case path.EmailCategory: case path.EmailCategory:
ids, _, _, err := ac.FetchMessageIDsFromDirectory(ctx, suite.user, containerID, "") ids, _, _, err := ac.Mail().GetAddedAndRemovedItemIDs(ctx, suite.user, containerID, "")
require.NoError(t, err, "getting message ids") require.NoError(t, err, "getting message ids")
require.NotEmpty(t, ids, "message ids in folder") require.NotEmpty(t, ids, "message ids in folder")
@ -950,7 +950,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_exchangeIncrementals() {
require.NoError(t, err, "deleting email item: %s", support.ConnectorStackErrorTrace(err)) require.NoError(t, err, "deleting email item: %s", support.ConnectorStackErrorTrace(err))
case path.ContactsCategory: case path.ContactsCategory:
ids, _, _, err := ac.FetchContactIDsFromDirectory(ctx, suite.user, containerID, "") ids, _, _, err := ac.Contacts().GetAddedAndRemovedItemIDs(ctx, suite.user, containerID, "")
require.NoError(t, err, "getting contact ids") require.NoError(t, err, "getting contact ids")
require.NotEmpty(t, ids, "contact ids in folder") require.NotEmpty(t, ids, "contact ids in folder")