From 2e3ee15fd4665ef2984b0755bd2f02fa4e1be256 Mon Sep 17 00:00:00 2001 From: Danny Date: Thu, 8 Sep 2022 23:39:52 -0400 Subject: [PATCH] Sent / Receive DateTime Stamp Incorrect (#792) ## Description Single Legacy Policies added to allow the values for sent / receive times to correspond to the original content for `exchange.Mail` objects ## Type of change - [x] :bug: Bugfix ## Issue(s) *closes #645 ## Test Plan - [x] :muscle: Manual Values can be inspected using e2e restore pipelines. --- src/internal/common/time.go | 7 ++++ src/internal/common/time_test.go | 7 ++++ .../exchange/exchange_service_test.go | 27 +++++++++++++++ .../connector/exchange/service_functions.go | 33 +++++++++++++++---- .../connector/graph_connector_test.go | 28 ---------------- .../connector/support/m365Transform.go | 1 + 6 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/internal/common/time.go b/src/internal/common/time.go index 424241293..a5f068ab6 100644 --- a/src/internal/common/time.go +++ b/src/internal/common/time.go @@ -8,6 +8,7 @@ import ( const ( StandardTimeFormat = time.RFC3339Nano SimpleDateTimeFormat = "02-Jan-2006_15:04:05" + LegacyFormat = time.RFC3339 ) // FormatNow produces the current time in UTC using the provided @@ -28,6 +29,12 @@ func FormatSimpleDateTime(t time.Time) string { return t.UTC().Format(SimpleDateTimeFormat) } +// FormatLegacyTime produces standard format for string values +// that are placed in SingleValueExtendedProperty tags +func FormatLegacyTime(t time.Time) string { + return t.UTC().Format(LegacyFormat) +} + // ParseTime makes a best attempt to produce a time value from // the provided string. Always returns a UTC timezone value. func ParseTime(s string) (time.Time, error) { diff --git a/src/internal/common/time_test.go b/src/internal/common/time_test.go index 2f828b1dd..dd0c5c758 100644 --- a/src/internal/common/time_test.go +++ b/src/internal/common/time_test.go @@ -26,6 +26,13 @@ func (suite *CommonTimeUnitSuite) TestFormatTime() { assert.Equal(t, now.UTC().Format(time.RFC3339Nano), result) } +func (suite *CommonTimeUnitSuite) TestLegacyTime() { + t := suite.T() + now := time.Now() + result := common.FormatLegacyTime(now) + assert.Equal(t, now.UTC().Format(time.RFC3339), result) +} + func (suite *CommonTimeUnitSuite) TestParseTime() { t := suite.T() now := time.Now() diff --git a/src/internal/connector/exchange/exchange_service_test.go b/src/internal/connector/exchange/exchange_service_test.go index 485851ef9..4a5b07503 100644 --- a/src/internal/connector/exchange/exchange_service_test.go +++ b/src/internal/connector/exchange/exchange_service_test.go @@ -407,6 +407,33 @@ func (suite *ExchangeServiceSuite) TestGetContainerID() { } } +// Restore Functions +// TestRestoreMessages uses mock data to ensure GraphConnector +// is able to restore a several messageable item to a Mailbox. +// The result should be all successful items restored within the same folder. +func (suite *ExchangeServiceSuite) TestRestoreMessages() { + t := suite.T() + userID := tester.M365UserID(t) + now := time.Now() + + folderName := "TestRestoreMessage: " + common.FormatSimpleDateTime(now) + folder, err := CreateMailFolder(suite.es, userID, folderName) + require.NoError(t, err) + + folderID := *folder.GetId() + + err = RestoreMailMessage(context.Background(), + mockconnector.GetMockMessageBytes("Exchange Service Mail Test"), + suite.es, + control.Copy, + folderID, + userID, + ) + require.NoError(t, err) + err = DeleteMailFolder(suite.es, userID, folderID) + assert.NoError(t, err) +} + // TestRestoreContact ensures contact object can be created, placed into // the Corso Folder. The function handles test clean-up. func (suite *ExchangeServiceSuite) TestRestoreContact() { diff --git a/src/internal/connector/exchange/service_functions.go b/src/internal/connector/exchange/service_functions.go index 867099a35..eb390fe35 100644 --- a/src/internal/connector/exchange/service_functions.go +++ b/src/internal/connector/exchange/service_functions.go @@ -448,14 +448,29 @@ func RestoreMailMessage( clone := support.ToMessage(originalMessage) valueID := RestorePropertyTag enableValue := RestoreCanonicalEnableValue - sv := models.NewSingleValueLegacyExtendedProperty() - sv.SetId(&valueID) - sv.SetValue(&enableValue) - svlep := []models.SingleValueLegacyExtendedPropertyable{sv} - clone.SetSingleValueExtendedProperties(svlep) - draft := false - clone.SetIsDraft(&draft) + // Set Extended Properties: + // 1st: No transmission + // 2nd: Send Date + // 3rd: Recv Date + sv1 := models.NewSingleValueLegacyExtendedProperty() + sv1.SetId(&valueID) + sv1.SetValue(&enableValue) + + sv2 := models.NewSingleValueLegacyExtendedProperty() + sendPropertyValue := common.FormatLegacyTime(*clone.GetSentDateTime()) + sendPropertyTag := "SystemTime 0x0039" + sv2.SetId(&sendPropertyTag) + sv2.SetValue(&sendPropertyValue) + + sv3 := models.NewSingleValueLegacyExtendedProperty() + recvPropertyValue := common.FormatLegacyTime(*clone.GetReceivedDateTime()) + recvPropertyTag := "SystemTime 0x0E06" + sv3.SetId(&recvPropertyTag) + sv3.SetValue(&recvPropertyValue) + + svlep := []models.SingleValueLegacyExtendedPropertyable{sv1, sv2, sv3} + clone.SetSingleValueExtendedProperties(svlep) // Switch workflow based on collision policy switch cp { @@ -482,5 +497,9 @@ func SendMailToBackStore(service graph.Service, user, destination string, messag return errors.New("message not Sent: blocked by server") } + if err != nil { + return support.WrapAndAppend(": "+support.ConnectorStackErrorTrace(err), err, nil) + } + return nil } diff --git a/src/internal/connector/graph_connector_test.go b/src/internal/connector/graph_connector_test.go index 706c905d8..85e81c1be 100644 --- a/src/internal/connector/graph_connector_test.go +++ b/src/internal/connector/graph_connector_test.go @@ -14,10 +14,8 @@ import ( "github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/connector/exchange" - "github.com/alcionai/corso/src/internal/connector/mockconnector" "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/data" - "github.com/alcionai/corso/src/internal/path" "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/pkg/selectors" ) @@ -240,32 +238,6 @@ func (suite *GraphConnectorIntegrationSuite) TestEventsSerializationRegression() suite.Equal(status.ObjectCount, status.Successful) } -// Restore Functions -// TestRestoreMessages uses mock data to ensure GraphConnector -// is able to restore a several messageable item to a Mailbox. -// The result should be all successful items restored within the same folder. -func (suite *GraphConnectorIntegrationSuite) TestRestoreMessages() { - t := suite.T() - category := path.EmailCategory - connector := loadConnector(t) - collection := make([]data.Collection, 0) - - for i := 0; i < 3; i++ { - mdc := mockconnector.NewMockExchangeCollection( - []string{"tenant", path.ExchangeService.String(), suite.user, category.String(), "Inbox"}, - 1) - collection = append(collection, mdc) - } - - err := connector.RestoreExchangeDataCollection(context.Background(), collection) - assert.NoError(suite.T(), err) - - status := connector.AwaitStatus() - assert.NotNil(t, status) - assert.Equal(t, status.ObjectCount, status.Successful) - assert.Equal(t, status.FolderCount, 1) -} - // TestAccessOfInboxAllUsers verifies that GraphConnector can // support `--all-users` for backup operations. Selector.DiscreteScopes // returns all of the users within one scope. Only users who have diff --git a/src/internal/connector/support/m365Transform.go b/src/internal/connector/support/m365Transform.go index 2bdf51e2e..6c613ecde 100644 --- a/src/internal/connector/support/m365Transform.go +++ b/src/internal/connector/support/m365Transform.go @@ -38,6 +38,7 @@ func CloneMessageableFields(orig, message models.Messageable) models.Messageable message.SetInternetMessageId(orig.GetInternetMessageId()) message.SetInternetMessageHeaders(orig.GetInternetMessageHeaders()) message.SetIsDeliveryReceiptRequested(orig.GetIsDeliveryReceiptRequested()) + message.SetIsDraft(orig.GetIsDraft()) message.SetIsRead(orig.GetIsRead()) message.SetIsReadReceiptRequested(orig.GetIsReadReceiptRequested()) message.SetParentFolderId(orig.GetParentFolderId())