fix message size report (#4493)

#### Does this PR need a docs update or release note?

- [x]  Yes, it's included

#### Type of change

- [x] 🐛 Bugfix

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-10-12 20:19:59 -06:00 committed by GitHub
parent aa287131cf
commit 5b8b371976
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 71 deletions

View File

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Catch and report cases where a protected resource is locked out of access. SDK consumers have a new errs sentinel that allows them to check for this case. - Catch and report cases where a protected resource is locked out of access. SDK consumers have a new errs sentinel that allows them to check for this case.
- Fix a case where missing item LastModifiedTimes could cause incremental backups to fail. - Fix a case where missing item LastModifiedTimes could cause incremental backups to fail.
- Email size metadata was incorrectly set to the size of the last attachment. Emails will now correctly report the size of the mail content plus the size of all attachments.
## [v0.14.0] (beta) - 2023-10-09 ## [v0.14.0] (beta) - 2023-10-09

View File

@ -148,6 +148,10 @@ func (cp *corsoProgress) FinishedFile(relativePath string, err error) {
Label(fault.LabelForceNoBackupCreation)) Label(fault.LabelForceNoBackupCreation))
return return
} else if info.Modified().IsZero() {
cp.errs.AddRecoverable(ctx, clues.New("zero-valued mod time").
WithClues(ctx).
Label(fault.LabelForceNoBackupCreation))
} }
err = cp.deets.Add(d.repoPath, d.locationPath, info) err = cp.deets.Add(d.repoPath, d.locationPath, info)

View File

