diff --git a/src/internal/connector/graph_connector_helper_test.go b/src/internal/connector/graph_connector_helper_test.go index 73083e837..51c78dd72 100644 --- a/src/internal/connector/graph_connector_helper_test.go +++ b/src/internal/connector/graph_connector_helper_test.go @@ -50,7 +50,16 @@ func mustToDataLayerPath( return res } -func emptyOrEqual[T any](t *testing.T, expected *T, got *T, msg string) { +func emptyOrEqual[T any](expected *T, got *T) bool { + if expected == nil || got == nil { + // Creates either the zero value or gets the value pointed to. + return reflect.DeepEqual(reflect.ValueOf(expected).Elem(), reflect.ValueOf(got).Elem()) + } + + return reflect.DeepEqual(*expected, *got) +} + +func testEmptyOrEqual[T any](t *testing.T, expected *T, got *T, msg string) { t.Helper() if expected == nil || got == nil { @@ -90,7 +99,7 @@ func checkMessage( ) { assert.Equal(t, expected.GetBccRecipients(), got.GetBccRecipients(), "BccRecipients") - emptyOrEqual(t, expected.GetBody().GetContentType(), got.GetBody().GetContentType(), "Body.ContentType") + testEmptyOrEqual(t, expected.GetBody().GetContentType(), got.GetBody().GetContentType(), "Body.ContentType") // Skip Body.Content as there may be display formatting that changes. @@ -114,44 +123,44 @@ func checkMessage( assert.Equal(t, expected.GetFrom(), got.GetFrom(), "From") - emptyOrEqual(t, expected.GetHasAttachments(), got.GetHasAttachments(), "HasAttachments") + testEmptyOrEqual(t, expected.GetHasAttachments(), got.GetHasAttachments(), "HasAttachments") // Skip Id as it's tied to this specific instance of the item. - emptyOrEqual(t, expected.GetImportance(), got.GetImportance(), "Importance") + testEmptyOrEqual(t, expected.GetImportance(), got.GetImportance(), "Importance") - emptyOrEqual(t, expected.GetInferenceClassification(), got.GetInferenceClassification(), "InferenceClassification") + testEmptyOrEqual(t, expected.GetInferenceClassification(), got.GetInferenceClassification(), "InferenceClassification") assert.Equal(t, expected.GetInternetMessageHeaders(), got.GetInternetMessageHeaders(), "InternetMessageHeaders") - emptyOrEqual(t, expected.GetInternetMessageId(), got.GetInternetMessageId(), "InternetMessageId") + testEmptyOrEqual(t, expected.GetInternetMessageId(), got.GetInternetMessageId(), "InternetMessageId") - emptyOrEqual( + testEmptyOrEqual( t, expected.GetIsDeliveryReceiptRequested(), got.GetIsDeliveryReceiptRequested(), "IsDeliverReceiptRequested", ) - emptyOrEqual(t, expected.GetIsDraft(), got.GetIsDraft(), "IsDraft") + testEmptyOrEqual(t, expected.GetIsDraft(), got.GetIsDraft(), "IsDraft") - emptyOrEqual(t, expected.GetIsRead(), got.GetIsRead(), "IsRead") + testEmptyOrEqual(t, expected.GetIsRead(), got.GetIsRead(), "IsRead") - emptyOrEqual(t, expected.GetIsReadReceiptRequested(), got.GetIsReadReceiptRequested(), "IsReadReceiptRequested") + testEmptyOrEqual(t, expected.GetIsReadReceiptRequested(), got.GetIsReadReceiptRequested(), "IsReadReceiptRequested") // Skip LastModifiedDateTime as it's tied to this specific instance of the item. // Skip ParentFolderId as we restore to a different folder by default. - emptyOrEqual(t, expected.GetReceivedDateTime(), got.GetReceivedDateTime(), "ReceivedDateTime") + testEmptyOrEqual(t, expected.GetReceivedDateTime(), got.GetReceivedDateTime(), "ReceivedDateTime") assert.Equal(t, expected.GetReplyTo(), got.GetReplyTo(), "ReplyTo") assert.Equal(t, expected.GetSender(), got.GetSender(), "Sender") - emptyOrEqual(t, expected.GetSentDateTime(), got.GetSentDateTime(), "SentDateTime") + testEmptyOrEqual(t, expected.GetSentDateTime(), got.GetSentDateTime(), "SentDateTime") - emptyOrEqual(t, expected.GetSubject(), got.GetSubject(), "Subject") + testEmptyOrEqual(t, expected.GetSubject(), got.GetSubject(), "Subject") assert.Equal(t, expected.GetToRecipients(), got.GetToRecipients(), "ToRecipients") @@ -165,13 +174,13 @@ func checkContact( expected models.Contactable, got models.Contactable, ) { - emptyOrEqual(t, expected.GetAssistantName(), got.GetAssistantName(), "AssistantName") + testEmptyOrEqual(t, expected.GetAssistantName(), got.GetAssistantName(), "AssistantName") - emptyOrEqual(t, expected.GetBirthday(), got.GetBirthday(), "Birthday") + testEmptyOrEqual(t, expected.GetBirthday(), got.GetBirthday(), "Birthday") assert.Equal(t, expected.GetBusinessAddress(), got.GetBusinessAddress()) - emptyOrEqual(t, expected.GetBusinessHomePage(), got.GetBusinessHomePage(), "BusinessHomePage") + testEmptyOrEqual(t, expected.GetBusinessHomePage(), got.GetBusinessHomePage(), "BusinessHomePage") assert.Equal(t, expected.GetBusinessPhones(), got.GetBusinessPhones()) @@ -181,21 +190,21 @@ func checkContact( assert.Equal(t, expected.GetChildren(), got.GetChildren()) - emptyOrEqual(t, expected.GetCompanyName(), got.GetCompanyName(), "CompanyName") + testEmptyOrEqual(t, expected.GetCompanyName(), got.GetCompanyName(), "CompanyName") // Skip CreatedDateTime as it's tied to this specific instance of the item. - emptyOrEqual(t, expected.GetDepartment(), got.GetDepartment(), "Department") + testEmptyOrEqual(t, expected.GetDepartment(), got.GetDepartment(), "Department") - emptyOrEqual(t, expected.GetDisplayName(), got.GetDisplayName(), "DisplayName") + testEmptyOrEqual(t, expected.GetDisplayName(), got.GetDisplayName(), "DisplayName") assert.Equal(t, expected.GetEmailAddresses(), got.GetEmailAddresses()) - emptyOrEqual(t, expected.GetFileAs(), got.GetFileAs(), "FileAs") + testEmptyOrEqual(t, expected.GetFileAs(), got.GetFileAs(), "FileAs") - emptyOrEqual(t, expected.GetGeneration(), got.GetGeneration(), "Generation") + testEmptyOrEqual(t, expected.GetGeneration(), got.GetGeneration(), "Generation") - emptyOrEqual(t, expected.GetGivenName(), got.GetGivenName(), "GivenName") + testEmptyOrEqual(t, expected.GetGivenName(), got.GetGivenName(), "GivenName") assert.Equal(t, expected.GetHomeAddress(), got.GetHomeAddress()) @@ -205,43 +214,252 @@ func checkContact( assert.Equal(t, expected.GetImAddresses(), got.GetImAddresses()) - emptyOrEqual(t, expected.GetInitials(), got.GetInitials(), "Initials") + testEmptyOrEqual(t, expected.GetInitials(), got.GetInitials(), "Initials") - emptyOrEqual(t, expected.GetJobTitle(), got.GetJobTitle(), "JobTitle") + testEmptyOrEqual(t, expected.GetJobTitle(), got.GetJobTitle(), "JobTitle") // Skip CreatedDateTime as it's tied to this specific instance of the item. - emptyOrEqual(t, expected.GetManager(), got.GetManager(), "Manager") + testEmptyOrEqual(t, expected.GetManager(), got.GetManager(), "Manager") - emptyOrEqual(t, expected.GetMiddleName(), got.GetMiddleName(), "MiddleName") + testEmptyOrEqual(t, expected.GetMiddleName(), got.GetMiddleName(), "MiddleName") - emptyOrEqual(t, expected.GetMobilePhone(), got.GetMobilePhone(), "MobilePhone") + testEmptyOrEqual(t, expected.GetMobilePhone(), got.GetMobilePhone(), "MobilePhone") - emptyOrEqual(t, expected.GetNickName(), got.GetNickName(), "NickName") + testEmptyOrEqual(t, expected.GetNickName(), got.GetNickName(), "NickName") - emptyOrEqual(t, expected.GetOfficeLocation(), got.GetOfficeLocation(), "OfficeLocation") + testEmptyOrEqual(t, expected.GetOfficeLocation(), got.GetOfficeLocation(), "OfficeLocation") assert.Equal(t, expected.GetOtherAddress(), got.GetOtherAddress()) // Skip ParentFolderId as it's tied to this specific instance of the item. - emptyOrEqual(t, expected.GetPersonalNotes(), got.GetPersonalNotes(), "PersonalNotes") + testEmptyOrEqual(t, expected.GetPersonalNotes(), got.GetPersonalNotes(), "PersonalNotes") assert.Equal(t, expected.GetPhoto(), got.GetPhoto()) - emptyOrEqual(t, expected.GetProfession(), got.GetProfession(), "Profession") + testEmptyOrEqual(t, expected.GetProfession(), got.GetProfession(), "Profession") - emptyOrEqual(t, expected.GetSpouseName(), got.GetSpouseName(), "SpouseName") + testEmptyOrEqual(t, expected.GetSpouseName(), got.GetSpouseName(), "SpouseName") - emptyOrEqual(t, expected.GetSurname(), got.GetSurname(), "Surname") + testEmptyOrEqual(t, expected.GetSurname(), got.GetSurname(), "Surname") - emptyOrEqual(t, expected.GetTitle(), got.GetTitle(), "Title") + testEmptyOrEqual(t, expected.GetTitle(), got.GetTitle(), "Title") - emptyOrEqual(t, expected.GetYomiCompanyName(), got.GetYomiCompanyName(), "YomiCompanyName") + testEmptyOrEqual(t, expected.GetYomiCompanyName(), got.GetYomiCompanyName(), "YomiCompanyName") - emptyOrEqual(t, expected.GetYomiGivenName(), got.GetYomiGivenName(), "YomiGivenName") + testEmptyOrEqual(t, expected.GetYomiGivenName(), got.GetYomiGivenName(), "YomiGivenName") - emptyOrEqual(t, expected.GetYomiSurname(), got.GetYomiSurname(), "YomiSurname") + testEmptyOrEqual(t, expected.GetYomiSurname(), got.GetYomiSurname(), "YomiSurname") +} + +func checkLocations( + t *testing.T, + expected []models.Locationable, + got []models.Locationable, +) { + pending := make([]*models.Locationable, len(expected)) + for i := 0; i < len(expected); i++ { + pending[i] = &expected[i] + } + + unexpected := []models.Locationable{} + + for i := 0; i < len(got); i++ { + found := false + + for j, maybe := range pending { + if maybe == nil { + // Already matched with something in got. + continue + } + + // Item matched, break out of inner loop and move to next item in got. + if locationEqual(*maybe, got[i]) { + pending[j] = nil + found = true + + break + } + } + + if !found { + unexpected = append(unexpected, got[i]) + } + } + + // Print differences. + missing := []models.Locationable{} + + for _, p := range pending { + if p == nil { + continue + } + + missing = append(missing, *p) + } + + if len(unexpected) == 0 && len(missing) == 0 { + return + } + + assert.Failf( + t, + "contain different elements", + "missing items: (%T)%v\nunexpected items: (%T)%v\n", + expected, + missing, + got, + unexpected, + ) +} + +func locationEqual(expected, got models.Locationable) bool { + if !reflect.DeepEqual(expected.GetAddress(), got.GetAddress()) { + return false + } + + if !reflect.DeepEqual(expected.GetCoordinates(), got.GetCoordinates()) { + return false + } + + if !emptyOrEqual(expected.GetDisplayName(), got.GetDisplayName()) { + return false + } + + if !emptyOrEqual(expected.GetLocationEmailAddress(), got.GetLocationEmailAddress()) { + return false + } + + if !emptyOrEqual(expected.GetLocationType(), got.GetLocationType()) { + return false + } + + // Skip checking UniqueId as it's marked as for internal use only. + + // Skip checking UniqueIdType as it's marked as for internal use only. + + if !emptyOrEqual(expected.GetLocationUri(), got.GetLocationUri()) { + return false + } + + return true +} + +func checkEvent( + t *testing.T, + expected models.Eventable, + got models.Eventable, +) { + testEmptyOrEqual(t, expected.GetAllowNewTimeProposals(), got.GetAllowNewTimeProposals(), "AllowNewTimeProposals") + + assert.Equal(t, expected.GetAttachments(), got.GetAttachments(), "Attachments") + + assert.Equal(t, expected.GetAttendees(), got.GetAttendees(), "Attendees") + + testEmptyOrEqual(t, expected.GetBody().GetContentType(), got.GetBody().GetContentType(), "Body.ContentType") + + // Skip checking Body.Content for now as M365 may have different formatting. + + // Skip checking BodyPreview for now as M365 may have different formatting. + + assert.Equal(t, expected.GetCalendar(), got.GetCalendar(), "Calendar") + + assert.Equal(t, expected.GetCategories(), got.GetCategories(), "Categories") + + // Skip ChangeKey as it's tied to this specific instance of the item. + + // Skip CreatedDateTime as it's tied to this specific instance of the item. + + assert.Equal(t, expected.GetEnd(), got.GetEnd(), "End") + + testEmptyOrEqual(t, expected.GetHasAttachments(), got.GetHasAttachments(), "HasAttachments") + + testEmptyOrEqual(t, expected.GetHideAttendees(), got.GetHideAttendees(), "HideAttendees") + + // TODO(ashmrtn): Uncomment when we figure out how to connect to the original + // event. + // testEmptyOrEqual(t, expected.GetICalUId(), got.GetICalUId(), "ICalUId") + + // Skip Id as it's tied to this specific instance of the item. + + testEmptyOrEqual(t, expected.GetImportance(), got.GetImportance(), "Importance") + + assert.Equal(t, expected.GetInstances(), got.GetInstances(), "Instances") + + testEmptyOrEqual(t, expected.GetIsAllDay(), got.GetIsAllDay(), "IsAllDay") + + testEmptyOrEqual(t, expected.GetIsCancelled(), got.GetIsCancelled(), "IsCancelled") + + testEmptyOrEqual(t, expected.GetIsDraft(), got.GetIsDraft(), "IsDraft") + + testEmptyOrEqual(t, expected.GetIsOnlineMeeting(), got.GetIsOnlineMeeting(), "IsOnlineMeeting") + + // TODO(ashmrtn): Uncomment when we figure out how to delegate event creation + // to another user. + // testEmptyOrEqual(t, expected.GetIsOrganizer(), got.GetIsOrganizer(), "IsOrganizer") + + testEmptyOrEqual(t, expected.GetIsReminderOn(), got.GetIsReminderOn(), "IsReminderOn") + + // Skip LastModifiedDateTime as it's tied to this specific instance of the item. + + // Cheating a little here in the name of code-reuse. model.Location needs + // custom compare logic because it has fields marked as "internal use only" + // that seem to change. + checkLocations( + t, + []models.Locationable{expected.GetLocation()}, + []models.Locationable{got.GetLocation()}, + ) + + checkLocations(t, expected.GetLocations(), got.GetLocations()) + + assert.Equal(t, expected.GetOnlineMeeting(), got.GetOnlineMeeting(), "OnlineMeeting") + + testEmptyOrEqual(t, expected.GetOnlineMeetingProvider(), got.GetOnlineMeetingProvider(), "OnlineMeetingProvider") + + testEmptyOrEqual(t, expected.GetOnlineMeetingUrl(), got.GetOnlineMeetingUrl(), "OnlineMeetingUrl") + + // TODO(ashmrtn): Uncomment when we figure out how to delegate event creation + // to another user. + // assert.Equal(t, expected.GetOrganizer(), got.GetOrganizer(), "Organizer") + + testEmptyOrEqual(t, expected.GetOriginalEndTimeZone(), got.GetOriginalEndTimeZone(), "OriginalEndTimeZone") + + testEmptyOrEqual(t, expected.GetOriginalStart(), got.GetOriginalStart(), "OriginalStart") + + testEmptyOrEqual(t, expected.GetOriginalStartTimeZone(), got.GetOriginalStartTimeZone(), "OriginalStartTimeZone") + + assert.Equal(t, expected.GetRecurrence(), got.GetRecurrence(), "Recurrence") + + testEmptyOrEqual( + t, + expected.GetReminderMinutesBeforeStart(), + got.GetReminderMinutesBeforeStart(), + "ReminderMinutesBeforeStart", + ) + + testEmptyOrEqual(t, expected.GetResponseRequested(), got.GetResponseRequested(), "ResponseRequested") + + // TODO(ashmrtn): Uncomment when we figure out how to connect to the original + // event. + // assert.Equal(t, expected.GetResponseStatus(), got.GetResponseStatus(), "ResponseStatus") + + testEmptyOrEqual(t, expected.GetSensitivity(), got.GetSensitivity(), "Sensitivity") + + testEmptyOrEqual(t, expected.GetSeriesMasterId(), got.GetSeriesMasterId(), "SeriesMasterId") + + testEmptyOrEqual(t, expected.GetShowAs(), got.GetShowAs(), "ShowAs") + + assert.Equal(t, expected.GetStart(), got.GetStart(), "Start") + + testEmptyOrEqual(t, expected.GetSubject(), got.GetSubject(), "Subject") + + testEmptyOrEqual(t, expected.GetTransactionId(), got.GetTransactionId(), "TransactionId") + + // Skip LastModifiedDateTime as it's tied to this specific instance of the item. + + testEmptyOrEqual(t, expected.GetType(), got.GetType(), "Type") } func compareExchangeEmail( @@ -296,6 +514,32 @@ func compareExchangeContact( checkContact(t, expectedContact, itemContact) } +func compareExchangeEvent( + t *testing.T, + expected map[string][]byte, + item data.Stream, +) { + itemData, err := io.ReadAll(item.ToReader()) + if !assert.NoError(t, err, "reading collection item: %s", item.UUID()) { + return + } + + itemEvent, err := support.CreateEventFromBytes(itemData) + if !assert.NoError(t, err, "deserializing backed up contact") { + return + } + + expectedBytes, ok := expected[*itemEvent.GetSubject()] + if !assert.True(t, ok, "unexpected item with subject %q", *itemEvent.GetSubject()) { + return + } + + expectedEvent, err := support.CreateEventFromBytes(expectedBytes) + assert.NoError(t, err, "deserializing source contact") + + checkEvent(t, expectedEvent, itemEvent) +} + func compareItem( t *testing.T, expected map[string][]byte, @@ -310,6 +554,8 @@ func compareItem( compareExchangeEmail(t, expected, item) case path.ContactsCategory: compareExchangeContact(t, expected, item) + case path.EventsCategory: + compareExchangeEvent(t, expected, item) default: assert.FailNowf(t, "unexpected Exchange category: %s", category.String()) } diff --git a/src/internal/connector/graph_connector_test.go b/src/internal/connector/graph_connector_test.go index 8bece711d..2845be9af 100644 --- a/src/internal/connector/graph_connector_test.go +++ b/src/internal/connector/graph_connector_test.go @@ -569,6 +569,100 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() { []string{dest.ContainerName}, )) + return backupSel.Selector + }, + }, + { + name: "MultipleEventsSingleCalendar", + service: path.ExchangeService, + expectedRestoreFolders: 1, + collections: []colInfo{ + { + pathElements: []string{"Work"}, + category: path.EventsCategory, + items: []itemInfo{ + { + name: "someencodeditemID", + data: mockconnector.GetMockEventWithSubjectBytes("Ghimley"), + lookupKey: "Ghimley", + }, + { + name: "someencodeditemID2", + data: mockconnector.GetMockEventWithSubjectBytes("Irgot"), + lookupKey: "Irgot", + }, + { + name: "someencodeditemID3", + data: mockconnector.GetMockEventWithSubjectBytes("Jannes"), + lookupKey: "Jannes", + }, + }, + }, + }, + // TODO(ashmrtn): Generalize this once we know the path transforms that + // occur during restore. + backupSelFunc: func(dest control.RestoreDestination, backupUser string) selectors.Selector { + backupSel := selectors.NewExchangeBackup() + backupSel.Include(backupSel.EventCalendars( + []string{backupUser}, + []string{dest.ContainerName}, + )) + + return backupSel.Selector + }, + }, + { + name: "MultipleEventsMultipleCalendars", + service: path.ExchangeService, + expectedRestoreFolders: 2, + collections: []colInfo{ + { + pathElements: []string{"Work"}, + category: path.EventsCategory, + items: []itemInfo{ + { + name: "someencodeditemID", + data: mockconnector.GetMockEventWithSubjectBytes("Ghimley"), + lookupKey: "Ghimley", + }, + { + name: "someencodeditemID2", + data: mockconnector.GetMockEventWithSubjectBytes("Irgot"), + lookupKey: "Irgot", + }, + { + name: "someencodeditemID3", + data: mockconnector.GetMockEventWithSubjectBytes("Jannes"), + lookupKey: "Jannes", + }, + }, + }, + { + pathElements: []string{"Personal"}, + category: path.EventsCategory, + items: []itemInfo{ + { + name: "someencodeditemID4", + data: mockconnector.GetMockEventWithSubjectBytes("Argon"), + lookupKey: "Argon", + }, + { + name: "someencodeditemID5", + data: mockconnector.GetMockEventWithSubjectBytes("Bernard"), + lookupKey: "Bernard", + }, + }, + }, + }, + // TODO(ashmrtn): Generalize this once we know the path transforms that + // occur during restore. + backupSelFunc: func(dest control.RestoreDestination, backupUser string) selectors.Selector { + backupSel := selectors.NewExchangeBackup() + backupSel.Include(backupSel.EventCalendars( + []string{backupUser}, + []string{dest.ContainerName}, + )) + return backupSel.Selector }, }, @@ -619,7 +713,9 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() { checkCollections(t, totalItems, expectedData, dcs) status = backupGC.AwaitStatus() - assert.Equal(t, test.expectedRestoreFolders, status.FolderCount, "status.FolderCount") + // TODO(ashmrtn): This will need to change when the restore layout is + // updated. + assert.Equal(t, 1, status.FolderCount, "status.FolderCount") assert.Equal(t, totalItems, status.ObjectCount, "status.ObjectCount") assert.Equal(t, totalItems, status.Successful, "status.Successful") }) diff --git a/src/internal/connector/mockconnector/mock_data_collection.go b/src/internal/connector/mockconnector/mock_data_collection.go index d04a2f532..f03d6428b 100644 --- a/src/internal/connector/mockconnector/mock_data_collection.go +++ b/src/internal/connector/mockconnector/mock_data_collection.go @@ -4,13 +4,10 @@ import ( "bytes" "io" "math/rand" - "strconv" - "strings" "time" "github.com/google/uuid" - "github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/path" @@ -144,96 +141,6 @@ func (med *MockExchangeData) Size() int64 { return med.size } -// GetMockContactBytes returns bytes for Contactable item. -// When hydrated: contact.GetGivenName() shows differences -func GetMockContactBytes(middleName string) []byte { - phone := generatePhoneNumber() - //nolint:lll - contact := "{\"id\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAEOAADSEBNbUIB9RL6ePDeF3FIYAABS7DZnAAA=\",\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users('foobar%408qzvrj.onmicrosoft.com')/contacts/$entity\"," + - "\"@odata.etag\":\"W/\\\"EQAAABYAAADSEBNbUIB9RL6ePDeF3FIYAABSx4Tr\\\"\",\"categories\":[],\"changeKey\":\"EQAAABYAAADSEBNbUIB9RL6ePDeF3FIYAABSx4Tr\",\"createdDateTime\":\"2019-08-04T06:55:33Z\",\"lastModifiedDateTime\":\"2019-08-04T06:55:33Z\",\"businessAddress\":{},\"businessPhones\":[],\"children\":[]," + - "\"displayName\":\"Santiago Quail\",\"emailAddresses\":[],\"fileAs\":\"Quail, Santiago\",\"mobilePhone\": \"" + phone + "\"," + - "\"givenName\":\"Santiago\",\"homeAddress\":{},\"homePhones\":[],\"imAddresses\":[],\"otherAddress\":{},\"parentFolderId\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9FIYAAAAAAEOAAA=\",\"personalNotes\":\"\",\"middleName\":\"" + middleName + "\",\"surname\":\"Quail\"}" - - return []byte(contact) -} - -// generatePhoneNumber creates a random phone number -// @return string representation in format (xxx)xxx-xxxx -func generatePhoneNumber() string { - numbers := make([]string, 0) - - for i := 0; i < 10; i++ { - temp := rand.Intn(10) - value := strconv.Itoa(temp) - numbers = append(numbers, value) - } - - area := strings.Join(numbers[:3], "") - prefix := strings.Join(numbers[3:6], "") - suffix := strings.Join(numbers[6:], "") - phoneNo := "(" + area + ")" + prefix + "-" + suffix - - return phoneNo -} - -// 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\"" + - ":[],\"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\"}" - - return []byte(event) -} - -func GetMockEventWithAttendeesBytes(subject string) []byte { - newTime := time.Now().AddDate(0, 0, 1) - conversion := common.FormatTime(newTime) - timeSlice := strings.Split(conversion, "T") - - //nolint:lll - event := "{\"id\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAABU_FdvAAA=\",\"@odata.etag\":\"W/\\\"0hATW1CAfUS+njw3hdxSGAAAVK7j9A==\\\"\"," + - "\"calendar@odata.associationLink\":\"https://graph.microsoft.com/v1.0/users('a4a472f8-ccb0-43ec-bf52-3697a91b926c')/calendars('AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9QmQYWNcI7hCpPrAQDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAAA=')/$ref\"," + - "\"calendar@odata.navigationLink\":\"https://graph.microsoft.com/v1.0/users('a4a472f8-ccb0-43ec-bf52-3697a91b926c')/calendars('AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9QmQYWNcI7hCpPrAQDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAAA=')\"," + - "\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users('a4a472f8-ccb0-43ec-bf52-3697a91b926c')/events/$entity\",\"categories\":[],\"changeKey\":\"0hATW1CAfUS+njw3hdxSGAAAVK7j9A==\",\"createdDateTime\":\"2022-08-06T12:47:56Z\",\"lastModifiedDateTime\":\"2022-08-06T12:49:59Z\",\"allowNewTimeProposals\":true," + - "\"attendees\":[{\"emailAddress\":{\"address\":\"george.martinez@8qzvrj.onmicrosoft.com\",\"name\":\"George Martinez\"},\"type\":\"required\",\"status\":{\"response\":\"none\",\"time\":\"0001-01-01T00:00:00Z\"}},{\"emailAddress\":{\"address\":\"LeeG@8qzvrj.onmicrosoft.com\",\"name\":\"Lee Gu\"},\"type\":\"required\",\"status\":{\"response\":\"none\",\"time\":\"0001-01-01T00:00:00Z\"}}]," + - "\"body\":{\"content\":\"\\n\\n\\n\\n\\n
\\nDiscuss matters concerning stock options and early release of quarterly earnings.
\\n
" + - "\\n
________________________________________________________________________________\\n
\\n
" + - "\\n
Microsoft Teams meeting\\n
\\n
\\n
Join on your computer or mobile app" + - "\\n
\\nClick\\n here to join the meeting
" + - "\\n
\\n
Meeting ID:\\n292 784 521 247
\\nPasscode: SzBkfK\\n" + - "\\n
Download\\n Teams | " + - "\\nJoin on the web
\\n
\\n
\\n
Learn more" + - "\\n | " + - "\\nMeeting options
\\n
\\n
\\n
\\n
\\n
\\n
________________________________________________________________________________" + - "\\n
\\n\\n\\n\",\"contentType\":\"html\"},\"bodyPreview\":\"Discuss matters concerning stock options and early release of quarterly earnings.\\n\\n\", " + - "\"end\":{\"dateTime\":\"" + timeSlice[0] + "T16:00:00.0000000\",\"timeZone\":\"UTC\"},\"hasAttachments\":false,\"hideAttendees\":false,\"iCalUId\":\"040000008200E00074C5B7101A82E0080000000010A45EC092A9D801000000000000000010000000999C7C6281C2B24A91D5502392B8EF38\",\"importance\":\"normal\",\"isAllDay\":false,\"isCancelled\":false,\"isDraft\":false,\"isOnlineMeeting\":true,\"isOrganizer\":true,\"isReminderOn\":true," + - "\"location\":{\"address\":{},\"coordinates\":{},\"displayName\":\"\",\"locationType\":\"default\",\"uniqueIdType\":\"unknown\"},\"locations\":[],\"onlineMeeting\":{\"joinUrl\":\"https://teams.microsoft.com/l/meetup-join/19%3ameeting_YWNhMzAxZjItMzE2My00ZGQzLTkzMDUtNjQ3NTY0NjNjMTZi%40thread.v2/0?context=%7b%22Tid%22%3a%224d603060-18d6-4764-b9be-4cb794d32b69%22%2c%22Oid%22%3a%22a4a472f8-ccb0-43ec-bf52-3697a91b926c%22%7d\"},\"onlineMeetingProvider\":\"teamsForBusiness\"," + - "\"organizer\":{\"emailAddress\":{\"address\":\"LidiaH@8qzvrj.onmicrosoft.com\",\"name\":\"Lidia Holloway\"}},\"originalEndTimeZone\":\"Eastern Standard Time\",\"originalStartTimeZone\":\"Eastern Standard Time\",\"reminderMinutesBeforeStart\":15,\"responseRequested\":true,\"responseStatus\":{\"response\":\"organizer\",\"time\":\"0001-01-01T00:00:00Z\"},\"sensitivity\":\"normal\",\"showAs\":\"busy\"," + - "\"start\":{\"dateTime\":\"" + timeSlice[0] + "T15:30:00.0000000\",\"timeZone\":\"UTC\"},\"subject\":\"Board " + subject + " Meeting\",\"transactionId\":\"28b36295-6cd3-952f-d8f5-deb313444a51\",\"type\":\"singleInstance\",\"webLink\":\"https://outlook.office365.com/owa/?itemid=AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAABU%2BFdvAAA%3D&exvsurl=1&path=/calendar/item\"}" - - return []byte(event) -} - type errReader struct { readErr error } diff --git a/src/internal/connector/mockconnector/mock_data_message.go b/src/internal/connector/mockconnector/mock_data_message.go index 156bbd1e6..a4e555de1 100644 --- a/src/internal/connector/mockconnector/mock_data_message.go +++ b/src/internal/connector/mockconnector/mock_data_message.go @@ -2,6 +2,10 @@ package mockconnector import ( "fmt" + "math/rand" + "strconv" + "strings" + "time" "github.com/alcionai/corso/src/internal/common" ) @@ -33,6 +37,28 @@ const ( "\"replyTo\":[],\"sender\":{\"emailAddress\":{\"address\":\"foobar@8qzvrj.onmicrosoft.com\",\"name\":\"A Stranger\"}},\"sentDateTime\":\"2022-09-26T23:15:46Z\"," + "\"subject\":\"%s\",\"toRecipients\":[{\"emailAddress\":{\"address\":\"LidiaH@8qzvrj.onmicrosoft.com\",\"name\":\"Lidia Holloway\"}}]," + "\"webLink\":\"https://outlook.office365.com/owa/?ItemID=AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAEMAADSEBNbUIB9RL6ePDeF3FIYAAB3XwIkAAA%%3D&exvsurl=1&viewmodel=ReadMessageItem\"}" + + // Order of fields to fill in: + // 1. start/end date + // 2. subject + eventTmpl = "{\"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\"" + + ":[],\"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\":\"%[1]sT07: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\":\"%[1]sT06:00:00.0000000\",\"timeZone\":\"UTC\"}," + + "\"subject\":\"%s\"," + + "\"type\":\"singleInstance\",\"webLink\":\"https://outlook.office365.com/owa/?itemid=AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAAAAAG76AAA%%3D&exvsurl=1&path=/calendar/item\"}" ) // GetMockMessageBytes returns bytes for Messageable item. @@ -159,3 +185,86 @@ func GetMessageWithOneDriveAttachment(subject string) []byte { return []byte(message) } + +// GetMockContactBytes returns bytes for Contactable item. +// When hydrated: contact.GetGivenName() shows differences +func GetMockContactBytes(middleName string) []byte { + phone := generatePhoneNumber() + //nolint:lll + contact := "{\"id\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAEOAADSEBNbUIB9RL6ePDeF3FIYAABS7DZnAAA=\",\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users('foobar%408qzvrj.onmicrosoft.com')/contacts/$entity\"," + + "\"@odata.etag\":\"W/\\\"EQAAABYAAADSEBNbUIB9RL6ePDeF3FIYAABSx4Tr\\\"\",\"categories\":[],\"changeKey\":\"EQAAABYAAADSEBNbUIB9RL6ePDeF3FIYAABSx4Tr\",\"createdDateTime\":\"2019-08-04T06:55:33Z\",\"lastModifiedDateTime\":\"2019-08-04T06:55:33Z\",\"businessAddress\":{},\"businessPhones\":[],\"children\":[]," + + "\"displayName\":\"Santiago Quail\",\"emailAddresses\":[],\"fileAs\":\"Quail, Santiago\",\"mobilePhone\": \"" + phone + "\"," + + "\"givenName\":\"Santiago\",\"homeAddress\":{},\"homePhones\":[],\"imAddresses\":[],\"otherAddress\":{},\"parentFolderId\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9FIYAAAAAAEOAAA=\",\"personalNotes\":\"\",\"middleName\":\"" + middleName + "\",\"surname\":\"Quail\"}" + + return []byte(contact) +} + +// generatePhoneNumber creates a random phone number +// @return string representation in format (xxx)xxx-xxxx +func generatePhoneNumber() string { + numbers := make([]string, 0) + + for i := 0; i < 10; i++ { + temp := rand.Intn(10) + value := strconv.Itoa(temp) + numbers = append(numbers, value) + } + + area := strings.Join(numbers[:3], "") + prefix := strings.Join(numbers[3:6], "") + suffix := strings.Join(numbers[6:], "") + phoneNo := "(" + area + ")" + prefix + "-" + suffix + + return phoneNo +} + +// 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") + fullSubject := " " + subject + " Review + Lunch" + + formatted := fmt.Sprintf(eventTmpl, timeSlice[0], fullSubject) + + return []byte(formatted) +} + +func GetMockEventWithSubjectBytes(subject string) []byte { + newTime := time.Now().AddDate(0, 0, 1) + conversion := common.FormatTime(newTime) + timeSlice := strings.Split(conversion, "T") + + formatted := fmt.Sprintf(eventTmpl, timeSlice[0], subject) + + return []byte(formatted) +} + +func GetMockEventWithAttendeesBytes(subject string) []byte { + newTime := time.Now().AddDate(0, 0, 1) + conversion := common.FormatTime(newTime) + timeSlice := strings.Split(conversion, "T") + + //nolint:lll + event := "{\"id\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAABU_FdvAAA=\",\"@odata.etag\":\"W/\\\"0hATW1CAfUS+njw3hdxSGAAAVK7j9A==\\\"\"," + + "\"calendar@odata.associationLink\":\"https://graph.microsoft.com/v1.0/users('a4a472f8-ccb0-43ec-bf52-3697a91b926c')/calendars('AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9QmQYWNcI7hCpPrAQDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAAA=')/$ref\"," + + "\"calendar@odata.navigationLink\":\"https://graph.microsoft.com/v1.0/users('a4a472f8-ccb0-43ec-bf52-3697a91b926c')/calendars('AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwAuAAAAAADCNgjhM9QmQYWNcI7hCpPrAQDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAAA=')\"," + + "\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users('a4a472f8-ccb0-43ec-bf52-3697a91b926c')/events/$entity\",\"categories\":[],\"changeKey\":\"0hATW1CAfUS+njw3hdxSGAAAVK7j9A==\",\"createdDateTime\":\"2022-08-06T12:47:56Z\",\"lastModifiedDateTime\":\"2022-08-06T12:49:59Z\",\"allowNewTimeProposals\":true," + + "\"attendees\":[{\"emailAddress\":{\"address\":\"george.martinez@8qzvrj.onmicrosoft.com\",\"name\":\"George Martinez\"},\"type\":\"required\",\"status\":{\"response\":\"none\",\"time\":\"0001-01-01T00:00:00Z\"}},{\"emailAddress\":{\"address\":\"LeeG@8qzvrj.onmicrosoft.com\",\"name\":\"Lee Gu\"},\"type\":\"required\",\"status\":{\"response\":\"none\",\"time\":\"0001-01-01T00:00:00Z\"}}]," + + "\"body\":{\"content\":\"\\n\\n\\n\\n\\n
\\nDiscuss matters concerning stock options and early release of quarterly earnings.
\\n
" + + "\\n
________________________________________________________________________________\\n
\\n
" + + "\\n
Microsoft Teams meeting\\n
\\n
\\n
Join on your computer or mobile app" + + "\\n
\\nClick\\n here to join the meeting
" + + "\\n
\\n
Meeting ID:\\n292 784 521 247
\\nPasscode: SzBkfK\\n" + + "\\n
Download\\n Teams | " + + "\\nJoin on the web
\\n
\\n
\\n
Learn more" + + "\\n | " + + "\\nMeeting options
\\n
\\n
\\n
\\n
\\n
\\n
________________________________________________________________________________" + + "\\n
\\n\\n\\n\",\"contentType\":\"html\"},\"bodyPreview\":\"Discuss matters concerning stock options and early release of quarterly earnings.\\n\\n\", " + + "\"end\":{\"dateTime\":\"" + timeSlice[0] + "T16:00:00.0000000\",\"timeZone\":\"UTC\"},\"hasAttachments\":false,\"hideAttendees\":false,\"iCalUId\":\"040000008200E00074C5B7101A82E0080000000010A45EC092A9D801000000000000000010000000999C7C6281C2B24A91D5502392B8EF38\",\"importance\":\"normal\",\"isAllDay\":false,\"isCancelled\":false,\"isDraft\":false,\"isOnlineMeeting\":true,\"isOrganizer\":true,\"isReminderOn\":true," + + "\"location\":{\"address\":{},\"coordinates\":{},\"displayName\":\"\",\"locationType\":\"default\",\"uniqueIdType\":\"unknown\"},\"locations\":[],\"onlineMeeting\":{\"joinUrl\":\"https://teams.microsoft.com/l/meetup-join/19%3ameeting_YWNhMzAxZjItMzE2My00ZGQzLTkzMDUtNjQ3NTY0NjNjMTZi%40thread.v2/0?context=%7b%22Tid%22%3a%224d603060-18d6-4764-b9be-4cb794d32b69%22%2c%22Oid%22%3a%22a4a472f8-ccb0-43ec-bf52-3697a91b926c%22%7d\"},\"onlineMeetingProvider\":\"teamsForBusiness\"," + + "\"organizer\":{\"emailAddress\":{\"address\":\"LidiaH@8qzvrj.onmicrosoft.com\",\"name\":\"Lidia Holloway\"}},\"originalEndTimeZone\":\"Eastern Standard Time\",\"originalStartTimeZone\":\"Eastern Standard Time\",\"reminderMinutesBeforeStart\":15,\"responseRequested\":true,\"responseStatus\":{\"response\":\"organizer\",\"time\":\"0001-01-01T00:00:00Z\"},\"sensitivity\":\"normal\",\"showAs\":\"busy\"," + + "\"start\":{\"dateTime\":\"" + timeSlice[0] + "T15:30:00.0000000\",\"timeZone\":\"UTC\"},\"subject\":\"Board " + subject + " Meeting\",\"transactionId\":\"28b36295-6cd3-952f-d8f5-deb313444a51\",\"type\":\"singleInstance\",\"webLink\":\"https://outlook.office365.com/owa/?itemid=AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAABU%2BFdvAAA%3D&exvsurl=1&path=/calendar/item\"}" + + return []byte(event) +}