From 7a25a7b70e9283528b4b48da2bf790277eeb2515 Mon Sep 17 00:00:00 2001 From: Danny Date: Wed, 31 Aug 2022 20:02:36 -0400 Subject: [PATCH] GC: Restore Event to M365 user account feature (#711) ## Description logic and orchestration to take byte representation of M365 event, create a Corso calendar, and place the event and calendar into the M365 user's account. ## Type of change Please check the type of change your PR introduces: - [x] :sunflower: Feature ## Issue(s) closes #599 closes #710 Requires PR #708 to ensure test folder is where events are created. ## Test Plan - [x] :zap: Unit test --- src/internal/connector/exchange/event_test.go | 5 ++-- .../exchange/exchange_service_test.go | 24 ++++++++++++++- .../connector/exchange/service_functions.go | 26 ++++++++++++++++ .../mockconnector/mock_data_collection.go | 30 ++++++++++++++----- 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/internal/connector/exchange/event_test.go b/src/internal/connector/exchange/event_test.go index d3e7a97fc..752d72ae7 100644 --- a/src/internal/connector/exchange/event_test.go +++ b/src/internal/connector/exchange/event_test.go @@ -76,9 +76,10 @@ func (suite *EventSuite) TestEventInfo() { bytes := mockconnector.GetMockEventBytes("Test Mock") event, err := support.CreateEventFromBytes(bytes) require.NoError(suite.T(), err) - subject := " Test MockReview + Lunch" + subject := " Test Mock Review + Lunch" organizer := "foobar3@8qzvrj.onmicrosoft.com" - eventTime := time.Date(2022, time.April, 28, 3, 41, 58, 0, time.UTC) + future := time.Now().AddDate(0, 0, 1) + eventTime := time.Date(2022, future.Month(), future.Day(), 6, 0, 0, 0, time.UTC) i := &details.ExchangeInfo{ ItemType: details.ExchangeEvent, Subject: subject, diff --git a/src/internal/connector/exchange/exchange_service_test.go b/src/internal/connector/exchange/exchange_service_test.go index dc3cd3f39..25dc25123 100644 --- a/src/internal/connector/exchange/exchange_service_test.go +++ b/src/internal/connector/exchange/exchange_service_test.go @@ -410,7 +410,7 @@ func (suite *ExchangeServiceSuite) TestGetContainerID() { // the Corso Folder. The function handles test clean-up. func (suite *ExchangeServiceSuite) TestRestoreContact() { t := suite.T() - userID := tester.M365UserID(suite.T()) + userID := tester.M365UserID(t) now := time.Now() folderName := "TestRestoreContact: " + common.FormatSimpleDateTime(now) @@ -430,6 +430,28 @@ func (suite *ExchangeServiceSuite) TestRestoreContact() { assert.NoError(t, err) } +// TestRestoreEvent verifies that event object is able to created +// and sent into the test account of the Corso user in the newly created Corso Calendar +func (suite *ExchangeServiceSuite) TestRestoreEvent() { + t := suite.T() + userID := tester.M365UserID(t) + name := "TestRestoreEvent: " + common.FormatSimpleDateTime(time.Now()) + calendar, err := CreateCalendar(suite.es, userID, name) + require.NoError(t, err) + + calendarID := *calendar.GetId() + err = RestoreExchangeEvent(context.Background(), + mockconnector.GetMockEventBytes("Restore Event "), + suite.es, + control.Copy, + calendarID, + userID) + assert.NoError(t, err) + // Removes calendar containing events created during the test + err = DeleteCalendar(suite.es, userID, *calendar.GetId()) + assert.NoError(t, err) +} + // TestGetRestoreContainer checks the ability to Create a "container" for the // GraphConnector's Restore Workflow based on OptionIdentifier. func (suite *ExchangeServiceSuite) TestGetRestoreContainer() { diff --git a/src/internal/connector/exchange/service_functions.go b/src/internal/connector/exchange/service_functions.go index bb96bf951..e13002f93 100644 --- a/src/internal/connector/exchange/service_functions.go +++ b/src/internal/connector/exchange/service_functions.go @@ -369,6 +369,8 @@ func RestoreExchangeObject( return RestoreMailMessage(ctx, bits, service, control.Copy, destination, user) case contacts: return RestoreExchangeContact(ctx, bits, service, control.Copy, destination, user) + case events: + return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user) default: return fmt.Errorf("type: %s not supported for exchange restore", category) } @@ -404,6 +406,30 @@ func RestoreExchangeContact( return nil } +func RestoreExchangeEvent( + ctx context.Context, + bits []byte, + service graph.Service, + cp control.CollisionPolicy, + destination, user string, +) error { + event, err := support.CreateEventFromBytes(bits) + if err != nil { + return err + } + + response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(event) + if err != nil { + return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) + } + + if response == nil { + return errors.New("msgraph event post fail: REST response not received") + } + + return nil +} + // RestoreMailMessage utility function to place an exchange.Mail // message into the user's M365 Exchange account. // @param bits - byte array representation of exchange.Message from Corso backstore diff --git a/src/internal/connector/mockconnector/mock_data_collection.go b/src/internal/connector/mockconnector/mock_data_collection.go index c9e762fde..8c81e8d73 100644 --- a/src/internal/connector/mockconnector/mock_data_collection.go +++ b/src/internal/connector/mockconnector/mock_data_collection.go @@ -3,6 +3,7 @@ package mockconnector import ( "bytes" "io" + "strings" "time" "github.com/google/uuid" @@ -122,17 +123,32 @@ func GetMockContactBytes(middleName string) []byte { // GetMockEventBytes returns test byte array representative of full Eventable item. func GetMockEventBytes(subject string) []byte { + newTime := time.Now().AddDate(0, 0, 1) + conversion := common.FormatTime(newTime) + timeSlice := strings.Split(conversion, "T") + //nolint:lll - event := "{\"id\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAAAAAG76AAA=\",\"calendar@odata.navigationLink\":\"https://graph.microsoft.com/v1.0/users('foobar@8qzvrj.onmicrosoft.com')/calendars('AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9QmQYWNcI7hCpPrAQDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAAA=')\",\"calendar@odata.associationLink\":\"https://graph.microsoft.com/v1.0/users('foobar@8qzvrj.onmicrosoft.com')/calendars('AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9QmQYWNcI7hCpPrAQDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAAA=')/$ref\"," + - "\"@odata.etag\":\"W/\\\"0hATW1CAfUS+njw3hdxSGAAAJIxNug==\\\"\",\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users('foobar%408qzvrj.onmicrosoft.com')/events/$entity\",\"categories\":[],\"changeKey\":\"0hATW1CAfUS+njw3hdxSGAAAJIxNug==\",\"createdDateTime\":\"2022-03-28T03:42:03Z\",\"lastModifiedDateTime\":\"2022-05-26T19:25:58Z\",\"allowNewTimeProposals\":true,\"attendees\"" + + event := "{\"id\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAAAAAG76AAA=\",\"calendar@odata.navigationLink\":" + + "\"https://graph.microsoft.com/v1.0/users('foobar@8qzvrj.onmicrosoft.com')/calendars('AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9QmQYWNcI7hCpPrAQDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAAA=')\"," + + "\"calendar@odata.associationLink\":\"https://graph.microsoft.com/v1.0/users('foobar@8qzvrj.onmicrosoft.com')/calendars('AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9QmQYWNcI7hCpPrAQDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAAA=')/$ref\"," + + "\"@odata.etag\":\"W/\\\"0hATW1CAfUS+njw3hdxSGAAAJIxNug==\\\"\",\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users('foobar%408qzvrj.onmicrosoft.com')/events/$entity\",\"categories\":[],\"changeKey\":\"0hATW1CAfUS+njw3hdxSGAAAJIxNug==\"," + + "\"createdDateTime\":\"2022-03-28T03:42:03Z\",\"lastModifiedDateTime\":\"2022-05-26T19:25:58Z\",\"allowNewTimeProposals\":true,\"attendees\"" + ":[{\"emailAddress\":{\"address\":\"foobar@8qzvrj.onmicrosoft.com\",\"name\":\"Fuu Gu\"},\"type\":\"required\",\"status\"" + ":{\"response\":\"none\",\"time\":\"0001-01-01T00:00:00Z\"}},{\"emailAddress\":{\"address\":\"foobar1@8qzvrj.onmicrosoft.com\",\"name\":\"Fuu Bar\"},\"type\":\"required\"" + - ",\"status\":{\"response\":\"none\",\"time\":\"0001-01-01T00:00:00Z\"}},{\"emailAddress\":{\"address\":\"foobar2@8qzvrj.onmicrosoft.com\",\"name\":\"Ru Buu\"},\"type\":\"required\",\"status\":{\"response\":\"none\",\"time\":\"0001-01-01T00:00:00Z\"}}],\"body\":{\"content\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n

This meeting is to review the latest Tailspin Toys project proposal.
\\r\\nBut why not eat some sushi while we’re at it? :)