@ -1067,6 +1067,7 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections_NoDetailsForMeta() {
DriveID: "drive-id", DriveID: "drive-id",
DriveName: "drive-name", DriveName: "drive-name",
ItemName: "item", ItemName: "item",
Modified: time.Now(),
} }
// tags that are supplied by the caller. This includes basic tags to support // tags that are supplied by the caller. This includes basic tags to support
@ -1124,10 +1125,11 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections_NoDetailsForMeta() {
info.ItemName = name info.ItemName = name
ms := &dataMock.Item{ ms := &dataMock.Item{
ItemID: name, ItemID: name,
Reader: io.NopCloser(&bytes.Buffer{}), Reader: io.NopCloser(&bytes.Buffer{}),
ItemSize: 0, ItemSize: 0,
ItemInfo: details.ItemInfo{OneDrive: &info}, ItemInfo: details.ItemInfo{OneDrive: &info},
ModifiedTime: info.Modified,
} }
streams = append(streams, ms) streams = append(streams, ms)
@ -1152,12 +1154,15 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections_NoDetailsForMeta() {
cols: func() []data.BackupCollection { cols: func() []data.BackupCollection {
info := baseOneDriveItemInfo info := baseOneDriveItemInfo
info.ItemName = testFileName info.ItemName = testFileName
// Update the mod time so it's not counted as cached.
info.Modified = info.Modified.Add(time.Hour)
ms := &dataMock.Item{ ms := &dataMock.Item{
ItemID: testFileName, ItemID: testFileName,
Reader: io.NopCloser(&bytes.Buffer{}), Reader: io.NopCloser(&bytes.Buffer{}),
ItemSize: 0, ItemSize: 0,
ItemInfo: details.ItemInfo{OneDrive: &info}, ItemInfo: details.ItemInfo{OneDrive: &info},
ModifiedTime: info.Modified,
} }
mc := &dataMock.Collection{ mc := &dataMock.Collection{
@ -1305,20 +1310,25 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections_ReaderError() {
loc2 := path.Builder{}.Append(suite.storePath2.Folders()...) loc2 := path.Builder{}.Append(suite.storePath2.Folders()...)
r := identity.NewReason(testTenant, testUser, path.ExchangeService, path.EmailCategory) r := identity.NewReason(testTenant, testUser, path.ExchangeService, path.EmailCategory)
info := exchMock.StubMailInfo()
info.Exchange.Modified = time.Now()
collections := []data.BackupCollection{ collections := []data.BackupCollection{
&dataMock.Collection{ &dataMock.Collection{
Path: suite.storePath1, Path: suite.storePath1,
Loc: loc1, Loc: loc1,
ItemData: []data.Item{ ItemData: []data.Item{
&dataMock.Item{ &dataMock.Item{
ItemID: testFileName, ItemID: testFileName,
Reader: io.NopCloser(bytes.NewReader(testFileData)), Reader: io.NopCloser(bytes.NewReader(testFileData)),
ItemInfo: exchMock.StubMailInfo(), ModifiedTime: info.Modified(),
ItemInfo: info,
}, },
&dataMock.Item{ &dataMock.Item{
ItemID: testFileName2, ItemID: testFileName2,
Reader: io.NopCloser(bytes.NewReader(testFileData2)), Reader: io.NopCloser(bytes.NewReader(testFileData2)),
ItemInfo: exchMock.StubMailInfo(), ModifiedTime: info.Modified(),
ItemInfo: info,
}, },
}, },
}, },
@ -1327,24 +1337,28 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections_ReaderError() {
Loc: loc2, Loc: loc2,
ItemData: []data.Item{ ItemData: []data.Item{
&dataMock.Item{ &dataMock.Item{
ItemID: testFileName3, ItemID: testFileName3,
Reader: io.NopCloser(bytes.NewReader(testFileData3)), Reader: io.NopCloser(bytes.NewReader(testFileData3)),
ItemInfo: exchMock.StubMailInfo(), ModifiedTime: info.Modified(),
ItemInfo: info,
}, },
&dataMock.Item{ &dataMock.Item{
ItemID: testFileName4, ItemID: testFileName4,
ReadErr: assert.AnError, ReadErr: assert.AnError,
ItemInfo: exchMock.StubMailInfo(), ModifiedTime: info.Modified(),
ItemInfo: info,
}, },
&dataMock.Item{ &dataMock.Item{
ItemID: testFileName5, ItemID: testFileName5,
Reader: io.NopCloser(bytes.NewReader(testFileData5)), Reader: io.NopCloser(bytes.NewReader(testFileData5)),
ItemInfo: exchMock.StubMailInfo(), ModifiedTime: info.Modified(),
ItemInfo: info,
}, },
&dataMock.Item{ &dataMock.Item{
ItemID: testFileName6, ItemID: testFileName6,
Reader: io.NopCloser(bytes.NewReader(testFileData6)), Reader: io.NopCloser(bytes.NewReader(testFileData6)),
ItemInfo: exchMock.StubMailInfo(), ModifiedTime: info.Modified(),
ItemInfo: info,
}, },
}, },
}, },

View File

@ -3,6 +3,7 @@ package api_test
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
@ -89,6 +90,9 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer() {
control.Copy) control.Copy)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
// ensure we don't bucket the mod time within a second
time.Sleep(2 * time.Second)
updatedFile := models.NewDriveItem() updatedFile := models.NewDriveItem()
updatedFile.SetAdditionalData(origFile.GetAdditionalData()) updatedFile.SetAdditionalData(origFile.GetAdditionalData())
updatedFile.SetCreatedBy(origFile.GetCreatedBy()) updatedFile.SetCreatedBy(origFile.GetCreatedBy())
@ -222,7 +226,7 @@ func (suite *DriveAPIIntgSuite) TestDrives_PostItemInContainer() {
assert.True( assert.True(
t, t,
ptr.Val(origFile.GetLastModifiedDateTime()).Before(ptr.Val(i.GetLastModifiedDateTime())), ptr.Val(origFile.GetLastModifiedDateTime()).Before(ptr.Val(i.GetLastModifiedDateTime())),
"replaced item should have the same mod time") "replaced item should have a later mod time")
}, },
}, },
} }

View File

@ -304,7 +304,7 @@ func (c Mail) GetItem(
if err == nil { if err == nil {
for _, a := range attached.GetValue() { for _, a := range attached.GetValue() {
attachSize := ptr.Val(a.GetSize()) attachSize := ptr.Val(a.GetSize())
size = +int64(attachSize) size += int64(attachSize)
} }
mail.SetAttachments(attached.GetValue()) mail.SetAttachments(attached.GetValue())
@ -380,7 +380,7 @@ func (c Mail) GetItem(
atts = append(atts, att) atts = append(atts, att)
attachSize := ptr.Val(a.GetSize()) attachSize := ptr.Val(a.GetSize())
size = +int64(attachSize) size += int64(attachSize)
} }
mail.SetAttachments(atts) mail.SetAttachments(atts)

View File

@ -231,20 +231,20 @@ func (suite *MailAPIIntgSuite) TestHugeAttachmentListDownload() {
{ {
name: "fetch with attachment", name: "fetch with attachment",
setupf: func() { setupf: func() {
mitem := models.NewMessage() email := models.NewMessage()
mitem.SetId(&mid) email.SetId(&mid)
mitem.SetHasAttachments(ptr.To(true)) email.SetHasAttachments(ptr.To(true))
interceptV1Path("users", "user", "messages", mid). interceptV1Path("users", "user", "messages", mid).
Reply(200). Reply(200).
JSON(parseableToMap(suite.T(), mitem)) JSON(parseableToMap(suite.T(), email))
atts := models.NewAttachmentCollectionResponse() atts := models.NewAttachmentCollectionResponse()
aitem := models.NewAttachment() attch := models.NewAttachment()
asize := int32(50) size := int32(50)
aitem.SetSize(&asize) attch.SetSize(&size)
atts.SetValue([]models.Attachmentable{aitem}) atts.SetValue([]models.Attachmentable{attch})
interceptV1Path("users", "user", "messages", mid, "attachments"). interceptV1Path("users", "user", "messages", mid, "attachments").
Reply(200). Reply(200).
@ -257,23 +257,22 @@ func (suite *MailAPIIntgSuite) TestHugeAttachmentListDownload() {
{ {
name: "fetch individual attachment", name: "fetch individual attachment",
setupf: func() { setupf: func() {
truthy := true email := models.NewMessage()
mitem := models.NewMessage() email.SetId(&mid)
mitem.SetId(&mid) email.SetHasAttachments(ptr.To(true))
mitem.SetHasAttachments(&truthy)
interceptV1Path("users", "user", "messages", mid). interceptV1Path("users", "user", "messages", mid).
Reply(200). Reply(200).
JSON(parseableToMap(suite.T(), mitem)) JSON(parseableToMap(suite.T(), email))
atts := models.NewAttachmentCollectionResponse() atts := models.NewAttachmentCollectionResponse()
aitem := models.NewAttachment() attch := models.NewAttachment()
aitem.SetId(&aid) attch.SetId(&aid)
asize := int32(200) size := int32(200)
aitem.SetSize(&asize) attch.SetSize(&size)
atts.SetValue([]models.Attachmentable{aitem}) atts.SetValue([]models.Attachmentable{attch})
interceptV1Path("users", "user", "messages", mid, "attachments"). interceptV1Path("users", "user", "messages", mid, "attachments").
Reply(503) Reply(503)
@ -284,7 +283,7 @@ func (suite *MailAPIIntgSuite) TestHugeAttachmentListDownload() {
interceptV1Path("users", "user", "messages", mid, "attachments", aid). interceptV1Path("users", "user", "messages", mid, "attachments", aid).
Reply(200). Reply(200).
JSON(parseableToMap(suite.T(), aitem)) JSON(parseableToMap(suite.T(), attch))
}, },
attachmentCount: 1, attachmentCount: 1,
size: 200, size: 200,
@ -294,22 +293,22 @@ func (suite *MailAPIIntgSuite) TestHugeAttachmentListDownload() {
name: "fetch multiple individual attachments", name: "fetch multiple individual attachments",
setupf: func() { setupf: func() {
truthy := true truthy := true
mitem := models.NewMessage() email := models.NewMessage()
mitem.SetId(&mid) email.SetId(&mid)
mitem.SetHasAttachments(&truthy) email.SetHasAttachments(&truthy)
interceptV1Path("users", "user", "messages", mid). interceptV1Path("users", "user", "messages", mid).
Reply(200). Reply(200).
JSON(parseableToMap(suite.T(), mitem)) JSON(parseableToMap(suite.T(), email))
atts := models.NewAttachmentCollectionResponse() atts := models.NewAttachmentCollectionResponse()
aitem := models.NewAttachment() attch := models.NewAttachment()
aitem.SetId(&aid) attch.SetId(&aid)
asize := int32(200) asize := int32(200)
aitem.SetSize(&asize) attch.SetSize(&asize)
atts.SetValue([]models.Attachmentable{aitem, aitem, aitem, aitem, aitem}) atts.SetValue([]models.Attachmentable{attch, attch, attch, attch, attch})
interceptV1Path("users", "user", "messages", mid, "attachments"). interceptV1Path("users", "user", "messages", mid, "attachments").
Reply(503) Reply(503)
@ -321,24 +320,24 @@ func (suite *MailAPIIntgSuite) TestHugeAttachmentListDownload() {
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
interceptV1Path("users", "user", "messages", mid, "attachments", aid). interceptV1Path("users", "user", "messages", mid, "attachments", aid).
Reply(200). Reply(200).
JSON(parseableToMap(suite.T(), aitem)) JSON(parseableToMap(suite.T(), attch))
} }
}, },
attachmentCount: 5, attachmentCount: 5,
size: 200, size: 1000,
expect: assert.NoError, expect: assert.NoError,
}, },
} }
for _, tt := range tests { for _, test := range tests {
suite.Run(tt.name, func() { suite.Run(test.name, func() {
t := suite.T() t := suite.T()
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
defer gock.Off() defer gock.Off()
tt.setupf() test.setupf()
item, _, err := suite.its.gockAC.Mail().GetItem( item, _, err := suite.its.gockAC.Mail().GetItem(
ctx, ctx,
@ -346,28 +345,26 @@ func (suite *MailAPIIntgSuite) TestHugeAttachmentListDownload() {
mid, mid,
false, false,
fault.New(true)) fault.New(true))
tt.expect(t, err) test.expect(t, err)
it, ok := item.(models.Messageable) it, ok := item.(models.Messageable)
require.True(t, ok, "convert to messageable") require.True(t, ok, "convert to messageable")
var size int64 var size int64
mailBody := it.GetBody()
if mailBody != nil { if it.GetBody() != nil {
content := ptr.Val(mailBody.GetContent()) content := ptr.Val(it.GetBody().GetContent())
if len(content) > 0 { size = int64(len(content))
size = int64(len(content))
}
} }
attachments := it.GetAttachments() attachments := it.GetAttachments()
for _, attachment := range attachments { for _, attachment := range attachments {
size = +int64(*attachment.GetSize()) size += int64(*attachment.GetSize())
} }
assert.Equal(t, *it.GetId(), mid) assert.Equal(t, *it.GetId(), mid)
assert.Equal(t, tt.attachmentCount, len(attachments), "attachment count") assert.Equal(t, test.attachmentCount, len(attachments), "attachment count")
assert.Equal(t, tt.size, size, "mail size") assert.Equal(t, test.size, size, "mail size")
assert.True(t, gock.IsDone(), "made all requests") assert.True(t, gock.IsDone(), "made all requests")
}) })
} }