From 8ead744e9fca3f14ac5625ad309ea60737957f9d Mon Sep 17 00:00:00 2001 From: Danny Date: Wed, 31 Aug 2022 18:03:54 -0400 Subject: [PATCH] GC: Restore: Create / Delete Calendar Feature (#708) Feature creation: Create/Delete Calendar for aM365 user added to main branch --- .../exchange/exchange_service_test.go | 70 +++++++++---------- .../connector/exchange/service_functions.go | 56 +++++++++------ .../connector/exchange/service_iterators.go | 16 +++-- src/internal/connector/graph_connector.go | 2 +- .../connector/graph_connector_test.go | 15 ++++ 5 files changed, 91 insertions(+), 68 deletions(-) diff --git a/src/internal/connector/exchange/exchange_service_test.go b/src/internal/connector/exchange/exchange_service_test.go index 499155018..dc3cd3f39 100644 --- a/src/internal/connector/exchange/exchange_service_test.go +++ b/src/internal/connector/exchange/exchange_service_test.go @@ -5,12 +5,12 @@ import ( "testing" "time" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/alcionai/corso/internal/common" + "github.com/alcionai/corso/internal/connector/graph" "github.com/alcionai/corso/internal/connector/mockconnector" "github.com/alcionai/corso/internal/tester" "github.com/alcionai/corso/pkg/account" @@ -430,61 +430,57 @@ func (suite *ExchangeServiceSuite) TestRestoreContact() { assert.NoError(t, err) } -// TestEstablishFolder checks the ability to Create a "container" for the +// TestGetRestoreContainer checks the ability to Create a "container" for the // GraphConnector's Restore Workflow based on OptionIdentifier. -func (suite *ExchangeServiceSuite) TestEstablishFolder() { +func (suite *ExchangeServiceSuite) TestGetRestoreContainer() { tests := []struct { - name string - option optionIdentifier - checkError assert.ErrorAssertionFunc + name string + option string + checkError assert.ErrorAssertionFunc + cleanupFunc func(graph.Service, string, string) error }{ { - name: "Establish User Restore Folder", - option: users, - checkError: assert.Error, + name: "Establish User Restore Folder", + option: "users", + checkError: assert.Error, + cleanupFunc: nil, }, { - name: "Establish Event Restore Location", - option: events, - checkError: assert.Error, + name: "Establish Event Restore Location", + option: "events", + checkError: assert.NoError, + cleanupFunc: DeleteCalendar, }, { - name: "Establish Restore Folder for Unknown", - option: unknown, - checkError: assert.Error, + name: "Establish Restore Folder for Unknown", + option: "unknown", + checkError: assert.Error, + cleanupFunc: nil, }, { - name: "Establish Restore folder for Mail", - option: messages, - checkError: assert.NoError, + name: "Establish Restore folder for Mail", + option: "mail", + checkError: assert.NoError, + cleanupFunc: DeleteMailFolder, }, { - name: "Establish Restore folder for Contacts", - option: contacts, - checkError: assert.NoError, + name: "Establish Restore folder for Contacts", + option: "contacts", + checkError: assert.NoError, + cleanupFunc: DeleteContactFolder, }, } - now := time.Now() - folderName := "CorsoEstablishFolder" + common.FormatSimpleDateTime(now) + userID := tester.M365UserID(suite.T()) for _, test := range tests { suite.T().Run(test.name, func(t *testing.T) { - folderID, err := establishFolder(suite.es, folderName, userID, test.option) + containerID, err := GetRestoreContainer(suite.es, userID, test.option) require.True(t, test.checkError(t, err)) - if folderID != "" { - switch test.option { - case messages: - err = DeleteMailFolder(suite.es, userID, folderID) - assert.NoError(t, err) - case contacts: - err = DeleteContactFolder(suite.es, userID, folderID) - assert.NoError(t, err) - default: - assert.NoError(t, - errors.New("unsupported type received folderID: "+test.option.String()), - ) - } + + if test.cleanupFunc != nil { + err = test.cleanupFunc(suite.es, userID, containerID) + assert.NoError(t, err) } }) } diff --git a/src/internal/connector/exchange/service_functions.go b/src/internal/connector/exchange/service_functions.go index 954b4a27c..bb96bf951 100644 --- a/src/internal/connector/exchange/service_functions.go +++ b/src/internal/connector/exchange/service_functions.go @@ -90,6 +90,21 @@ type MailFolder struct { DisplayName string } +// 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(gs graph.Service, user, calendarName string) (models.Calendarable, error) { + requestbody := models.NewCalendar() + requestbody.SetName(&calendarName) + + return gs.Client().UsersById(user).Calendars().Post(requestbody) +} + +// 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(gs graph.Service, user, calendarID string) error { + return gs.Client().UsersById(user).CalendarsById(calendarID).Delete() +} + // CreateContactFolder makes a contact folder with the displayName of folderName. // If successful, returns the created folder object. func CreateContactFolder(gs graph.Service, user, folderName string) (models.ContactFolderable, error) { @@ -176,7 +191,7 @@ func GetContainerID(service graph.Service, containerName, user string, category transform = models.CreateCalendarCollectionResponseFromDiscriminatorValue isCalendar = true default: - return nil, fmt.Errorf("unsupported category %s for GetFolderID()", category) + return nil, fmt.Errorf("unsupported category %s for GetContainerID()", category) } response, err := query(service, user) @@ -197,7 +212,7 @@ func GetContainerID(service graph.Service, containerName, user string, category return nil, err } - callbackFunc := iterateFindFolderID( + callbackFunc := iterateFindContainerID( &targetID, containerName, service.Adapter().GetBaseUrl(), @@ -281,30 +296,18 @@ func SetupExchangeCollectionVars(scope selectors.ExchangeScope) ( return nil, nil, nil, errors.New("exchange scope option not supported") } -// GetRestoreFolder utility function to create +// GetRestoreContainer utility function to create // an unique folder for the restore process // @param category: input from fullPath()[2] // that defines the application the folder is created in. -func GetRestoreFolder( +func GetRestoreContainer( service graph.Service, user, category string, ) (string, error) { - newFolder := fmt.Sprintf("Corso_Restore_%s", common.FormatNow(common.SimpleDateTimeFormat)) + name := fmt.Sprintf("Corso_Restore_%s", common.FormatNow(common.SimpleDateTimeFormat)) + option := categoryToOptionIdentifier(category) - switch category { - case mailCategory, contactsCategory: - return establishFolder(service, newFolder, user, categoryToOptionIdentifier(category)) - default: - return "", fmt.Errorf("%s category not supported", category) - } -} - -func establishFolder( - service graph.Service, - folderName, user string, - optID optionIdentifier, -) (string, error) { - folderID, err := GetContainerID(service, folderName, user, optID) + folderID, err := GetContainerID(service, name, user, option) if err == nil { return *folderID, nil } @@ -313,23 +316,30 @@ func establishFolder( return "", support.WrapAndAppend(user, err, err) } - switch optID { + switch option { case messages: - fold, err := CreateMailFolder(service, user, folderName) + fold, err := CreateMailFolder(service, user, name) if err != nil { return "", support.WrapAndAppend(user, err, err) } return *fold.GetId(), nil case contacts: - fold, err := CreateContactFolder(service, user, folderName) + fold, err := CreateContactFolder(service, user, name) if err != nil { return "", support.WrapAndAppend(user, err, err) } return *fold.GetId(), nil + case events: + calendar, err := CreateCalendar(service, user, name) + if err != nil { + return "", support.WrapAndAppend(user, err, err) + } + + return *calendar.GetId(), nil default: - return "", fmt.Errorf("category: %s not supported for folder creation", optID) + return "", fmt.Errorf("category: %s not supported for folder creation", option) } } diff --git a/src/internal/connector/exchange/service_iterators.go b/src/internal/connector/exchange/service_iterators.go index fd48a8ba7..04305ebe1 100644 --- a/src/internal/connector/exchange/service_iterators.go +++ b/src/internal/connector/exchange/service_iterators.go @@ -299,14 +299,16 @@ func IterateFilterFolderDirectoriesForCollections( } } -// iterateFindFolderID is a utility function that supports finding +// iterateFindContainerID is a utility function that supports finding // M365 folders objects that matches the folderName. Iterator callback function // will work on folderCollection responses whose objects implement // the displayable interface. If folder exists, the function updates the -// folderID memory address that was passed in. -func iterateFindFolderID( - folderID **string, - folderName, errorIdentifier string, +// containerID memory address that was passed in. +// @param containerName is the string representation of the folder, directory or calendar holds +// the underlying M365 objects +func iterateFindContainerID( + containerID **string, + containerName, errorIdentifier string, isCalendar bool, errs error, ) func(any) bool { @@ -334,12 +336,12 @@ func iterateFindFolderID( return true } - if folderName == *folder.GetDisplayName() { + if containerName == *folder.GetDisplayName() { if folder.GetId() == nil { return true // invalid folder } - *folderID = folder.GetId() + *containerID = folder.GetId() return false } diff --git a/src/internal/connector/graph_connector.go b/src/internal/connector/graph_connector.go index ae25e12f3..38fff9e67 100644 --- a/src/internal/connector/graph_connector.go +++ b/src/internal/connector/graph_connector.go @@ -263,7 +263,7 @@ func (gc *GraphConnector) RestoreExchangeDataCollection( if _, ok := pathCounter[directory]; !ok { pathCounter[directory] = true - folderID, errs = exchange.GetRestoreFolder(&gc.graphService, user, category) + folderID, errs = exchange.GetRestoreContainer(&gc.graphService, user, category) if errs != nil { return errs diff --git a/src/internal/connector/graph_connector_test.go b/src/internal/connector/graph_connector_test.go index a1dfc6863..9d3921237 100644 --- a/src/internal/connector/graph_connector_test.go +++ b/src/internal/connector/graph_connector_test.go @@ -321,3 +321,18 @@ func (suite *GraphConnectorIntegrationSuite) TestCreateAndDeleteContactFolder() assert.NoError(suite.T(), err) } } + +// TestCreateAndDeleteCalendar verifies GraphConnector has the ability to create and remove +// exchange.Event.Calendars within the tenant +func (suite *GraphConnectorIntegrationSuite) TestCreateAndDeleteCalendar() { + now := time.Now() + service := suite.connector.Service() + calendarName := "TestCalendar: " + common.FormatSimpleDateTime(now) + calendar, err := exchange.CreateCalendar(service, suite.user, calendarName) + assert.NoError(suite.T(), err) + + if calendar != nil { + err = exchange.DeleteCalendar(service, suite.user, *calendar.GetId()) + assert.NoError(suite.T(), err) + } +}