refactor exchange attachment downloads (#2302)
## Description Three changes: adds a LargeItem servicer to the exchange api client. Migrates attachment downloads in mail and events out of serialize and into the GetItem func. Finally, utilizes the largeItem servicer to download attachments, instead of the standard servicer. A follow-up PR will add mocked test cases for these changes. ## Does this PR need a docs update or release note? - [x] ✅ Yes, it's included ## Type of change - [x] 🌻 Feature ## Issue(s) * #2299 ## Test Plan - [x] 💚 E2E
This commit is contained in:
parent
3e106dbac8
commit
0d8eb8f4fa
@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Inline attachments (e.g. copy/paste ) are discovered and backed up correctly ([#2163](https://github.com/alcionai/corso/issues/2163))
|
- Inline attachments (e.g. copy/paste ) are discovered and backed up correctly ([#2163](https://github.com/alcionai/corso/issues/2163))
|
||||||
- Guest and External users (for cloud accounts) and non-on-premise users (for systems that use on-prem AD syncs) are now excluded from backup and restore operations.
|
- Guest and External users (for cloud accounts) and non-on-premise users (for systems that use on-prem AD syncs) are now excluded from backup and restore operations.
|
||||||
- Remove the M365 license guid check in OneDrive backup which wasn't reliable.
|
- Remove the M365 license guid check in OneDrive backup which wasn't reliable.
|
||||||
|
- Reduced extra socket consumption while downloading multiple drive files.
|
||||||
|
- Extended timeout boundaries for exchange attachment downloads, reducing risk of cancellation on large files.
|
||||||
|
|
||||||
## [v0.1.0] (alpha) - 2023-01-13
|
## [v0.1.0] (alpha) - 2023-01-13
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,11 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/microsoft/kiota-abstractions-go/serialization"
|
"github.com/microsoft/kiota-abstractions-go/serialization"
|
||||||
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
@ -57,6 +59,11 @@ type Client struct {
|
|||||||
// The stable service is re-usable for any non-paged request.
|
// The stable service is re-usable for any non-paged request.
|
||||||
// This allows us to maintain performance across async requests.
|
// This allows us to maintain performance across async requests.
|
||||||
stable graph.Servicer
|
stable graph.Servicer
|
||||||
|
|
||||||
|
// The largeItem graph servicer is configured specifically for
|
||||||
|
// downloading large items. Specifically for use when handling
|
||||||
|
// attachments, and for no other use.
|
||||||
|
largeItem graph.Servicer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient produces a new exchange api client. Must be used in
|
// NewClient produces a new exchange api client. Must be used in
|
||||||
@ -67,27 +74,45 @@ func NewClient(creds account.M365Config) (Client, error) {
|
|||||||
return Client{}, err
|
return Client{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Client{creds, s}, nil
|
li, err := newLargeItemService(creds)
|
||||||
|
if err != nil {
|
||||||
|
return Client{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Client{creds, s, li}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// service generates a new service. Used for paged and other long-running
|
// service generates a new service. Used for paged and other long-running
|
||||||
// requests instead of the client's stable service, so that in-flight state
|
// requests instead of the client's stable service, so that in-flight state
|
||||||
// within the adapter doesn't get clobbered
|
// within the adapter doesn't get clobbered
|
||||||
func (c Client) service() (*graph.Service, error) {
|
func (c Client) service() (*graph.Service, error) {
|
||||||
return newService(c.Credentials)
|
s, err := newService(c.Credentials)
|
||||||
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newService(creds account.M365Config) (*graph.Service, error) {
|
func newService(creds account.M365Config) (*graph.Service, error) {
|
||||||
adapter, err := graph.CreateAdapter(
|
a, err := graph.CreateAdapter(
|
||||||
|
creds.AzureTenantID,
|
||||||
|
creds.AzureClientID,
|
||||||
|
creds.AzureClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "generating no-timeout graph adapter")
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph.NewService(a), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLargeItemService(creds account.M365Config) (*graph.Service, error) {
|
||||||
|
a, err := graph.CreateAdapter(
|
||||||
creds.AzureTenantID,
|
creds.AzureTenantID,
|
||||||
creds.AzureClientID,
|
creds.AzureClientID,
|
||||||
creds.AzureClientSecret,
|
creds.AzureClientSecret,
|
||||||
)
|
graph.NoTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "generating graph api service client")
|
return nil, errors.Wrap(err, "generating no-timeout graph adapter")
|
||||||
}
|
}
|
||||||
|
|
||||||
return graph.NewService(adapter), nil
|
return graph.NewService(a), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -117,3 +142,14 @@ func orNow(t *time.Time) time.Time {
|
|||||||
|
|
||||||
return *t
|
return *t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HasAttachments(body models.ItemBodyable) bool {
|
||||||
|
if body.GetContent() == nil || body.GetContentType() == nil ||
|
||||||
|
*body.GetContentType() == models.TEXT_BODYTYPE || len(*body.GetContent()) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
content := *body.GetContent()
|
||||||
|
|
||||||
|
return strings.Contains(content, "src=\"cid:")
|
||||||
|
}
|
||||||
|
|||||||
@ -3,11 +3,14 @@ package api
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/mockconnector"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
)
|
)
|
||||||
@ -190,3 +193,57 @@ func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
var stubHTMLContent = "<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style type=\"text/css\" style=\"display:none\">\r\n<!--\r\np\r\n\t{margin-top:0;\r\n\tmargin-bottom:0}\r\n-->\r\n</style></head><body dir=\"ltr\"><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">Happy New Year,</div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">In accordance with TPS report guidelines, there have been questions about how to address our activities SharePoint Cover page. Do you believe this is the best picture? </div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><img class=\"FluidPluginCopy ContentPasted0 w-2070 h-1380\" size=\"5854817\" data-outlook-trace=\"F:1|T:1\" src=\"cid:85f4faa3-9851-40c7-ba0a-e63dce1185f9\" style=\"max-width:100%\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">Let me know if this meets our culture requirements.</div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">Warm Regards,</div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">Dustin</div></body></html>"
|
||||||
|
|
||||||
|
func (suite *ExchangeServiceSuite) TestHasAttachments() {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
hasAttachment assert.BoolAssertionFunc
|
||||||
|
getBodyable func(t *testing.T) models.ItemBodyable
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Mock w/out attachment",
|
||||||
|
hasAttachment: assert.False,
|
||||||
|
getBodyable: func(t *testing.T) models.ItemBodyable {
|
||||||
|
byteArray := mockconnector.GetMockMessageWithBodyBytes(
|
||||||
|
"Test",
|
||||||
|
"This is testing",
|
||||||
|
"This is testing",
|
||||||
|
)
|
||||||
|
message, err := support.CreateMessageFromBytes(byteArray)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return message.GetBody()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mock w/ inline attachment",
|
||||||
|
hasAttachment: assert.True,
|
||||||
|
getBodyable: func(t *testing.T) models.ItemBodyable {
|
||||||
|
byteArray := mockconnector.GetMessageWithOneDriveAttachment("Test legacy")
|
||||||
|
message, err := support.CreateMessageFromBytes(byteArray)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return message.GetBody()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Edge Case",
|
||||||
|
hasAttachment: assert.True,
|
||||||
|
getBodyable: func(t *testing.T) models.ItemBodyable {
|
||||||
|
body := models.NewItemBody()
|
||||||
|
body.SetContent(&stubHTMLContent)
|
||||||
|
cat := models.HTML_BODYTYPE
|
||||||
|
body.SetContentType(&cat)
|
||||||
|
return body
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
found := HasAttachments(test.getBodyable(t))
|
||||||
|
test.hasAttachment(t, found)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -85,12 +85,37 @@ func (c Events) GetItem(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
user, itemID string,
|
user, itemID string,
|
||||||
) (serialization.Parsable, *details.ExchangeInfo, error) {
|
) (serialization.Parsable, *details.ExchangeInfo, error) {
|
||||||
evt, err := c.stable.Client().UsersById(user).EventsById(itemID).Get(ctx, nil)
|
event, err := c.stable.Client().UsersById(user).EventsById(itemID).Get(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return evt, EventInfo(evt), nil
|
var errs *multierror.Error
|
||||||
|
|
||||||
|
if *event.GetHasAttachments() || HasAttachments(event.GetBody()) {
|
||||||
|
for count := 0; count < numberOfRetries; count++ {
|
||||||
|
attached, err := c.largeItem.
|
||||||
|
Client().
|
||||||
|
UsersById(user).
|
||||||
|
EventsById(itemID).
|
||||||
|
Attachments().
|
||||||
|
Get(ctx, nil)
|
||||||
|
if err == nil {
|
||||||
|
event.SetAttachments(attached.GetValue())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Ctx(ctx).Debugw("retrying event attachment download", "err", err)
|
||||||
|
errs = multierror.Append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Ctx(ctx).Errorw("event attachment download exceeded maximum retries", "err", errs)
|
||||||
|
return nil, nil, support.WrapAndAppend(itemID, errors.Wrap(err, "download event attachment"), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return event, EventInfo(event), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) GetAllCalendarNamesForUser(
|
func (c Client) GetAllCalendarNamesForUser(
|
||||||
@ -249,8 +274,7 @@ func (c Events) GetAddedAndRemovedItemIDs(
|
|||||||
// Serialization
|
// Serialization
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// Serialize retrieves attachment data identified by the event item, and then
|
// Serialize transforms the event into a byte slice.
|
||||||
// serializes it into a byte slice.
|
|
||||||
func (c Events) Serialize(
|
func (c Events) Serialize(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
item serialization.Parsable,
|
item serialization.Parsable,
|
||||||
@ -268,31 +292,6 @@ func (c Events) Serialize(
|
|||||||
|
|
||||||
defer writer.Close()
|
defer writer.Close()
|
||||||
|
|
||||||
if *event.GetHasAttachments() || support.HasAttachments(event.GetBody()) {
|
|
||||||
// getting all the attachments might take a couple attempts due to filesize
|
|
||||||
var retriesErr error
|
|
||||||
|
|
||||||
for count := 0; count < numberOfRetries; count++ {
|
|
||||||
attached, err := c.stable.
|
|
||||||
Client().
|
|
||||||
UsersById(user).
|
|
||||||
EventsById(itemID).
|
|
||||||
Attachments().
|
|
||||||
Get(ctx, nil)
|
|
||||||
retriesErr = err
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
event.SetAttachments(attached.GetValue())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if retriesErr != nil {
|
|
||||||
logger.Ctx(ctx).Debug("exceeded maximum retries")
|
|
||||||
return nil, support.WrapAndAppend(itemID, errors.Wrap(retriesErr, "attachment failed"), nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = writer.WriteObjectValue("", event); err != nil {
|
if err = writer.WriteObjectValue("", event); err != nil {
|
||||||
return nil, support.SetNonRecoverableError(errors.Wrap(err, itemID))
|
return nil, support.SetNonRecoverableError(errors.Wrap(err, itemID))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,7 +97,8 @@ func (c Mail) GetContainerByID(
|
|||||||
return service.Client().UsersById(userID).MailFoldersById(dirID).Get(ctx, ofmf)
|
return service.Client().UsersById(userID).MailFoldersById(dirID).Get(ctx, ofmf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetItem retrieves a Messageable item.
|
// GetItem retrieves a Messageable item. If the item contains an attachment, that
|
||||||
|
// attachment is also downloaded.
|
||||||
func (c Mail) GetItem(
|
func (c Mail) GetItem(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
user, itemID string,
|
user, itemID string,
|
||||||
@ -107,6 +108,31 @@ func (c Mail) GetItem(
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errs *multierror.Error
|
||||||
|
|
||||||
|
if *mail.GetHasAttachments() || HasAttachments(mail.GetBody()) {
|
||||||
|
for count := 0; count < numberOfRetries; count++ {
|
||||||
|
attached, err := c.largeItem.
|
||||||
|
Client().
|
||||||
|
UsersById(user).
|
||||||
|
MessagesById(itemID).
|
||||||
|
Attachments().
|
||||||
|
Get(ctx, nil)
|
||||||
|
if err == nil {
|
||||||
|
mail.SetAttachments(attached.GetValue())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Ctx(ctx).Debugw("retrying mail attachment download", "err", err)
|
||||||
|
errs = multierror.Append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Ctx(ctx).Errorw("mail attachment download exceeded maximum retries", "err", errs)
|
||||||
|
return nil, nil, support.WrapAndAppend(itemID, errors.Wrap(err, "downloading mail attachment"), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return mail, MailInfo(mail), nil
|
return mail, MailInfo(mail), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,8 +264,7 @@ func (c Mail) GetAddedAndRemovedItemIDs(
|
|||||||
// Serialization
|
// Serialization
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// Serialize retrieves attachment data identified by the mail item, and then
|
// Serialize transforms the mail item into a byte slice.
|
||||||
// serializes it into a byte slice.
|
|
||||||
func (c Mail) Serialize(
|
func (c Mail) Serialize(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
item serialization.Parsable,
|
item serialization.Parsable,
|
||||||
@ -257,31 +282,6 @@ func (c Mail) Serialize(
|
|||||||
|
|
||||||
defer writer.Close()
|
defer writer.Close()
|
||||||
|
|
||||||
if *msg.GetHasAttachments() || support.HasAttachments(msg.GetBody()) {
|
|
||||||
// getting all the attachments might take a couple attempts due to filesize
|
|
||||||
var retriesErr error
|
|
||||||
|
|
||||||
for count := 0; count < numberOfRetries; count++ {
|
|
||||||
attached, err := c.stable.
|
|
||||||
Client().
|
|
||||||
UsersById(user).
|
|
||||||
MessagesById(itemID).
|
|
||||||
Attachments().
|
|
||||||
Get(ctx, nil)
|
|
||||||
retriesErr = err
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
msg.SetAttachments(attached.GetValue())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if retriesErr != nil {
|
|
||||||
logger.Ctx(ctx).Debug("exceeded maximum retries")
|
|
||||||
return nil, support.WrapAndAppend(itemID, errors.Wrap(retriesErr, "attachment failed"), nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = writer.WriteObjectValue("", msg); err != nil {
|
if err = writer.WriteObjectValue("", msg); err != nil {
|
||||||
return nil, support.SetNonRecoverableError(errors.Wrap(err, itemID))
|
return nil, support.SetNonRecoverableError(errors.Wrap(err, itemID))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -243,6 +243,8 @@ func (handler *LoggingMiddleware) Intercept(
|
|||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return immediately if the response is good (2xx).
|
||||||
|
// If api logging is toggled, log a body-less dump of the request/resp.
|
||||||
if (resp.StatusCode / 100) == 2 {
|
if (resp.StatusCode / 100) == 2 {
|
||||||
if logger.DebugAPI || os.Getenv(logGraphRequestsEnvKey) != "" {
|
if logger.DebugAPI || os.Getenv(logGraphRequestsEnvKey) != "" {
|
||||||
respDump, _ := httputil.DumpResponse(resp, false)
|
respDump, _ := httputil.DumpResponse(resp, false)
|
||||||
@ -263,6 +265,10 @@ func (handler *LoggingMiddleware) Intercept(
|
|||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log errors according to api debugging configurations.
|
||||||
|
// When debugging is toggled, every non-2xx is recorded with a respose dump.
|
||||||
|
// Otherwise, throttling cases and other non-2xx responses are logged
|
||||||
|
// with a slimmer reference for telemetry/supportability purposes.
|
||||||
if logger.DebugAPI || os.Getenv(logGraphRequestsEnvKey) != "" {
|
if logger.DebugAPI || os.Getenv(logGraphRequestsEnvKey) != "" {
|
||||||
respDump, _ := httputil.DumpResponse(resp, true)
|
respDump, _ := httputil.DumpResponse(resp, true)
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
package support
|
package support
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
absser "github.com/microsoft/kiota-abstractions-go/serialization"
|
absser "github.com/microsoft/kiota-abstractions-go/serialization"
|
||||||
js "github.com/microsoft/kiota-serialization-json-go"
|
js "github.com/microsoft/kiota-serialization-json-go"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
@ -73,14 +71,3 @@ func CreateListFromBytes(bytes []byte) (models.Listable, error) {
|
|||||||
|
|
||||||
return list, nil
|
return list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func HasAttachments(body models.ItemBodyable) bool {
|
|
||||||
if body.GetContent() == nil || body.GetContentType() == nil ||
|
|
||||||
*body.GetContentType() == models.TEXT_BODYTYPE || len(*body.GetContent()) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
content := *body.GetContent()
|
|
||||||
|
|
||||||
return strings.Contains(content, "src=\"cid:")
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package support
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -160,56 +159,3 @@ func (suite *DataSupportSuite) TestCreateListFromBytes() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DataSupportSuite) TestHasAttachments() {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
hasAttachment assert.BoolAssertionFunc
|
|
||||||
getBodyable func(t *testing.T) models.ItemBodyable
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Mock w/out attachment",
|
|
||||||
hasAttachment: assert.False,
|
|
||||||
getBodyable: func(t *testing.T) models.ItemBodyable {
|
|
||||||
byteArray := mockconnector.GetMockMessageWithBodyBytes(
|
|
||||||
"Test",
|
|
||||||
"This is testing",
|
|
||||||
"This is testing",
|
|
||||||
)
|
|
||||||
message, err := CreateMessageFromBytes(byteArray)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return message.GetBody()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mock w/ inline attachment",
|
|
||||||
hasAttachment: assert.True,
|
|
||||||
getBodyable: func(t *testing.T) models.ItemBodyable {
|
|
||||||
byteArray := mockconnector.GetMessageWithOneDriveAttachment("Test legacy")
|
|
||||||
message, err := CreateMessageFromBytes(byteArray)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return message.GetBody()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Edge Case",
|
|
||||||
hasAttachment: assert.True,
|
|
||||||
getBodyable: func(t *testing.T) models.ItemBodyable {
|
|
||||||
//nolint:lll
|
|
||||||
content := "<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style type=\"text/css\" style=\"display:none\">\r\n<!--\r\np\r\n\t{margin-top:0;\r\n\tmargin-bottom:0}\r\n-->\r\n</style></head><body dir=\"ltr\"><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">Happy New Year,</div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">In accordance with TPS report guidelines, there have been questions about how to address our activities SharePoint Cover page. Do you believe this is the best picture? </div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><img class=\"FluidPluginCopy ContentPasted0 w-2070 h-1380\" size=\"5854817\" data-outlook-trace=\"F:1|T:1\" src=\"cid:85f4faa3-9851-40c7-ba0a-e63dce1185f9\" style=\"max-width:100%\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">Let me know if this meets our culture requirements.</div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">Warm Regards,</div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\"><br></div><div class=\"elementToProof\" style=\"font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0); background-color:rgb(255,255,255)\">Dustin</div></body></html>"
|
|
||||||
body := models.NewItemBody()
|
|
||||||
body.SetContent(&content)
|
|
||||||
cat := models.HTML_BODYTYPE
|
|
||||||
body.SetContentType(&cat)
|
|
||||||
return body
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
found := HasAttachments(test.getBodyable(t))
|
|
||||||
test.hasAttachment(t, found)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user