\\r\\n\\r\\n\\r\\n\"" + - ",\"contentType\":\"html\"},\"bodyPreview\":\"This meeting is to review the latest Tailspin Toys project proposal.\\r\\nBut why not eat some sushi while we’re at it? :)\",\"end\":{\"dateTime\":\"2022-04-28T04:41:58.0000000\",\"timeZone\":\"UTC\"},\"hasAttachments\":false,\"hideAttendees\":false,\"iCalUId\":\"040000008200E00074C5B7101A82E0080000000035723BC75542D801000000000000000010000000E1E7C8F785242E4894DA13AEFB947B85\",\"importance\":\"normal\",\"isAllDay\":false,\"isCancelled\":false,\"isDraft\":false,\"isOnlineMeeting\":false,\"isOrganizer\":false,\"isReminderOn\":true," + - "\"location\":{\"displayName\":\"Umi Sake House (2230 1st Ave, Seattle, WA 98121 US)\",\"locationType\":\"default\",\"uniqueId\":\"Umi Sake House (2230 1st Ave, Seattle, WA 98121 US)\",\"uniqueIdType\":\"private\"},\"locations\":[{\"displayName\":\"Umi Sake House (2230 1st Ave, Seattle, WA 98121 US)\",\"locationType\":\"default\",\"uniqueId\":\"\",\"uniqueIdType\":\"unknown\"}],\"onlineMeetingProvider\":\"unknown\",\"organizer\"" + - ":{\"emailAddress\":{\"address\":\"foobar3@8qzvrj.onmicrosoft.com\",\"name\":\"Anu Pierson\"}},\"originalEndTimeZone\":\"UTC\",\"originalStartTimeZone\":\"UTC\",\"reminderMinutesBeforeStart\":15,\"responseRequested\":true,\"responseStatus\":{\"response\":\"notResponded\",\"time\":\"0001-01-01T00:00:00Z\"},\"sensitivity\":\"normal\",\"showAs\":\"tentative\",\"start\":{\"dateTime\":\"2022-04-28T03:41:58.0000000\",\"timeZone\":\"UTC\"}," + + ",\"status\":{\"response\":\"none\",\"time\":\"0001-01-01T00:00:00Z\"}},{\"emailAddress\":{\"address\":\"foobar2@8qzvrj.onmicrosoft.com\",\"name\":\"Ru Buu\"}," + + "\"type\":\"required\",\"status\":{\"response\":\"none\",\"time\":\"0001-01-01T00:00:00Z\"}}],\"body\":{\"content\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n" + + "

