diff --git a/src/cmd/getM365/getItem.go b/src/cmd/getM365/getItem.go index 8f79a92d9..44da83567 100644 --- a/src/cmd/getM365/getItem.go +++ b/src/cmd/getM365/getItem.go @@ -77,12 +77,12 @@ func handleGetCommand(cmd *cobra.Command, args []string) error { return nil } - gc, err := getGC(ctx) + gc, creds, err := getGC(ctx) if err != nil { return err } - err = runDisplayM365JSON(ctx, gc.Service) + err = runDisplayM365JSON(ctx, gc.Service, creds) if err != nil { return Only(ctx, errors.Wrapf(err, "unable to create mock from M365: %s", m365ID)) } @@ -93,6 +93,7 @@ func handleGetCommand(cmd *cobra.Command, args []string) error { func runDisplayM365JSON( ctx context.Context, gs graph.Servicer, + creds account.M365Config, ) error { var ( get api.GraphRetrievalFunc @@ -100,9 +101,11 @@ func runDisplayM365JSON( cat = graph.StringToPathCategory(category) ) + ac := api.Client{Credentials: creds} + switch cat { case path.EmailCategory, path.EventsCategory, path.ContactsCategory: - get, serializeFunc = exchange.GetQueryAndSerializeFunc(cat) + get, serializeFunc = exchange.GetQueryAndSerializeFunc(ac, cat) default: return fmt.Errorf("unable to process category: %s", cat) } @@ -111,7 +114,7 @@ func runDisplayM365JSON( sw := kw.NewJsonSerializationWriter() - response, err := get(ctx, gs, user, m365ID) + response, err := get(ctx, user, m365ID) if err != nil { return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) } @@ -159,7 +162,7 @@ func runDisplayM365JSON( // Helpers //------------------------------------------------------------------------------- -func getGC(ctx context.Context) (*connector.GraphConnector, error) { +func getGC(ctx context.Context) (*connector.GraphConnector, account.M365Config, error) { // get account info m365Cfg := account.M365Config{ M365: credentials.GetM365(), @@ -168,13 +171,13 @@ func getGC(ctx context.Context) (*connector.GraphConnector, error) { acct, err := account.NewAccount(account.ProviderM365, m365Cfg) if err != nil { - return nil, Only(ctx, errors.Wrap(err, "finding m365 account details")) + return nil, m365Cfg, Only(ctx, errors.Wrap(err, "finding m365 account details")) } gc, err := connector.NewGraphConnector(ctx, acct, connector.Users) if err != nil { - return nil, Only(ctx, errors.Wrap(err, "connecting to graph API")) + return nil, m365Cfg, Only(ctx, errors.Wrap(err, "connecting to graph API")) } - return gc, nil + return gc, m365Cfg, nil } diff --git a/src/internal/connector/exchange/api/api.go b/src/internal/connector/exchange/api/api.go index b3ee67677..f03765f34 100644 --- a/src/internal/connector/exchange/api/api.go +++ b/src/internal/connector/exchange/api/api.go @@ -4,8 +4,10 @@ import ( "context" "github.com/microsoft/kiota-abstractions-go/serialization" + "github.com/pkg/errors" "github.com/alcionai/corso/src/internal/connector/graph" + "github.com/alcionai/corso/src/pkg/account" ) // --------------------------------------------------------------------------- @@ -26,14 +28,13 @@ type DeltaUpdate struct { // into M365 backstore. Responses -> returned items will only contain the information // that is included in the options // TODO: use selector or path for granularity into specific folders or specific date ranges -type GraphQuery func(ctx context.Context, gs graph.Servicer, userID string) (serialization.Parsable, error) +type GraphQuery func(ctx context.Context, userID string) (serialization.Parsable, error) // GraphRetrievalFunctions are functions from the Microsoft Graph API that retrieve // the default associated data of a M365 object. This varies by object. Additional // Queries must be run to obtain the omitted fields. type GraphRetrievalFunc func( ctx context.Context, - gs graph.Servicer, user, m365ID string, ) (serialization.Parsable, error) @@ -41,10 +42,25 @@ type GraphRetrievalFunc func( // interfaces // --------------------------------------------------------------------------- -// API is a struct used to fulfill the interface for exchange +// Client is used to fulfill the interface for exchange // queries that are traditionally backed by GraphAPI. A // struct is used in this case, instead of deferring to // pure function wrappers, so that the boundary separates the // granular implementation of the graphAPI and kiota away // from the exchange package's broader intents. -// type API struct{} +type Client struct { + Credentials account.M365Config +} + +func (c Client) service() (*graph.Service, error) { + adapter, err := graph.CreateAdapter( + c.Credentials.AzureTenantID, + c.Credentials.AzureClientID, + c.Credentials.AzureClientSecret, + ) + if err != nil { + return nil, errors.Wrap(err, "generating graph api service client") + } + + return graph.NewService(adapter), nil +} diff --git a/src/internal/connector/exchange/api/api_test.go b/src/internal/connector/exchange/api/api_test.go index f31bffe5e..dffd398ff 100644 --- a/src/internal/connector/exchange/api/api_test.go +++ b/src/internal/connector/exchange/api/api_test.go @@ -164,6 +164,8 @@ func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() { ctx, flush := tester.NewContext() defer flush() + c := Client{suite.credentials} + userID := tester.M365UserID(suite.T()) tests := []struct { name string @@ -171,17 +173,17 @@ func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() { }{ { name: "GraphQuery: Get All ContactFolders", - function: GetAllContactFolderNamesForUser, + function: c.GetAllContactFolderNamesForUser, }, { name: "GraphQuery: Get All Calendars for User", - function: GetAllCalendarNamesForUser, + function: c.GetAllCalendarNamesForUser, }, } for _, test := range tests { suite.T().Run(test.name, func(t *testing.T) { - response, err := test.function(ctx, suite.gs, userID) + response, err := test.function(ctx, userID) assert.NoError(t, err) assert.NotNil(t, response) }) diff --git a/src/internal/connector/exchange/api/contacts.go b/src/internal/connector/exchange/api/contacts.go index 48dc4ba15..89395a9fb 100644 --- a/src/internal/connector/exchange/api/contacts.go +++ b/src/internal/connector/exchange/api/contacts.go @@ -15,55 +15,79 @@ import ( // CreateContactFolder makes a contact folder with the displayName of folderName. // If successful, returns the created folder object. -func CreateContactFolder( +func (c Client) CreateContactFolder( ctx context.Context, - gs graph.Servicer, user, folderName string, ) (models.ContactFolderable, error) { + service, err := c.service() + if err != nil { + return nil, err + } + requestBody := models.NewContactFolder() temp := folderName requestBody.SetDisplayName(&temp) - return gs.Client().UsersById(user).ContactFolders().Post(ctx, requestBody, nil) + return service.Client().UsersById(user).ContactFolders().Post(ctx, requestBody, nil) } // DeleteContactFolder deletes the ContactFolder associated with the M365 ID if permissions are valid. // Errors returned if the function call was not successful. -func DeleteContactFolder(ctx context.Context, gs graph.Servicer, user, folderID string) error { - return gs.Client().UsersById(user).ContactFoldersById(folderID).Delete(ctx, nil) +func (c Client) DeleteContactFolder( + ctx context.Context, + user, folderID string, +) error { + service, err := c.service() + if err != nil { + return err + } + + return service.Client().UsersById(user).ContactFoldersById(folderID).Delete(ctx, nil) } // RetrieveContactDataForUser is a GraphRetrievalFun that returns all associated fields. -func RetrieveContactDataForUser( +func (c Client) RetrieveContactDataForUser( ctx context.Context, - gs graph.Servicer, user, m365ID string, ) (serialization.Parsable, error) { - return gs.Client().UsersById(user).ContactsById(m365ID).Get(ctx, nil) + service, err := c.service() + if err != nil { + return nil, err + } + + return service.Client().UsersById(user).ContactsById(m365ID).Get(ctx, nil) } // GetAllContactFolderNamesForUser is a GraphQuery function for getting // ContactFolderId and display names for contacts. All other information is omitted. // Does not return the default Contact Folder -func GetAllContactFolderNamesForUser( +func (c Client) GetAllContactFolderNamesForUser( ctx context.Context, - gs graph.Servicer, user string, ) (serialization.Parsable, error) { + service, err := c.service() + if err != nil { + return nil, err + } + options, err := optionsForContactFolders([]string{"displayName", "parentFolderId"}) if err != nil { return nil, err } - return gs.Client().UsersById(user).ContactFolders().Get(ctx, options) + return service.Client().UsersById(user).ContactFolders().Get(ctx, options) } -func GetContactFolderByID( +func (c Client) GetContactFolderByID( ctx context.Context, - gs graph.Servicer, userID, dirID string, optionalFields ...string, ) (models.ContactFolderable, error) { + service, err := c.service() + if err != nil { + return nil, err + } + fields := append([]string{"displayName", "parentFolderId"}, optionalFields...) ofcf, err := optionsForContactFolderByID(fields) @@ -71,7 +95,7 @@ func GetContactFolderByID( return nil, errors.Wrapf(err, "options for contact folder: %v", fields) } - return gs.Client(). + return service.Client(). UsersById(userID). ContactFoldersById(dirID). Get(ctx, ofcf) @@ -79,38 +103,47 @@ func GetContactFolderByID( // TODO: we want this to be the full handler, not only the builder. // but this halfway point minimizes changes for now. -func GetContactChildFoldersBuilder( +func (c Client) GetContactChildFoldersBuilder( ctx context.Context, - gs graph.Servicer, userID, baseDirID string, optionalFields ...string, ) ( *users.ItemContactFoldersItemChildFoldersRequestBuilder, *users.ItemContactFoldersItemChildFoldersRequestBuilderGetRequestConfiguration, + *graph.Service, error, ) { + service, err := c.service() + if err != nil { + return nil, nil, nil, err + } + fields := append([]string{"displayName", "parentFolderId"}, optionalFields...) ofcf, err := optionsForContactChildFolders(fields) if err != nil { - return nil, nil, errors.Wrapf(err, "options for contact child folders: %v", fields) + return nil, nil, nil, errors.Wrapf(err, "options for contact child folders: %v", fields) } - builder := gs.Client(). + builder := service.Client(). UsersById(userID). ContactFoldersById(baseDirID). ChildFolders() - return builder, ofcf, nil + return builder, ofcf, service, nil } // FetchContactIDsFromDirectory function that returns a list of all the m365IDs of the contacts // of the targeted directory -func FetchContactIDsFromDirectory( +func (c Client) FetchContactIDsFromDirectory( ctx context.Context, - gs graph.Servicer, user, directoryID, oldDelta string, ) ([]string, []string, DeltaUpdate, error) { + service, err := c.service() + if err != nil { + return nil, nil, DeltaUpdate{}, err + } + var ( errs *multierror.Error ids []string @@ -167,14 +200,14 @@ func FetchContactIDsFromDirectory( break } - builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(*nextLink, gs.Adapter()) + builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(*nextLink, service.Adapter()) } return nil } if len(oldDelta) > 0 { - err := getIDs(users.NewItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, gs.Adapter())) + err := getIDs(users.NewItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, service.Adapter())) // note: happy path, not the error condition if err == nil { return ids, removedIDs, DeltaUpdate{deltaURL, false}, errs.ErrorOrNil() @@ -189,7 +222,7 @@ func FetchContactIDsFromDirectory( errs = nil } - builder := gs.Client(). + builder := service.Client(). UsersById(user). ContactFoldersById(directoryID). Contacts(). diff --git a/src/internal/connector/exchange/api/events.go b/src/internal/connector/exchange/api/events.go index dce5ea0fe..a3636f2ba 100644 --- a/src/internal/connector/exchange/api/events.go +++ b/src/internal/connector/exchange/api/events.go @@ -15,68 +15,103 @@ import ( // 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 -func CreateCalendar(ctx context.Context, gs graph.Servicer, user, calendarName string) (models.Calendarable, error) { +func (c Client) CreateCalendar( + ctx context.Context, + user, calendarName string, +) (models.Calendarable, error) { + service, err := c.service() + if err != nil { + return nil, err + } + requestbody := models.NewCalendar() requestbody.SetName(&calendarName) - return gs.Client().UsersById(user).Calendars().Post(ctx, requestbody, nil) + return service.Client().UsersById(user).Calendars().Post(ctx, requestbody, nil) } // 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 -func DeleteCalendar(ctx context.Context, gs graph.Servicer, user, calendarID string) error { - return gs.Client().UsersById(user).CalendarsById(calendarID).Delete(ctx, nil) +func (c Client) DeleteCalendar( + ctx context.Context, + user, calendarID string, +) error { + service, err := c.service() + if err != nil { + return err + } + + return service.Client().UsersById(user).CalendarsById(calendarID).Delete(ctx, nil) } // RetrieveEventDataForUser is a GraphRetrievalFunc that returns event data. // Calendarable and attachment fields are omitted due to size -func RetrieveEventDataForUser( +func (c Client) RetrieveEventDataForUser( ctx context.Context, - gs graph.Servicer, user, m365ID string, ) (serialization.Parsable, error) { - return gs.Client().UsersById(user).EventsById(m365ID).Get(ctx, nil) + service, err := c.service() + if err != nil { + return nil, err + } + + return service.Client().UsersById(user).EventsById(m365ID).Get(ctx, nil) } -func GetAllCalendarNamesForUser(ctx context.Context, gs graph.Servicer, user string) (serialization.Parsable, error) { +func (c Client) GetAllCalendarNamesForUser( + ctx context.Context, + user string, +) (serialization.Parsable, error) { + service, err := c.service() + if err != nil { + return nil, err + } + options, err := optionsForCalendars([]string{"name", "owner"}) if err != nil { return nil, err } - return gs.Client().UsersById(user).Calendars().Get(ctx, options) + return service.Client().UsersById(user).Calendars().Get(ctx, options) } // TODO: we want this to be the full handler, not only the builder. // but this halfway point minimizes changes for now. -func GetCalendarsBuilder( +func (c Client) GetCalendarsBuilder( ctx context.Context, - gs graph.Servicer, userID string, optionalFields ...string, ) ( *users.ItemCalendarsRequestBuilder, *users.ItemCalendarsRequestBuilderGetRequestConfiguration, + *graph.Service, error, ) { - ofcf, err := optionsForCalendars(optionalFields) + service, err := c.service() if err != nil { - return nil, nil, errors.Wrapf(err, "options for event calendars: %v", optionalFields) + return nil, nil, nil, err } - builder := gs.Client(). - UsersById(userID). - Calendars() + ofcf, err := optionsForCalendars(optionalFields) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "options for event calendars: %v", optionalFields) + } - return builder, ofcf, nil + builder := service.Client().UsersById(userID).Calendars() + + return builder, ofcf, service, nil } // FetchEventIDsFromCalendar returns a list of all M365IDs of events of the targeted Calendar. -func FetchEventIDsFromCalendar( +func (c Client) FetchEventIDsFromCalendar( ctx context.Context, - gs graph.Servicer, user, calendarID, oldDelta string, ) ([]string, []string, DeltaUpdate, error) { + service, err := c.service() + if err != nil { + return nil, nil, DeltaUpdate{}, err + } + var ( errs *multierror.Error ids []string @@ -87,10 +122,7 @@ func FetchEventIDsFromCalendar( return nil, nil, DeltaUpdate{}, err } - builder := gs.Client(). - UsersById(user). - CalendarsById(calendarID). - Events() + builder := service.Client().UsersById(user).CalendarsById(calendarID).Events() for { resp, err := builder.Get(ctx, options) @@ -121,7 +153,7 @@ func FetchEventIDsFromCalendar( break } - builder = users.NewItemCalendarsItemEventsRequestBuilder(*nextLink, gs.Adapter()) + builder = users.NewItemCalendarsItemEventsRequestBuilder(*nextLink, service.Adapter()) } // Events don't have a delta endpoint so just return an empty string. diff --git a/src/internal/connector/exchange/api/mail.go b/src/internal/connector/exchange/api/mail.go index f7d31fedc..ba7059dba 100644 --- a/src/internal/connector/exchange/api/mail.go +++ b/src/internal/connector/exchange/api/mail.go @@ -15,26 +15,39 @@ import ( // 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 -func CreateMailFolder(ctx context.Context, gs graph.Servicer, user, folder string) (models.MailFolderable, error) { +func (c Client) CreateMailFolder( + ctx context.Context, + user, folder string, +) (models.MailFolderable, error) { + service, err := c.service() + if err != nil { + return nil, err + } + isHidden := false requestBody := models.NewMailFolder() requestBody.SetDisplayName(&folder) requestBody.SetIsHidden(&isHidden) - return gs.Client().UsersById(user).MailFolders().Post(ctx, requestBody, nil) + return service.Client().UsersById(user).MailFolders().Post(ctx, requestBody, nil) } -func CreateMailFolderWithParent( +func (c Client) CreateMailFolderWithParent( ctx context.Context, - gs graph.Servicer, user, folder, parentID string, ) (models.MailFolderable, error) { + service, err := c.service() + if err != nil { + return nil, err + } + isHidden := false requestBody := models.NewMailFolder() requestBody.SetDisplayName(&folder) requestBody.SetIsHidden(&isHidden) - return gs.Client(). + return service. + Client(). UsersById(user). MailFoldersById(parentID). ChildFolders(). @@ -43,18 +56,30 @@ func CreateMailFolderWithParent( // 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 -func DeleteMailFolder(ctx context.Context, gs graph.Servicer, user, folderID string) error { - return gs.Client().UsersById(user).MailFoldersById(folderID).Delete(ctx, nil) +func (c Client) DeleteMailFolder( + ctx context.Context, + user, folderID string, +) error { + service, err := c.service() + if err != nil { + return err + } + + return service.Client().UsersById(user).MailFoldersById(folderID).Delete(ctx, nil) } // RetrieveMessageDataForUser is a GraphRetrievalFunc that returns message data. // Attachment field is omitted due to size. -func RetrieveMessageDataForUser( +func (c Client) RetrieveMessageDataForUser( ctx context.Context, - gs graph.Servicer, user, m365ID string, ) (serialization.Parsable, error) { - return gs.Client().UsersById(user).MessagesById(m365ID).Get(ctx, nil) + service, err := c.service() + if err != nil { + return nil, err + } + + return service.Client().UsersById(user).MessagesById(m365ID).Get(ctx, nil) } // GetMailFoldersBuilder retrieves all of the users current mail folders. @@ -62,41 +87,56 @@ func RetrieveMessageDataForUser( // not contain historical data. // TODO: we want this to be the full handler, not only the builder. // but this halfway point minimizes changes for now. -func GetAllMailFoldersBuilder( +func (c Client) GetAllMailFoldersBuilder( ctx context.Context, - gs graph.Servicer, userID string, -) *users.ItemMailFoldersDeltaRequestBuilder { - return gs.Client(). +) ( + *users.ItemMailFoldersDeltaRequestBuilder, + *graph.Service, + error, +) { + service, err := c.service() + if err != nil { + return nil, nil, err + } + + builder := service.Client(). UsersById(userID). MailFolders(). Delta() + + return builder, service, nil } -func GetMailFolderByID( +func (c Client) GetMailFolderByID( ctx context.Context, - gs graph.Servicer, userID, dirID string, optionalFields ...string, ) (models.MailFolderable, error) { + service, err := c.service() + if err != nil { + return nil, err + } + ofmf, err := optionsForMailFoldersItem(optionalFields) if err != nil { return nil, errors.Wrapf(err, "options for mail folder: %v", optionalFields) } - return gs.Client(). - UsersById(userID). - MailFoldersById(dirID). - Get(ctx, ofmf) + return service.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 FetchMessageIDsFromDirectory( +func (c Client) FetchMessageIDsFromDirectory( ctx context.Context, - gs graph.Servicer, user, directoryID, oldDelta string, ) ([]string, []string, DeltaUpdate, error) { + service, err := c.service() + if err != nil { + return nil, nil, DeltaUpdate{}, err + } + var ( errs *multierror.Error ids []string @@ -153,14 +193,14 @@ func FetchMessageIDsFromDirectory( break } - builder = users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(*nextLink, gs.Adapter()) + builder = users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(*nextLink, service.Adapter()) } return nil } if len(oldDelta) > 0 { - err := getIDs(users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, gs.Adapter())) + err := getIDs(users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, service.Adapter())) // note: happy path, not the error condition if err == nil { return ids, removedIDs, DeltaUpdate{deltaURL, false}, errs.ErrorOrNil() @@ -175,11 +215,7 @@ func FetchMessageIDsFromDirectory( errs = nil } - builder := gs.Client(). - UsersById(user). - MailFoldersById(directoryID). - Messages(). - Delta() + builder := service.Client().UsersById(user).MailFoldersById(directoryID).Messages().Delta() if err := getIDs(builder); err != nil { return nil, nil, DeltaUpdate{}, err diff --git a/src/internal/connector/exchange/contact_folder_cache.go b/src/internal/connector/exchange/contact_folder_cache.go index 29f13e3f5..388cf10ab 100644 --- a/src/internal/connector/exchange/contact_folder_cache.go +++ b/src/internal/connector/exchange/contact_folder_cache.go @@ -16,7 +16,8 @@ var _ graph.ContainerResolver = &contactFolderCache{} type contactFolderCache struct { *containerResolver - gs graph.Servicer + ac api.Client + // gs graph.Servicer userID string } @@ -25,7 +26,7 @@ func (cfc *contactFolderCache) populateContactRoot( directoryID string, baseContainerPath []string, ) error { - f, err := api.GetContactFolderByID(ctx, cfc.gs, cfc.userID, directoryID) + f, err := cfc.ac.GetContactFolderByID(ctx, cfc.userID, directoryID) if err != nil { return errors.Wrapf( err, @@ -56,9 +57,8 @@ func (cfc *contactFolderCache) Populate( var errs error - builder, options, err := api.GetContactChildFoldersBuilder( + builder, options, servicer, err := cfc.ac.GetContactChildFoldersBuilder( ctx, - cfc.gs, cfc.userID, baseID) if err != nil { @@ -97,7 +97,7 @@ func (cfc *contactFolderCache) Populate( break } - builder = msuser.NewItemContactFoldersItemChildFoldersRequestBuilder(*resp.GetOdataNextLink(), cfc.gs.Adapter()) + builder = msuser.NewItemContactFoldersItemChildFoldersRequestBuilder(*resp.GetOdataNextLink(), servicer.Adapter()) } if err := cfc.populatePaths(ctx); err != nil { diff --git a/src/internal/connector/exchange/container_resolver_test.go b/src/internal/connector/exchange/container_resolver_test.go index 8099a7843..f2250c439 100644 --- a/src/internal/connector/exchange/container_resolver_test.go +++ b/src/internal/connector/exchange/container_resolver_test.go @@ -494,9 +494,6 @@ func (suite *FolderCacheIntegrationSuite) TestCreateContainerDestination() { m365, err := a.M365Config() require.NoError(suite.T(), err) - connector, err := createService(m365) - require.NoError(suite.T(), err) - var ( user = tester.M365UserID(suite.T()) directoryCaches = make(map[path.CategoryType]graph.ContainerResolver) @@ -596,11 +593,10 @@ func (suite *FolderCacheIntegrationSuite) TestCreateContainerDestination() { suite.T().Run(test.name, func(t *testing.T) { folderID, err := CreateContainerDestinaion( ctx, - connector, + m365, test.pathFunc1(t), folderName, - directoryCaches, - ) + directoryCaches) require.NoError(t, err) resolver := directoryCaches[test.category] @@ -609,11 +605,10 @@ func (suite *FolderCacheIntegrationSuite) TestCreateContainerDestination() { secondID, err := CreateContainerDestinaion( ctx, - connector, + m365, test.pathFunc2(t), folderName, - directoryCaches, - ) + directoryCaches) require.NoError(t, err) _, err = resolver.IDToPath(ctx, secondID) diff --git a/src/internal/connector/exchange/event_calendar_cache.go b/src/internal/connector/exchange/event_calendar_cache.go index 362fa902a..15ccdeb83 100644 --- a/src/internal/connector/exchange/event_calendar_cache.go +++ b/src/internal/connector/exchange/event_calendar_cache.go @@ -16,7 +16,8 @@ var _ graph.ContainerResolver = &eventCalendarCache{} type eventCalendarCache struct { *containerResolver - gs graph.Servicer + // gs graph.Servicer + ac api.Client userID string } @@ -32,7 +33,7 @@ func (ecc *eventCalendarCache) Populate( ecc.containerResolver = newContainerResolver() } - builder, options, err := api.GetCalendarsBuilder(ctx, ecc.gs, ecc.userID, "name") + builder, options, servicer, err := ecc.ac.GetCalendarsBuilder(ctx, ecc.userID, "name") if err != nil { return err } @@ -67,7 +68,7 @@ func (ecc *eventCalendarCache) Populate( break } - builder = msuser.NewItemCalendarsRequestBuilder(*resp.GetOdataNextLink(), ecc.gs.Adapter()) + builder = msuser.NewItemCalendarsRequestBuilder(*resp.GetOdataNextLink(), servicer.Adapter()) } for _, container := range directories { diff --git a/src/internal/connector/exchange/exchange_data_collection.go b/src/internal/connector/exchange/exchange_data_collection.go index b5aca9064..5b1b05b81 100644 --- a/src/internal/connector/exchange/exchange_data_collection.go +++ b/src/internal/connector/exchange/exchange_data_collection.go @@ -59,6 +59,7 @@ type Collection struct { // service - client/adapter pair used to access M365 back store service graph.Servicer + ac api.Client category path.CategoryType statusUpdater support.StatusUpdater @@ -88,12 +89,14 @@ func NewCollection( user string, curr, prev path.Path, category path.CategoryType, + ac api.Client, service graph.Servicer, statusUpdater support.StatusUpdater, ctrlOpts control.Options, doNotMergeItems bool, ) Collection { collection := Collection{ + ac: ac, category: category, ctrl: ctrlOpts, data: make(chan data.Stream, collectionChannelBufferSize), @@ -136,14 +139,14 @@ func (col *Collection) Items() <-chan data.Stream { // GetQueryAndSerializeFunc helper function that returns the two functions functions // required to convert M365 identifier into a byte array filled with the serialized data -func GetQueryAndSerializeFunc(category path.CategoryType) (api.GraphRetrievalFunc, GraphSerializeFunc) { +func GetQueryAndSerializeFunc(ac api.Client, category path.CategoryType) (api.GraphRetrievalFunc, GraphSerializeFunc) { switch category { case path.ContactsCategory: - return api.RetrieveContactDataForUser, serializeAndStreamContact + return ac.RetrieveContactDataForUser, serializeAndStreamContact case path.EventsCategory: - return api.RetrieveEventDataForUser, serializeAndStreamEvent + return ac.RetrieveEventDataForUser, serializeAndStreamEvent case path.EmailCategory: - return api.RetrieveMessageDataForUser, serializeAndStreamMessage + return ac.RetrieveMessageDataForUser, serializeAndStreamMessage // Unsupported options returns nil, nil default: return nil, nil @@ -204,7 +207,7 @@ func (col *Collection) streamItems(ctx context.Context) { // get QueryBasedonIdentifier // verify that it is the correct type in called function // serializationFunction - query, serializeFunc := GetQueryAndSerializeFunc(col.category) + query, serializeFunc := GetQueryAndSerializeFunc(col.ac, col.category) if query == nil { errs = fmt.Errorf("unrecognized collection type: %s", col.category) return @@ -263,7 +266,7 @@ func (col *Collection) streamItems(ctx context.Context) { ) for i := 1; i <= numberOfRetries; i++ { - response, err = query(ctx, col.service, user, id) + response, err = query(ctx, user, id) if err == nil { break } diff --git a/src/internal/connector/exchange/exchange_data_collection_test.go b/src/internal/connector/exchange/exchange_data_collection_test.go index 62b1c48f7..79efc59f4 100644 --- a/src/internal/connector/exchange/exchange_data_collection_test.go +++ b/src/internal/connector/exchange/exchange_data_collection_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/alcionai/corso/src/internal/connector/exchange/api" "github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/path" @@ -136,7 +137,7 @@ func (suite *ExchangeDataCollectionSuite) TestNewCollection_state() { c := NewCollection( "u", test.curr, test.prev, - 0, nil, nil, control.Options{}, + 0, api.Client{}, nil, nil, control.Options{}, false) assert.Equal(t, test.expect, c.State()) }) diff --git a/src/internal/connector/exchange/folder_resolver_test.go b/src/internal/connector/exchange/folder_resolver_test.go index 95f2ee305..0cc219761 100644 --- a/src/internal/connector/exchange/folder_resolver_test.go +++ b/src/internal/connector/exchange/folder_resolver_test.go @@ -7,13 +7,15 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/alcionai/corso/src/internal/connector/exchange/api" "github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/pkg/account" ) type CacheResolverSuite struct { suite.Suite - gs graph.Servicer + credentials account.M365Config } func TestCacheResolverIntegrationSuite(t *testing.T) { @@ -35,10 +37,7 @@ func (suite *CacheResolverSuite) SetupSuite() { m365, err := a.M365Config() require.NoError(t, err) - service, err := createService(m365) - require.NoError(t, err) - - suite.gs = service + suite.credentials = m365 } func (suite *CacheResolverSuite) TestPopulate() { @@ -48,14 +47,14 @@ func (suite *CacheResolverSuite) TestPopulate() { eventFunc := func(t *testing.T) graph.ContainerResolver { return &eventCalendarCache{ userID: tester.M365UserID(t), - gs: suite.gs, + ac: api.Client{Credentials: suite.credentials}, } } contactFunc := func(t *testing.T) graph.ContainerResolver { return &contactFolderCache{ userID: tester.M365UserID(t), - gs: suite.gs, + ac: api.Client{Credentials: suite.credentials}, } } diff --git a/src/internal/connector/exchange/iterators_test.go b/src/internal/connector/exchange/iterators_test.go index d3f2c8621..fd784138a 100644 --- a/src/internal/connector/exchange/iterators_test.go +++ b/src/internal/connector/exchange/iterators_test.go @@ -15,6 +15,7 @@ import ( "github.com/alcionai/corso/src/internal/connector/mockconnector" "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/selectors" ) @@ -84,7 +85,7 @@ func (suite *ExchangeIteratorSuite) TestCollectionFunctions() { tests := []struct { name string - queryFunc api.GraphQuery + queryFunc func(account.M365Config) api.GraphQuery scope selectors.ExchangeScope iterativeFunction func( container map[string]graph.Container, @@ -93,14 +94,18 @@ func (suite *ExchangeIteratorSuite) TestCollectionFunctions() { transformer absser.ParsableFactory }{ { - name: "Contacts Iterative Check", - queryFunc: api.GetAllContactFolderNamesForUser, + name: "Contacts Iterative Check", + queryFunc: func(amc account.M365Config) api.GraphQuery { + return api.Client{Credentials: amc}.GetAllContactFolderNamesForUser + }, transformer: models.CreateContactFolderCollectionResponseFromDiscriminatorValue, iterativeFunction: IterativeCollectContactContainers, }, { - name: "Events Iterative Check", - queryFunc: api.GetAllCalendarNamesForUser, + name: "Events Iterative Check", + queryFunc: func(amc account.M365Config) api.GraphQuery { + return api.Client{Credentials: amc}.GetAllCalendarNamesForUser + }, transformer: models.CreateCalendarCollectionResponseFromDiscriminatorValue, iterativeFunction: IterativeCollectCalendarContainers, }, @@ -114,7 +119,7 @@ func (suite *ExchangeIteratorSuite) TestCollectionFunctions() { service, err := createService(m365) require.NoError(t, err) - response, err := test.queryFunc(ctx, service, userID) + response, err := test.queryFunc(m365)(ctx, userID) require.NoError(t, err) // Iterator Creation diff --git a/src/internal/connector/exchange/mail_folder_cache.go b/src/internal/connector/exchange/mail_folder_cache.go index 0f09a74f5..40ad2d297 100644 --- a/src/internal/connector/exchange/mail_folder_cache.go +++ b/src/internal/connector/exchange/mail_folder_cache.go @@ -20,7 +20,8 @@ var _ graph.ContainerResolver = &mailFolderCache{} // nameLookup map: Key: DisplayName Value: ID type mailFolderCache struct { *containerResolver - gs graph.Servicer + // gs graph.Servicer + ac api.Client userID string } @@ -35,7 +36,7 @@ func (mc *mailFolderCache) populateMailRoot( for _, fldr := range []string{rootFolderAlias, DefaultMailFolder} { var directory string - f, err := api.GetMailFolderByID(ctx, mc.gs, mc.userID, fldr, "displayName", "parentFolderId") + f, err := mc.ac.GetMailFolderByID(ctx, mc.userID, fldr, "displayName", "parentFolderId") if err != nil { return errors.Wrap(err, "fetching root folder"+support.ConnectorStackErrorTrace(err)) } @@ -67,7 +68,10 @@ func (mc *mailFolderCache) Populate( return err } - query := api.GetAllMailFoldersBuilder(ctx, mc.gs, mc.userID) + query, servicer, err := mc.ac.GetAllMailFoldersBuilder(ctx, mc.userID) + if err != nil { + return err + } var errs *multierror.Error @@ -94,7 +98,7 @@ func (mc *mailFolderCache) Populate( break } - query = msfolderdelta.NewItemMailFoldersDeltaRequestBuilder(*link, mc.gs.Adapter()) + query = msfolderdelta.NewItemMailFoldersDeltaRequestBuilder(*link, servicer.Adapter()) } if err := mc.populatePaths(ctx); err != nil { diff --git a/src/internal/connector/exchange/mail_folder_cache_test.go b/src/internal/connector/exchange/mail_folder_cache_test.go index 1d3c01e75..9568747bb 100644 --- a/src/internal/connector/exchange/mail_folder_cache_test.go +++ b/src/internal/connector/exchange/mail_folder_cache_test.go @@ -8,8 +8,9 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/alcionai/corso/src/internal/connector/graph" + "github.com/alcionai/corso/src/internal/connector/exchange/api" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/pkg/account" ) const ( @@ -26,7 +27,7 @@ const ( type MailFolderCacheIntegrationSuite struct { suite.Suite - gs graph.Servicer + credentials account.M365Config } func TestMailFolderCacheIntegrationSuite(t *testing.T) { @@ -48,10 +49,7 @@ func (suite *MailFolderCacheIntegrationSuite) SetupSuite() { m365, err := a.M365Config() require.NoError(t, err) - service, err := createService(m365) - require.NoError(t, err) - - suite.gs = service + suite.credentials = m365 } func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() { @@ -85,7 +83,7 @@ func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() { suite.T().Run(test.name, func(t *testing.T) { mfc := mailFolderCache{ userID: userID, - gs: suite.gs, + ac: api.Client{Credentials: suite.credentials}, } require.NoError(t, mfc.Populate(ctx, test.root, test.path...)) diff --git a/src/internal/connector/exchange/restore_test.go b/src/internal/connector/exchange/restore_test.go index 6a58d400a..4904eaa4c 100644 --- a/src/internal/connector/exchange/restore_test.go +++ b/src/internal/connector/exchange/restore_test.go @@ -15,13 +15,16 @@ import ( "github.com/alcionai/corso/src/internal/connector/mockconnector" "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/path" ) type ExchangeRestoreSuite struct { suite.Suite - gs graph.Servicer + gs graph.Servicer + credentials account.M365Config + ac api.Client } func TestExchangeRestoreSuite(t *testing.T) { @@ -41,13 +44,16 @@ func (suite *ExchangeRestoreSuite) SetupSuite() { m365, err := a.M365Config() require.NoError(t, err) - adpt, err := graph.CreateAdapter( - m365.AzureTenantID, - m365.AzureClientID, - m365.AzureClientSecret) - require.NoError(t, err) + suite.credentials = m365 + suite.ac = api.Client{Credentials: m365} - suite.gs = graph.NewService(adpt) + // adpt, err := graph.CreateAdapter( + // m365.AzureTenantID, + // m365.AzureClientID, + // m365.AzureClientSecret) + // require.NoError(t, err) + + // suite.gs = graph.NewService(adpt) require.NoError(suite.T(), err) } @@ -65,14 +71,14 @@ func (suite *ExchangeRestoreSuite) TestRestoreContact() { folderName = "TestRestoreContact: " + common.FormatSimpleDateTime(now) ) - aFolder, err := api.CreateContactFolder(ctx, suite.gs, userID, folderName) + aFolder, err := suite.ac.CreateContactFolder(ctx, userID, folderName) require.NoError(t, err) folderID := *aFolder.GetId() defer func() { // Remove the folder containing contact prior to exiting test - err = api.DeleteContactFolder(ctx, suite.gs, userID, folderID) + err = suite.ac.DeleteContactFolder(ctx, userID, folderID) assert.NoError(t, err) }() @@ -98,14 +104,14 @@ func (suite *ExchangeRestoreSuite) TestRestoreEvent() { name = "TestRestoreEvent: " + common.FormatSimpleDateTime(time.Now()) ) - calendar, err := api.CreateCalendar(ctx, suite.gs, userID, name) + calendar, err := suite.ac.CreateCalendar(ctx, userID, name) require.NoError(t, err) calendarID := *calendar.GetId() defer func() { // Removes calendar containing events created during the test - err = api.DeleteCalendar(ctx, suite.gs, userID, calendarID) + err = suite.ac.DeleteCalendar(ctx, userID, calendarID) assert.NoError(t, err) }() @@ -134,17 +140,17 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name string bytes []byte category path.CategoryType - cleanupFunc func(context.Context, graph.Servicer, string, string) error + cleanupFunc func(context.Context, string, string) error destination func(*testing.T, context.Context) string }{ { name: "Test Mail", bytes: mockconnector.GetMockMessageBytes("Restore Exchange Object"), category: path.EmailCategory, - cleanupFunc: api.DeleteMailFolder, + cleanupFunc: suite.ac.DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailObject: " + common.FormatSimpleDateTime(now) - folder, err := api.CreateMailFolder(ctx, suite.gs, userID, folderName) + folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -154,10 +160,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Mail: One Direct Attachment", bytes: mockconnector.GetMockMessageWithDirectAttachment("Restore 1 Attachment"), category: path.EmailCategory, - cleanupFunc: api.DeleteMailFolder, + cleanupFunc: suite.ac.DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailwithAttachment: " + common.FormatSimpleDateTime(now) - folder, err := api.CreateMailFolder(ctx, suite.gs, userID, folderName) + folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -167,10 +173,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Mail: One Large Attachment", bytes: mockconnector.GetMockMessageWithLargeAttachment("Restore Large Attachment"), category: path.EmailCategory, - cleanupFunc: api.DeleteMailFolder, + cleanupFunc: suite.ac.DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailwithLargeAttachment: " + common.FormatSimpleDateTime(now) - folder, err := api.CreateMailFolder(ctx, suite.gs, userID, folderName) + folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -180,10 +186,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Mail: Two Attachments", bytes: mockconnector.GetMockMessageWithTwoAttachments("Restore 2 Attachments"), category: path.EmailCategory, - cleanupFunc: api.DeleteMailFolder, + cleanupFunc: suite.ac.DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailwithAttachments: " + common.FormatSimpleDateTime(now) - folder, err := api.CreateMailFolder(ctx, suite.gs, userID, folderName) + folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -193,10 +199,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Mail: Reference(OneDrive) Attachment", bytes: mockconnector.GetMessageWithOneDriveAttachment("Restore Reference(OneDrive) Attachment"), category: path.EmailCategory, - cleanupFunc: api.DeleteMailFolder, + cleanupFunc: suite.ac.DeleteMailFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreMailwithReferenceAttachment: " + common.FormatSimpleDateTime(now) - folder, err := api.CreateMailFolder(ctx, suite.gs, userID, folderName) + folder, err := suite.ac.CreateMailFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -207,10 +213,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Contact", bytes: mockconnector.GetMockContactBytes("Test_Omega"), category: path.ContactsCategory, - cleanupFunc: api.DeleteContactFolder, + cleanupFunc: suite.ac.DeleteContactFolder, destination: func(t *testing.T, ctx context.Context) string { folderName := "TestRestoreContactObject: " + common.FormatSimpleDateTime(now) - folder, err := api.CreateContactFolder(ctx, suite.gs, userID, folderName) + folder, err := suite.ac.CreateContactFolder(ctx, userID, folderName) require.NoError(t, err) return *folder.GetId() @@ -220,10 +226,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Events", bytes: mockconnector.GetDefaultMockEventBytes("Restored Event Object"), category: path.EventsCategory, - cleanupFunc: api.DeleteCalendar, + cleanupFunc: suite.ac.DeleteCalendar, destination: func(t *testing.T, ctx context.Context) string { calendarName := "TestRestoreEventObject: " + common.FormatSimpleDateTime(now) - calendar, err := api.CreateCalendar(ctx, suite.gs, userID, calendarName) + calendar, err := suite.ac.CreateCalendar(ctx, userID, calendarName) require.NoError(t, err) return *calendar.GetId() @@ -233,10 +239,10 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { name: "Test Event with Attachment", bytes: mockconnector.GetMockEventWithAttachment("Restored Event Attachment"), category: path.EventsCategory, - cleanupFunc: api.DeleteCalendar, + cleanupFunc: suite.ac.DeleteCalendar, destination: func(t *testing.T, ctx context.Context) string { calendarName := "TestRestoreEventObject_" + common.FormatSimpleDateTime(now) - calendar, err := api.CreateCalendar(ctx, suite.gs, userID, calendarName) + calendar, err := suite.ac.CreateCalendar(ctx, userID, calendarName) require.NoError(t, err) return *calendar.GetId() @@ -262,7 +268,7 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { assert.NoError(t, err, support.ConnectorStackErrorTrace(err)) assert.NotNil(t, info, "item info is populated") - cleanupError := test.cleanupFunc(ctx, service, userID, destination) + cleanupError := test.cleanupFunc(ctx, userID, destination) assert.NoError(t, cleanupError) }) } diff --git a/src/internal/connector/exchange/service_functions.go b/src/internal/connector/exchange/service_functions.go index 47d3736df..3dc36abcc 100644 --- a/src/internal/connector/exchange/service_functions.go +++ b/src/internal/connector/exchange/service_functions.go @@ -6,6 +6,7 @@ import ( "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/pkg/account" "github.com/alcionai/corso/src/pkg/path" @@ -36,34 +37,29 @@ func PopulateExchangeContainerResolver( qp graph.QueryParams, ) (graph.ContainerResolver, error) { var ( - res graph.ContainerResolver - cacheRoot string - service, err = createService(qp.Credentials) + res graph.ContainerResolver + cacheRoot string ) - if err != nil { - return nil, err - } - switch qp.Category { case path.EmailCategory: res = &mailFolderCache{ userID: qp.ResourceOwner, - gs: service, + ac: api.Client{Credentials: qp.Credentials}, } cacheRoot = rootFolderAlias case path.ContactsCategory: res = &contactFolderCache{ userID: qp.ResourceOwner, - gs: service, + ac: api.Client{Credentials: qp.Credentials}, } cacheRoot = DefaultContactFolder case path.EventsCategory: res = &eventCalendarCache{ userID: qp.ResourceOwner, - gs: service, + ac: api.Client{Credentials: qp.Credentials}, } cacheRoot = DefaultCalendar diff --git a/src/internal/connector/exchange/service_iterators.go b/src/internal/connector/exchange/service_iterators.go index c0b54ef91..834e06a21 100644 --- a/src/internal/connector/exchange/service_iterators.go +++ b/src/internal/connector/exchange/service_iterators.go @@ -35,6 +35,7 @@ func filterContainersAndFillCollections( ) error { var ( errs error + ac = api.Client{Credentials: qp.Credentials} // folder ID -> delta url or folder path lookups deltaURLs = map[string]string{} currPaths = map[string]string{} @@ -43,7 +44,7 @@ func filterContainersAndFillCollections( tombstones = makeTombstones(dps) ) - getJobs, err := getFetchIDFunc(qp.Category) + getJobs, err := getFetchIDFunc(ac, qp.Category) if err != nil { return support.WrapAndAppend(qp.ResourceOwner, err, errs) } @@ -85,7 +86,7 @@ func filterContainersAndFillCollections( } } - added, removed, newDelta, err := getJobs(ctx, service, qp.ResourceOwner, cID, prevDelta) + added, removed, newDelta, err := getJobs(ctx, qp.ResourceOwner, cID, prevDelta) if err != nil { if graph.IsErrDeletedInFlight(err) == nil { errs = support.WrapAndAppend(qp.ResourceOwner, err, errs) @@ -110,11 +111,11 @@ func filterContainersAndFillCollections( currPath, prevPath, scope.Category().PathType(), + ac, service, statusUpdater, ctrlOpts, - newDelta.Reset, - ) + newDelta.Reset) collections[cID] = &edc edc.added = append(edc.added, added...) @@ -160,11 +161,11 @@ func filterContainersAndFillCollections( nil, // marks the collection as deleted prevPath, scope.Category().PathType(), + ac, service, statusUpdater, ctrlOpts, - false, - ) + false) collections[id] = &edc } @@ -267,18 +268,17 @@ func IterativeCollectCalendarContainers( // container supports fetching delta records. type FetchIDFunc func( ctx context.Context, - gs graph.Servicer, user, containerID, oldDeltaToken string, ) ([]string, []string, api.DeltaUpdate, error) -func getFetchIDFunc(category path.CategoryType) (FetchIDFunc, error) { +func getFetchIDFunc(ac api.Client, category path.CategoryType) (FetchIDFunc, error) { switch category { case path.EmailCategory: - return api.FetchMessageIDsFromDirectory, nil + return ac.FetchMessageIDsFromDirectory, nil case path.EventsCategory: - return api.FetchEventIDsFromCalendar, nil + return ac.FetchEventIDsFromCalendar, nil case path.ContactsCategory: - return api.FetchContactIDsFromDirectory, nil + return ac.FetchContactIDsFromDirectory, nil default: return nil, fmt.Errorf("category %s not supported by getFetchIDFunc", category) } diff --git a/src/internal/connector/exchange/service_restore.go b/src/internal/connector/exchange/service_restore.go index 2c201bdf5..9babfdaad 100644 --- a/src/internal/connector/exchange/service_restore.go +++ b/src/internal/connector/exchange/service_restore.go @@ -17,6 +17,7 @@ import ( "github.com/alcionai/corso/src/internal/data" D "github.com/alcionai/corso/src/internal/diagnostics" "github.com/alcionai/corso/src/internal/observe" + "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/logger" @@ -284,6 +285,7 @@ func SendMailToBackStore( // @param dest: container destination to M365 func RestoreExchangeDataCollections( ctx context.Context, + creds account.M365Config, gs graph.Servicer, dest control.RestoreDestination, dcs []data.Collection, @@ -313,7 +315,7 @@ func RestoreExchangeDataCollections( containerID, err := CreateContainerDestinaion( ctx, - gs, + creds, dc.FullPath(), dest.ContainerName, userCaches) @@ -431,12 +433,13 @@ func restoreCollection( // @ returns the container ID of the new destination container. func CreateContainerDestinaion( ctx context.Context, - gs graph.Servicer, + creds account.M365Config, directory path.Path, destination string, caches map[path.CategoryType]graph.ContainerResolver, ) (string, error) { var ( + ac = api.Client{Credentials: creds} newCache = false user = directory.ResourceOwner() category = directory.Category() @@ -449,7 +452,7 @@ func CreateContainerDestinaion( if directoryCache == nil { mfc := &mailFolderCache{ userID: user, - gs: gs, + ac: ac, } caches[category] = mfc @@ -459,16 +462,17 @@ func CreateContainerDestinaion( return establishMailRestoreLocation( ctx, + ac, newPathFolders, directoryCache, user, - gs, newCache) + case path.ContactsCategory: if directoryCache == nil { cfc := &contactFolderCache{ userID: user, - gs: gs, + ac: ac, } caches[category] = cfc newCache = true @@ -477,16 +481,17 @@ func CreateContainerDestinaion( return establishContactsRestoreLocation( ctx, + ac, newPathFolders, directoryCache, user, - gs, newCache) + case path.EventsCategory: if directoryCache == nil { ecc := &eventCalendarCache{ userID: user, - gs: gs, + ac: ac, } caches[category] = ecc newCache = true @@ -495,10 +500,10 @@ func CreateContainerDestinaion( return establishEventsRestoreLocation( ctx, + ac, newPathFolders, directoryCache, user, - gs, newCache, ) default: @@ -513,10 +518,10 @@ func CreateContainerDestinaion( // @param isNewCache identifies if the cache is created and not populated func establishMailRestoreLocation( ctx context.Context, + ac api.Client, folders []string, mfc graph.ContainerResolver, user string, - service graph.Servicer, isNewCache bool, ) (string, error) { // Process starts with the root folder in order to recreate @@ -533,12 +538,7 @@ func establishMailRestoreLocation( continue } - temp, err := api.CreateMailFolderWithParent( - ctx, - service, - user, - folder, - folderID) + temp, err := ac.CreateMailFolderWithParent(ctx, user, folder, folderID) if err != nil { // Should only error if cache malfunctions or incorrect parameters return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) @@ -574,10 +574,10 @@ func establishMailRestoreLocation( // @param isNewCache bool representation of whether Populate function needs to be run func establishContactsRestoreLocation( ctx context.Context, + ac api.Client, folders []string, cfc graph.ContainerResolver, user string, - gs graph.Servicer, isNewCache bool, ) (string, error) { cached, ok := cfc.PathInCache(folders[0]) @@ -585,7 +585,7 @@ func establishContactsRestoreLocation( return cached, nil } - temp, err := api.CreateContactFolder(ctx, gs, user, folders[0]) + temp, err := ac.CreateContactFolder(ctx, user, folders[0]) if err != nil { return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) } @@ -607,10 +607,10 @@ func establishContactsRestoreLocation( func establishEventsRestoreLocation( ctx context.Context, + ac api.Client, folders []string, ecc graph.ContainerResolver, // eventCalendarCache user string, - gs graph.Servicer, isNewCache bool, ) (string, error) { cached, ok := ecc.PathInCache(folders[0]) @@ -618,7 +618,7 @@ func establishEventsRestoreLocation( return cached, nil } - temp, err := api.CreateCalendar(ctx, gs, user, folders[0]) + temp, err := ac.CreateCalendar(ctx, user, folders[0]) if err != nil { return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) } diff --git a/src/internal/connector/graph_connector.go b/src/internal/connector/graph_connector.go index aeaf8193b..e047a017b 100644 --- a/src/internal/connector/graph_connector.go +++ b/src/internal/connector/graph_connector.go @@ -252,6 +252,7 @@ func (gc *GraphConnector) UnionSiteIDsAndWebURLs(ctx context.Context, ids, urls // SideEffect: gc.status is updated at the completion of operation func (gc *GraphConnector) RestoreDataCollections( ctx context.Context, + acct account.Account, selector selectors.Selector, dest control.RestoreDestination, dcs []data.Collection, @@ -265,9 +266,14 @@ func (gc *GraphConnector) RestoreDataCollections( deets = &details.Builder{} ) + creds, err := acct.M365Config() + if err != nil { + return nil, errors.Wrap(err, "malformed azure credentials") + } + switch selector.Service { case selectors.ServiceExchange: - status, err = exchange.RestoreExchangeDataCollections(ctx, gc.Service, dest, dcs, deets) + status, err = exchange.RestoreExchangeDataCollections(ctx, creds, gc.Service, dest, dcs, deets) case selectors.ServiceOneDrive: status, err = onedrive.RestoreCollections(ctx, gc.Service, dest, dcs, deets) case selectors.ServiceSharePoint: diff --git a/src/internal/connector/graph_connector_disconnected_test.go b/src/internal/connector/graph_connector_disconnected_test.go index afefcfad8..f16846acc 100644 --- a/src/internal/connector/graph_connector_disconnected_test.go +++ b/src/internal/connector/graph_connector_disconnected_test.go @@ -168,18 +168,20 @@ func (suite *DisconnectedGraphConnectorSuite) TestGraphConnector_ErrorChecking() } func (suite *DisconnectedGraphConnectorSuite) TestRestoreFailsBadService() { - t := suite.T() - ctx, flush := tester.NewContext() defer flush() - gc := GraphConnector{wg: &sync.WaitGroup{}} - sel := selectors.Selector{ - Service: selectors.ServiceUnknown, - } - dest := tester.DefaultTestRestoreDestination() + var ( + t = suite.T() + acct = tester.NewM365Account(t) + dest = tester.DefaultTestRestoreDestination() + gc = GraphConnector{wg: &sync.WaitGroup{}} + sel = selectors.Selector{ + Service: selectors.ServiceUnknown, + } + ) - deets, err := gc.RestoreDataCollections(ctx, sel, dest, nil) + deets, err := gc.RestoreDataCollections(ctx, acct, sel, dest, nil) assert.Error(t, err) assert.NotNil(t, deets) diff --git a/src/internal/connector/graph_connector_test.go b/src/internal/connector/graph_connector_test.go index c788ca07b..28481a26e 100644 --- a/src/internal/connector/graph_connector_test.go +++ b/src/internal/connector/graph_connector_test.go @@ -17,6 +17,7 @@ import ( "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/pkg/account" "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/selectors" @@ -133,8 +134,10 @@ func (suite *GraphConnectorUnitSuite) TestUnionSiteIDsAndWebURLs() { type GraphConnectorIntegrationSuite struct { suite.Suite - connector *GraphConnector - user string + connector *GraphConnector + user string + acct account.Account + credentials account.M365Config } func TestGraphConnectorIntegrationSuite(t *testing.T) { @@ -155,6 +158,7 @@ func (suite *GraphConnectorIntegrationSuite) SetupSuite() { suite.connector = loadConnector(ctx, suite.T(), Users) suite.user = tester.M365UserID(suite.T()) + suite.acct = tester.NewM365Account(suite.T()) tester.LogTimeOfTest(suite.T()) } @@ -265,7 +269,12 @@ func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() { ctx, flush := tester.NewContext() defer flush() - deets, err := suite.connector.RestoreDataCollections(ctx, test.sel, dest, test.col) + deets, err := suite.connector.RestoreDataCollections( + ctx, + suite.acct, + test.sel, + dest, + test.col) require.NoError(t, err) assert.NotNil(t, deets) @@ -308,6 +317,7 @@ func mustGetDefaultDriveID( func runRestoreBackupTest( t *testing.T, + acct account.Account, test restoreBackupInfo, tenant string, users []string, @@ -349,7 +359,12 @@ func runRestoreBackupTest( restoreGC := loadConnector(ctx, t, test.resource) restoreSel := getSelectorWith(test.service) - deets, err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections) + deets, err := restoreGC.RestoreDataCollections( + ctx, + acct, + restoreSel, + dest, + collections) require.NoError(t, err) assert.NotNil(t, deets) @@ -724,7 +739,7 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() { for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - runRestoreBackupTest(t, test, suite.connector.tenant, []string{suite.user}) + runRestoreBackupTest(t, suite.acct, test, suite.connector.tenant, []string{suite.user}) }) } } @@ -833,7 +848,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames ) restoreGC := loadConnector(ctx, t, test.resource) - deets, err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections) + deets, err := restoreGC.RestoreDataCollections(ctx, suite.acct, restoreSel, dest, collections) require.NoError(t, err) require.NotNil(t, deets) @@ -977,7 +992,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiuserRestoreAndBackup() { for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - runRestoreBackupTest(t, test, suite.connector.tenant, users) + runRestoreBackupTest(t, suite.acct, test, suite.connector.tenant, users) }) } } diff --git a/src/internal/operations/backup_integration_test.go b/src/internal/operations/backup_integration_test.go index 17d16f99c..08f242965 100644 --- a/src/internal/operations/backup_integration_test.go +++ b/src/internal/operations/backup_integration_test.go @@ -294,6 +294,7 @@ func generateContainerOfItems( ctx context.Context, gc *connector.GraphConnector, service path.ServiceType, + acct account.Account, cat path.CategoryType, sel selectors.Selector, tenantID, userID, destFldr string, @@ -330,7 +331,7 @@ func generateContainerOfItems( dest, collections) - deets, err := gc.RestoreDataCollections(ctx, sel, dest, dataColls) + deets, err := gc.RestoreDataCollections(ctx, acct, sel, dest, dataColls) require.NoError(t, err) return deets @@ -709,6 +710,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_exchangeIncrementals() { ctx, gc, path.ExchangeService, + acct, category, selectors.NewExchangeRestore(users).Selector, m365.AzureTenantID, suite.user, destName, diff --git a/src/internal/operations/restore.go b/src/internal/operations/restore.go index c532111a3..80c7b986a 100644 --- a/src/internal/operations/restore.go +++ b/src/internal/operations/restore.go @@ -188,7 +188,12 @@ func (op *RestoreOperation) Run(ctx context.Context) (restoreDetails *details.De defer closer() defer close(restoreComplete) - restoreDetails, err = gc.RestoreDataCollections(ctx, op.Selectors, op.Destination, dcs) + restoreDetails, err = gc.RestoreDataCollections( + ctx, + op.account, + op.Selectors, + op.Destination, + dcs) if err != nil { err = errors.Wrap(err, "restoring service data") opStats.writeErr = err