Restore/backup test for email attachments (#1057)
## Description Test to check that attachments are properly populated and fetched during restore and backup Some notes: * make slice element comparison function generic so it can compare locations and attachments * very hacky comparison for attachment content using reflection as it's not easily accessible otherwise. Changes to the underlying struct will cause runtime failures in the test case if/when they occur ## Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [x] 🤖 Test - [ ] 💻 CI/Deployment - [ ] 🐹 Trivial/Minor ## Issue(s) * #913 ## Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
79f50cad82
commit
4ba8f50c53
@ -71,6 +71,70 @@ func testEmptyOrEqual[T any](t *testing.T, expected *T, got *T, msg string) {
|
|||||||
assert.Equal(t, *expected, *got, msg)
|
assert.Equal(t, *expected, *got, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testElementsMatch[T any](
|
||||||
|
t *testing.T,
|
||||||
|
expected []T,
|
||||||
|
got []T,
|
||||||
|
equalityCheck func(expectedItem, gotItem T) bool,
|
||||||
|
) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
pending := make([]*T, len(expected))
|
||||||
|
for i := 0; i < len(expected); i++ {
|
||||||
|
pending[i] = &expected[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
unexpected := []T{}
|
||||||
|
|
||||||
|
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 equalityCheck(*maybe, got[i]) {
|
||||||
|
pending[j] = nil
|
||||||
|
found = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
unexpected = append(unexpected, got[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print differences.
|
||||||
|
missing := []T{}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
type itemInfo struct {
|
type itemInfo struct {
|
||||||
// lookupKey is a string that can be used to find this data from a set of
|
// lookupKey is a string that can be used to find this data from a set of
|
||||||
// other data in the same collection. This key should be something that will
|
// other data in the same collection. This key should be something that will
|
||||||
@ -92,11 +156,50 @@ type colInfo struct {
|
|||||||
items []itemInfo
|
items []itemInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func attachmentEqual(
|
||||||
|
expected models.Attachmentable,
|
||||||
|
got models.Attachmentable,
|
||||||
|
) bool {
|
||||||
|
// This is super hacky, but seems like it would be good to have a comparison
|
||||||
|
// of the actual content. I think the only other way to really get it is to
|
||||||
|
// serialize both structs to JSON and pull it from there or something though.
|
||||||
|
expectedData := reflect.Indirect(reflect.ValueOf(expected)).FieldByName("contentBytes").Bytes()
|
||||||
|
gotData := reflect.Indirect(reflect.ValueOf(got)).FieldByName("contentBytes").Bytes()
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expectedData, gotData) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !emptyOrEqual(expected.GetContentType(), got.GetContentType()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip Id as it's tied to this specific instance of the item.
|
||||||
|
|
||||||
|
if !emptyOrEqual(expected.GetIsInline(), got.GetIsInline()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip LastModifiedDateTime as it's tied to this specific instance of the item.
|
||||||
|
|
||||||
|
if !emptyOrEqual(expected.GetName(), got.GetName()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip Size as the server clobbers whatever value we give it. It's unknown
|
||||||
|
// how they populate size though as it's not just the length of the byte
|
||||||
|
// array backing the content.
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func checkMessage(
|
func checkMessage(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
expected models.Messageable,
|
expected models.Messageable,
|
||||||
got models.Messageable,
|
got models.Messageable,
|
||||||
) {
|
) {
|
||||||
|
testElementsMatch(t, expected.GetAttachments(), got.GetAttachments(), attachmentEqual)
|
||||||
|
|
||||||
assert.Equal(t, expected.GetBccRecipients(), got.GetBccRecipients(), "BccRecipients")
|
assert.Equal(t, expected.GetBccRecipients(), got.GetBccRecipients(), "BccRecipients")
|
||||||
|
|
||||||
testEmptyOrEqual(t, expected.GetBody().GetContentType(), got.GetBody().GetContentType(), "Body.ContentType")
|
testEmptyOrEqual(t, expected.GetBody().GetContentType(), got.GetBody().GetContentType(), "Body.ContentType")
|
||||||
@ -253,67 +356,6 @@ func checkContact(
|
|||||||
testEmptyOrEqual(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 {
|
func locationEqual(expected, got models.Locationable) bool {
|
||||||
if !reflect.DeepEqual(expected.GetAddress(), got.GetAddress()) {
|
if !reflect.DeepEqual(expected.GetAddress(), got.GetAddress()) {
|
||||||
return false
|
return false
|
||||||
@ -406,13 +448,14 @@ func checkEvent(
|
|||||||
// Cheating a little here in the name of code-reuse. model.Location needs
|
// 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"
|
// custom compare logic because it has fields marked as "internal use only"
|
||||||
// that seem to change.
|
// that seem to change.
|
||||||
checkLocations(
|
testElementsMatch(
|
||||||
t,
|
t,
|
||||||
[]models.Locationable{expected.GetLocation()},
|
[]models.Locationable{expected.GetLocation()},
|
||||||
[]models.Locationable{got.GetLocation()},
|
[]models.Locationable{got.GetLocation()},
|
||||||
|
locationEqual,
|
||||||
)
|
)
|
||||||
|
|
||||||
checkLocations(t, expected.GetLocations(), got.GetLocations())
|
testElementsMatch(t, expected.GetLocations(), got.GetLocations(), locationEqual)
|
||||||
|
|
||||||
assert.Equal(t, expected.GetOnlineMeeting(), got.GetOnlineMeeting(), "OnlineMeeting")
|
assert.Equal(t, expected.GetOnlineMeeting(), got.GetOnlineMeeting(), "OnlineMeeting")
|
||||||
|
|
||||||
|
|||||||
@ -438,6 +438,44 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() {
|
|||||||
backupSelFunc func(dest control.RestoreDestination, backupUser string) selectors.Selector
|
backupSelFunc func(dest control.RestoreDestination, backupUser string) selectors.Selector
|
||||||
expectedRestoreFolders int
|
expectedRestoreFolders int
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "EmailsWithAttachments",
|
||||||
|
service: path.ExchangeService,
|
||||||
|
expectedRestoreFolders: 1,
|
||||||
|
collections: []colInfo{
|
||||||
|
{
|
||||||
|
pathElements: []string{"Inbox"},
|
||||||
|
category: path.EmailCategory,
|
||||||
|
items: []itemInfo{
|
||||||
|
{
|
||||||
|
name: "someencodeditemID",
|
||||||
|
data: mockconnector.GetMockMessageWithDirectAttachment(
|
||||||
|
subjectText + "-1",
|
||||||
|
),
|
||||||
|
lookupKey: subjectText + "-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "someencodeditemID2",
|
||||||
|
data: mockconnector.GetMockMessageWithTwoAttachments(
|
||||||
|
subjectText + "-2",
|
||||||
|
),
|
||||||
|
lookupKey: subjectText + "-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 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.MailFolders(
|
||||||
|
[]string{backupUser},
|
||||||
|
[]string{dest.ContainerName},
|
||||||
|
))
|
||||||
|
|
||||||
|
return backupSel.Selector
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "MultipleEmailsSingleFolder",
|
name: "MultipleEmailsSingleFolder",
|
||||||
service: path.ExchangeService,
|
service: path.ExchangeService,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user