This meeting is to review the latest Tailspin Toys project proposal.
\\r\\nBut why not eat some sushi while we’re at it? :)

\\r\\n\\r\\n\\r\\n\"" + + ",\"contentType\":\"html\"},\"bodyPreview\":\"This meeting is to review the latest Tailspin Toys project proposal.\\r\\nBut why not eat some sushi while we’re at it? :)\"," + + "\"end\":{\"dateTime\":\"" + timeSlice[0] + "T07:00:00.0000000\",\"timeZone\":\"UTC\"},\"hasAttachments\":false,\"hideAttendees\":false,\"iCalUId\":" + + "\"040000008200E00074C5B7101A82E0080000000035723BC75542D801000000000000000010000000E1E7C8F785242E4894DA13AEFB947B85\",\"importance\":\"normal\",\"isAllDay\":false,\"isCancelled\":false," + + "\"isDraft\":false,\"isOnlineMeeting\":false,\"isOrganizer\":false,\"isReminderOn\":true," + + "\"location\":{\"displayName\":\"Umi Sake House (2230 1st Ave, Seattle, WA 98121 US)\",\"locationType\":\"default\",\"uniqueId\":\"Umi Sake House (2230 1st Ave, Seattle, WA 98121 US)\"," + + "\"uniqueIdType\":\"private\"},\"locations\":[{\"displayName\":\"Umi Sake House (2230 1st Ave, Seattle, WA 98121 US)\",\"locationType\":\"default\",\"uniqueId\":\"\",\"uniqueIdType\":\"unknown\"}],\"onlineMeetingProvider\":\"unknown\",\"organizer\"" + + ":{\"emailAddress\":{\"address\":\"foobar3@8qzvrj.onmicrosoft.com\",\"name\":\"Anu Pierson\"}},\"originalEndTimeZone\":\"UTC\",\"originalStartTimeZone\":\"UTC\"," + + "\"reminderMinutesBeforeStart\":15,\"responseRequested\":true,\"responseStatus\":{\"response\":\"notResponded\",\"time\":\"0001-01-01T00:00:00Z\"},\"sensitivity\":\"normal\",\"showAs\":\"tentative\"," + + "\"start\":{\"dateTime\":\"" + timeSlice[0] + "T06:00:00.0000000\",\"timeZone\":\"UTC\"}," + "\"subject\":\" " + subject + - "Review + Lunch\",\"type\":\"singleInstance\",\"webLink\":\"https://outlook.office365.com/owa/?itemid=AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAAAAAG76AAA%3D&exvsurl=1&path=/calendar/item\"}" + " Review + Lunch\",\"type\":\"singleInstance\",\"webLink\":\"https://outlook.office365.com/owa/?itemid=AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAAAAAG76AAA%3D&exvsurl=1&path=/calendar/item\"}" return []byte(event) }