cleanup exchange api code, move out of support (#3455)

normalizes exchange api naming and client usage.
Also moves serialization and transformation to and from graph client objects out of graph/support and into the api. Some parts of graph/support have been moved into conn/ exchange instead, since they're more oriented around certain forms of object consumption rather than broad object serialization.

Except for different uses of api client graph servicers, all changes are movement/renaming.

---

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

- [x]  No

#### Type of change

- [x] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #1996

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-05-25 17:53:11 -06:00 committed by GitHub
parent d5ffcae94e
commit 874e3c3a50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 787 additions and 836 deletions

View File

@ -8,7 +8,6 @@ import (
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
) )
@ -85,7 +84,7 @@ func uploadAttachment(
// item Attachments to be skipped until the completion of Issue #2353 // item Attachments to be skipped until the completion of Issue #2353
if attachmentType == models.ITEM_ATTACHMENTTYPE { if attachmentType == models.ITEM_ATTACHMENTTYPE {
a, err := support.ToItemAttachment(attachment) a, err := toItemAttachment(attachment)
if err != nil { if err != nil {
logger.CtxErr(ctx, err).Info(fmt.Sprintf("item attachment type not supported: %v", attachmentType)) logger.CtxErr(ctx, err).Info(fmt.Sprintf("item attachment type not supported: %v", attachmentType))
return nil return nil

View File

@ -1,4 +1,4 @@
package support package exchange
import ( import (
"fmt" "fmt"

View File

@ -266,10 +266,14 @@ func createCollections(
) ([]data.BackupCollection, error) { ) ([]data.BackupCollection, error) {
var ( var (
allCollections = make([]data.BackupCollection, 0) allCollections = make([]data.BackupCollection, 0)
ac = api.Client{Credentials: creds}
category = scope.Category().PathType() category = scope.Category().PathType()
) )
ac, err := api.NewClient(creds)
if err != nil {
return nil, clues.Wrap(err, "getting api client").WithClues(ctx)
}
ctx = clues.Add(ctx, "category", category) ctx = clues.Add(ctx, "category", category)
getter, err := getterByType(ac, category) getter, err := getterByType(ac, category)

View File

@ -483,7 +483,7 @@ func (suite *DataCollectionsIntegrationSuite) TestMailSerializationRegression()
continue continue
} }
message, err := support.CreateMessageFromBytes(buf.Bytes()) message, err := api.BytesToMessageable(buf.Bytes())
assert.NotNil(t, message) assert.NotNil(t, message)
assert.NoError(t, err, clues.ToCore(err)) assert.NoError(t, err, clues.ToCore(err))
} }
@ -553,7 +553,7 @@ func (suite *DataCollectionsIntegrationSuite) TestContactSerializationRegression
continue continue
} }
contact, err := support.CreateContactFromBytes(buf.Bytes()) contact, err := api.BytesToContactable(buf.Bytes())
assert.NotNil(t, contact) assert.NotNil(t, contact)
assert.NoError(t, err, "converting contact bytes: "+buf.String(), clues.ToCore(err)) assert.NoError(t, err, "converting contact bytes: "+buf.String(), clues.ToCore(err))
count++ count++
@ -683,7 +683,7 @@ func (suite *DataCollectionsIntegrationSuite) TestEventsSerializationRegression(
continue continue
} }
event, err := support.CreateEventFromBytes(buf.Bytes()) event, err := api.BytesToEventable(buf.Bytes())
assert.NotNil(t, event) assert.NotNil(t, event)
assert.NoError(t, err, "creating event from bytes: "+buf.String(), clues.ToCore(err)) assert.NoError(t, err, "creating event from bytes: "+buf.String(), clues.ToCore(err))
} }

View File

@ -10,8 +10,8 @@ import (
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock" exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"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/services/m365/api"
) )
type ExchangeIteratorSuite struct { type ExchangeIteratorSuite struct {
@ -25,7 +25,7 @@ func TestExchangeIteratorSuite(t *testing.T) {
func (suite *ExchangeIteratorSuite) TestDisplayable() { func (suite *ExchangeIteratorSuite) TestDisplayable() {
t := suite.T() t := suite.T()
bytes := exchMock.ContactBytes("Displayable") bytes := exchMock.ContactBytes("Displayable")
contact, err := support.CreateContactFromBytes(bytes) contact, err := api.BytesToContactable(bytes)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
aDisplayable, ok := contact.(graph.Displayable) aDisplayable, ok := contact.(graph.Displayable)
@ -37,7 +37,7 @@ func (suite *ExchangeIteratorSuite) TestDisplayable() {
func (suite *ExchangeIteratorSuite) TestDescendable() { func (suite *ExchangeIteratorSuite) TestDescendable() {
t := suite.T() t := suite.T()
bytes := exchMock.MessageBytes("Descendable") bytes := exchMock.MessageBytes("Descendable")
message, err := support.CreateMessageFromBytes(bytes) message, err := api.BytesToMessageable(bytes)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
aDescendable, ok := message.(graph.Descendable) aDescendable, ok := message.(graph.Descendable)

View File

@ -6,15 +6,14 @@ import (
"testing" "testing"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"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/support"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/services/m365/api"
) )
type MockSuite struct { type MockSuite struct {
@ -78,7 +77,7 @@ func (suite *MockSuite) TestMockExchangeCollection_NewExchangeCollectionMail_Hyd
assert.NoError(t, err, clues.ToCore(err)) assert.NoError(t, err, clues.ToCore(err))
byteArray := buf.Bytes() byteArray := buf.Bytes()
something, err := support.CreateFromBytes(byteArray, models.CreateMessageFromDiscriminatorValue) something, err := api.BytesToMessageable(byteArray)
assert.NoError(t, err, clues.ToCore(err)) assert.NoError(t, err, clues.ToCore(err))
assert.NotNil(t, something) assert.NotNil(t, something)
} }
@ -146,7 +145,7 @@ func (suite *MockExchangeDataSuite) TestMockByteHydration() {
name: "Message Bytes", name: "Message Bytes",
transformation: func(t *testing.T) error { transformation: func(t *testing.T) error {
bytes := MessageBytes(subject) bytes := MessageBytes(subject)
_, err := support.CreateMessageFromBytes(bytes) _, err := api.BytesToMessageable(bytes)
return err return err
}, },
}, },
@ -154,7 +153,7 @@ func (suite *MockExchangeDataSuite) TestMockByteHydration() {
name: "Event Message Response: Regression", name: "Event Message Response: Regression",
transformation: func(t *testing.T) error { transformation: func(t *testing.T) error {
bytes := EventMessageResponse(subject) bytes := EventMessageResponse(subject)
_, err := support.CreateMessageFromBytes(bytes) _, err := api.BytesToEventable(bytes)
return err return err
}, },
}, },
@ -162,7 +161,7 @@ func (suite *MockExchangeDataSuite) TestMockByteHydration() {
name: "Event Message Request: Regression", name: "Event Message Request: Regression",
transformation: func(t *testing.T) error { transformation: func(t *testing.T) error {
bytes := EventMessageRequest(subject) bytes := EventMessageRequest(subject)
_, err := support.CreateMessageFromBytes(bytes) _, err := api.BytesToEventable(bytes)
return err return err
}, },
}, },
@ -170,7 +169,7 @@ func (suite *MockExchangeDataSuite) TestMockByteHydration() {
name: "Contact Bytes", name: "Contact Bytes",
transformation: func(t *testing.T) error { transformation: func(t *testing.T) error {
bytes := ContactBytes(subject) bytes := ContactBytes(subject)
_, err := support.CreateContactFromBytes(bytes) _, err := api.BytesToContactable(bytes)
return err return err
}, },
}, },
@ -178,7 +177,7 @@ func (suite *MockExchangeDataSuite) TestMockByteHydration() {
name: "Event No Attendees Bytes", name: "Event No Attendees Bytes",
transformation: func(t *testing.T) error { transformation: func(t *testing.T) error {
bytes := EventBytes(subject) bytes := EventBytes(subject)
_, err := support.CreateEventFromBytes(bytes) _, err := api.BytesToEventable(bytes)
return err return err
}, },
}, },

View File

@ -65,12 +65,12 @@ func RestoreItem(
// RestoreContact wraps api.Contacts().PostItem() // RestoreContact wraps api.Contacts().PostItem()
func RestoreContact( func RestoreContact(
ctx context.Context, ctx context.Context,
bits []byte, body []byte,
cli itemPoster[models.Contactable], cli itemPoster[models.Contactable],
cp control.CollisionPolicy, cp control.CollisionPolicy,
destination, user string, destination, user string,
) (*details.ExchangeInfo, error) { ) (*details.ExchangeInfo, error) {
contact, err := support.CreateContactFromBytes(bits) contact, err := api.BytesToContactable(body)
if err != nil { if err != nil {
return nil, graph.Wrap(ctx, err, "creating contact from bytes") return nil, graph.Wrap(ctx, err, "creating contact from bytes")
} }
@ -83,7 +83,7 @@ func RestoreContact(
} }
info := api.ContactInfo(contact) info := api.ContactInfo(contact)
info.Size = int64(len(bits)) info.Size = int64(len(body))
return info, nil return info, nil
} }
@ -91,7 +91,7 @@ func RestoreContact(
// RestoreEvent wraps api.Events().PostItem() // RestoreEvent wraps api.Events().PostItem()
func RestoreEvent( func RestoreEvent(
ctx context.Context, ctx context.Context,
bits []byte, body []byte,
itemCli itemPoster[models.Eventable], itemCli itemPoster[models.Eventable],
attachmentCli attachmentPoster, attachmentCli attachmentPoster,
gs graph.Servicer, gs graph.Servicer,
@ -99,7 +99,7 @@ func RestoreEvent(
destination, user string, destination, user string,
errs *fault.Bus, errs *fault.Bus,
) (*details.ExchangeInfo, error) { ) (*details.ExchangeInfo, error) {
event, err := support.CreateEventFromBytes(bits) event, err := api.BytesToEventable(body)
if err != nil { if err != nil {
return nil, clues.Wrap(err, "creating event from bytes").WithClues(ctx) return nil, clues.Wrap(err, "creating event from bytes").WithClues(ctx)
} }
@ -108,7 +108,7 @@ func RestoreEvent(
var ( var (
el = errs.Local() el = errs.Local()
transformedEvent = support.ToEventSimplified(event) transformedEvent = toEventSimplified(event)
attached []models.Attachmentable attached []models.Attachmentable
) )
@ -141,7 +141,7 @@ func RestoreEvent(
} }
info := api.EventInfo(event) info := api.EventInfo(event)
info.Size = int64(len(bits)) info.Size = int64(len(body))
return info, el.Failure() return info, el.Failure()
} }
@ -149,7 +149,7 @@ func RestoreEvent(
// RestoreMessage wraps api.Mail().PostItem(), handling attachment creation along the way // RestoreMessage wraps api.Mail().PostItem(), handling attachment creation along the way
func RestoreMessage( func RestoreMessage(
ctx context.Context, ctx context.Context,
bits []byte, body []byte,
itemCli itemPoster[models.Messageable], itemCli itemPoster[models.Messageable],
attachmentCli attachmentPoster, attachmentCli attachmentPoster,
gs graph.Servicer, gs graph.Servicer,
@ -158,7 +158,7 @@ func RestoreMessage(
errs *fault.Bus, errs *fault.Bus,
) (*details.ExchangeInfo, error) { ) (*details.ExchangeInfo, error) {
// Creates messageable object from original bytes // Creates messageable object from original bytes
msg, err := support.CreateMessageFromBytes(bits) msg, err := api.BytesToMessageable(body)
if err != nil { if err != nil {
return nil, clues.Wrap(err, "creating mail from bytes").WithClues(ctx) return nil, clues.Wrap(err, "creating mail from bytes").WithClues(ctx)
} }
@ -166,7 +166,7 @@ func RestoreMessage(
ctx = clues.Add(ctx, "item_id", ptr.Val(msg.GetId())) ctx = clues.Add(ctx, "item_id", ptr.Val(msg.GetId()))
var ( var (
clone = support.ToMessage(msg) clone = toMessage(msg)
valueID = MailRestorePropertyTag valueID = MailRestorePropertyTag
enableValue = RestoreCanonicalEnableValue enableValue = RestoreCanonicalEnableValue
) )
@ -244,7 +244,7 @@ func RestoreMessage(
} }
} }
return api.MailInfo(clone, int64(len(bits))), el.Failure() return api.MailInfo(clone, int64(len(body))), el.Failure()
} }
// RestoreCollections restores M365 objects in data.RestoreCollection to MSFT // RestoreCollections restores M365 objects in data.RestoreCollection to MSFT

View File

@ -1,4 +1,4 @@
package support package exchange
import ( import (
"fmt" "fmt"
@ -53,7 +53,7 @@ func CloneMessageableFields(orig, message models.Messageable) models.Messageable
return message return message
} }
func ToMessage(orig models.Messageable) models.Messageable { func toMessage(orig models.Messageable) models.Messageable {
message := models.NewMessage() message := models.NewMessage()
temp := CloneMessageableFields(orig, message) temp := CloneMessageableFields(orig, message)
@ -70,7 +70,7 @@ func ToMessage(orig models.Messageable) models.Messageable {
// - Instead of adding attendees and generating spurious notifications, // - Instead of adding attendees and generating spurious notifications,
// add a summary of attendees at the beginning to the event before the original body content // add a summary of attendees at the beginning to the event before the original body content
// - event.attendees is set to an empty list // - event.attendees is set to an empty list
func ToEventSimplified(orig models.Eventable) models.Eventable { func toEventSimplified(orig models.Eventable) models.Eventable {
attendees := FormatAttendees(orig, ptr.Val(orig.GetBody().GetContentType()) == models.HTML_BODYTYPE) attendees := FormatAttendees(orig, ptr.Val(orig.GetBody().GetContentType()) == models.HTML_BODYTYPE)
orig.SetAttendees([]models.Attendeeable{}) orig.SetAttendees([]models.Attendeeable{})
origBody := orig.GetBody() origBody := orig.GetBody()
@ -144,165 +144,6 @@ func insertStringToBody(body getContenter, newContent string) string {
return newContent + content return newContent + content
} }
// CloneListItem creates a new `SharePoint.ListItem` and stores the original item's
// M365 data into it set fields.
// - https://learn.microsoft.com/en-us/graph/api/resources/listitem?view=graph-rest-1.0
func CloneListItem(orig models.ListItemable) models.ListItemable {
newItem := models.NewListItem()
newFieldData := retrieveFieldData(orig.GetFields())
newItem.SetAdditionalData(orig.GetAdditionalData())
newItem.SetAnalytics(orig.GetAnalytics())
newItem.SetContentType(orig.GetContentType())
newItem.SetCreatedBy(orig.GetCreatedBy())
newItem.SetCreatedByUser(orig.GetCreatedByUser())
newItem.SetCreatedDateTime(orig.GetCreatedDateTime())
newItem.SetDescription(orig.GetDescription())
// ETag cannot be carried forward
newItem.SetFields(newFieldData)
newItem.SetLastModifiedBy(orig.GetLastModifiedBy())
newItem.SetLastModifiedByUser(orig.GetLastModifiedByUser())
newItem.SetLastModifiedDateTime(orig.GetLastModifiedDateTime())
newItem.SetOdataType(orig.GetOdataType())
// parentReference and SharePointIDs cause error on upload.
// POST Command will link items to the created list.
newItem.SetVersions(orig.GetVersions())
return newItem
}
// retrieveFieldData utility function to clone raw listItem data from the embedded
// additionalData map
// Further details on FieldValueSets:
// - https://learn.microsoft.com/en-us/graph/api/resources/fieldvalueset?view=graph-rest-1.0
func retrieveFieldData(orig models.FieldValueSetable) models.FieldValueSetable {
fields := models.NewFieldValueSet()
additionalData := make(map[string]any)
fieldData := orig.GetAdditionalData()
// M365 Book keeping values removed during new Item Creation
// Removed Values:
// -- Prefixes -> @odata.context : absolute path to previous list
// . -> @odata.etag : Embedded link to Prior M365 ID
// -- String Match: Read-Only Fields
// -> id : previous un
for key, value := range fieldData {
if strings.HasPrefix(key, "_") || strings.HasPrefix(key, "@") ||
key == "Edit" || key == "Created" || key == "Modified" ||
strings.Contains(key, "LookupId") || strings.Contains(key, "ChildCount") || strings.Contains(key, "LinkTitle") {
continue
}
additionalData[key] = value
}
fields.SetAdditionalData(additionalData)
return fields
}
// ToListable utility function to encapsulate stored data for restoration.
// New Listable omits trackable fields such as `id` or `ETag` and other read-only
// objects that are prevented upon upload. Additionally, read-Only columns are
// not attached in this method.
// ListItems are not included in creation of new list, and have to be restored
// in separate call.
func ToListable(orig models.Listable, displayName string) models.Listable {
newList := models.NewList()
newList.SetContentTypes(orig.GetContentTypes())
newList.SetCreatedBy(orig.GetCreatedBy())
newList.SetCreatedByUser(orig.GetCreatedByUser())
newList.SetCreatedDateTime(orig.GetCreatedDateTime())
newList.SetDescription(orig.GetDescription())
newList.SetDisplayName(&displayName)
newList.SetLastModifiedBy(orig.GetLastModifiedBy())
newList.SetLastModifiedByUser(orig.GetLastModifiedByUser())
newList.SetLastModifiedDateTime(orig.GetLastModifiedDateTime())
newList.SetList(orig.GetList())
newList.SetOdataType(orig.GetOdataType())
newList.SetParentReference(orig.GetParentReference())
columns := make([]models.ColumnDefinitionable, 0)
leg := map[string]struct{}{
"Attachments": {},
"Edit": {},
"Content Type": {},
}
for _, cd := range orig.GetColumns() {
var (
displayName string
readOnly bool
)
if name, ok := ptr.ValOK(cd.GetDisplayName()); ok {
displayName = name
}
if ro, ok := ptr.ValOK(cd.GetReadOnly()); ok {
readOnly = ro
}
_, isLegacy := leg[displayName]
// Skips columns that cannot be uploaded for models.ColumnDefinitionable:
// - ReadOnly, Title, or Legacy columns: Attachments, Edit, or Content Type
if readOnly || displayName == "Title" || isLegacy {
continue
}
columns = append(columns, cloneColumnDefinitionable(cd))
}
newList.SetColumns(columns)
return newList
}
// cloneColumnDefinitionable utility function for encapsulating models.ColumnDefinitionable data
// into new object for upload.
func cloneColumnDefinitionable(orig models.ColumnDefinitionable) models.ColumnDefinitionable {
newColumn := models.NewColumnDefinition()
newColumn.SetAdditionalData(orig.GetAdditionalData())
newColumn.SetBoolean(orig.GetBoolean())
newColumn.SetCalculated(orig.GetCalculated())
newColumn.SetChoice(orig.GetChoice())
newColumn.SetColumnGroup(orig.GetColumnGroup())
newColumn.SetContentApprovalStatus(orig.GetContentApprovalStatus())
newColumn.SetCurrency(orig.GetCurrency())
newColumn.SetDateTime(orig.GetDateTime())
newColumn.SetDefaultValue(orig.GetDefaultValue())
newColumn.SetDescription(orig.GetDescription())
newColumn.SetDisplayName(orig.GetDisplayName())
newColumn.SetEnforceUniqueValues(orig.GetEnforceUniqueValues())
newColumn.SetGeolocation(orig.GetGeolocation())
newColumn.SetHidden(orig.GetHidden())
newColumn.SetHyperlinkOrPicture(orig.GetHyperlinkOrPicture())
newColumn.SetIndexed(orig.GetIndexed())
newColumn.SetIsDeletable(orig.GetIsDeletable())
newColumn.SetIsReorderable(orig.GetIsReorderable())
newColumn.SetIsSealed(orig.GetIsSealed())
newColumn.SetLookup(orig.GetLookup())
newColumn.SetName(orig.GetName())
newColumn.SetNumber(orig.GetNumber())
newColumn.SetOdataType(orig.GetOdataType())
newColumn.SetPersonOrGroup(orig.GetPersonOrGroup())
newColumn.SetPropagateChanges(orig.GetPropagateChanges())
newColumn.SetReadOnly(orig.GetReadOnly())
newColumn.SetRequired(orig.GetRequired())
newColumn.SetSourceColumn(orig.GetSourceColumn())
newColumn.SetSourceContentType(orig.GetSourceContentType())
newColumn.SetTerm(orig.GetTerm())
newColumn.SetText(orig.GetText())
newColumn.SetThumbnail(orig.GetThumbnail())
newColumn.SetType(orig.GetType())
newColumn.SetValidation(orig.GetValidation())
return newColumn
}
// =============================================================================================== // ===============================================================================================
// Sanitization section // Sanitization section
// Set of functions that support ItemAttachemtable object restoration. // Set of functions that support ItemAttachemtable object restoration.
@ -329,9 +170,9 @@ const (
contactItemType = "#microsoft.graph.contact" contactItemType = "#microsoft.graph.contact"
) )
// ToItemAttachment transforms internal item, OutlookItemables, into // toItemAttachment transforms internal item, OutlookItemables, into
// objects that are able to be uploaded into M365. // objects that are able to be uploaded into M365.
func ToItemAttachment(orig models.Attachmentable) (models.Attachmentable, error) { func toItemAttachment(orig models.Attachmentable) (models.Attachmentable, error) {
transform, ok := orig.(models.ItemAttachmentable) transform, ok := orig.(models.ItemAttachmentable)
if !ok { // Shouldn't ever happen if !ok { // Shouldn't ever happen
return nil, clues.New("transforming attachment to item attachment") return nil, clues.New("transforming attachment to item attachment")
@ -452,7 +293,7 @@ func sanitizeEvent(orig models.Eventable) (models.Eventable, error) {
} }
func sanitizeMessage(orig models.Messageable) (models.Messageable, error) { func sanitizeMessage(orig models.Messageable) (models.Messageable, error) {
message := ToMessage(orig) message := toMessage(orig)
// TODO #2428 (dadam39): re-apply nested attachments for itemAttachments // TODO #2428 (dadam39): re-apply nested attachments for itemAttachments
// Upstream: https://github.com/microsoft/kiota-serialization-json-go/issues/61 // Upstream: https://github.com/microsoft/kiota-serialization-json-go/issues/61

View File

@ -1,4 +1,4 @@
package support package exchange
import ( import (
"testing" "testing"
@ -12,24 +12,25 @@ import (
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock" exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/services/m365/api"
) )
type SupportTestSuite struct { type TransformUnitTest struct {
tester.Suite tester.Suite
} }
func TestSupportTestSuite(t *testing.T) { func TestSupportTestSuite(t *testing.T) {
suite.Run(t, &SupportTestSuite{Suite: tester.NewUnitSuite(t)}) suite.Run(t, &TransformUnitTest{Suite: tester.NewUnitSuite(t)})
} }
func (suite *SupportTestSuite) TestToMessage() { func (suite *TransformUnitTest) TestToMessage() {
t := suite.T() t := suite.T()
bytes := exchMock.MessageBytes("m365 mail support test") bytes := exchMock.MessageBytes("m365 mail support test")
message, err := CreateMessageFromBytes(bytes) message, err := api.BytesToMessageable(bytes)
require.NoError(suite.T(), err, clues.ToCore(err)) require.NoError(suite.T(), err, clues.ToCore(err))
clone := ToMessage(message) clone := toMessage(message)
assert.Equal(t, message.GetBccRecipients(), clone.GetBccRecipients()) assert.Equal(t, message.GetBccRecipients(), clone.GetBccRecipients())
assert.Equal(t, message.GetSubject(), clone.GetSubject()) assert.Equal(t, message.GetSubject(), clone.GetSubject())
assert.Equal(t, message.GetSender(), clone.GetSender()) assert.Equal(t, message.GetSender(), clone.GetSender())
@ -37,14 +38,14 @@ func (suite *SupportTestSuite) TestToMessage() {
assert.NotEqual(t, message.GetId(), clone.GetId()) assert.NotEqual(t, message.GetId(), clone.GetId())
} }
func (suite *SupportTestSuite) TestToEventSimplified_attendees() { func (suite *TransformUnitTest) TestToEventSimplified_attendees() {
t := suite.T() t := suite.T()
bytes := exchMock.EventWithAttendeesBytes("M365 Event Support Test") bytes := exchMock.EventWithAttendeesBytes("M365 Event Support Test")
event, err := CreateEventFromBytes(bytes) event, err := api.BytesToEventable(bytes)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
attendees := event.GetAttendees() attendees := event.GetAttendees()
newEvent := ToEventSimplified(event) newEvent := toEventSimplified(event)
assert.Empty(t, newEvent.GetHideAttendees()) assert.Empty(t, newEvent.GetHideAttendees())
assert.Equal(t, ptr.Val(event.GetBody().GetContentType()), ptr.Val(newEvent.GetBody().GetContentType())) assert.Equal(t, ptr.Val(event.GetBody().GetContentType()), ptr.Val(newEvent.GetBody().GetContentType()))
@ -57,7 +58,7 @@ func (suite *SupportTestSuite) TestToEventSimplified_attendees() {
} }
} }
func (suite *SupportTestSuite) TestToEventSimplified_recurrence() { func (suite *TransformUnitTest) TestToEventSimplified_recurrence() {
var ( var (
t = suite.T() t = suite.T()
subject = "M365 Event Support Test" subject = "M365 Event Support Test"
@ -72,7 +73,7 @@ func (suite *SupportTestSuite) TestToEventSimplified_recurrence() {
name: "Test recurrence: Unspecified", name: "Test recurrence: Unspecified",
event: func() models.Eventable { event: func() models.Eventable {
bytes := exchMock.EventWithSubjectBytes(subject) bytes := exchMock.EventWithSubjectBytes(subject)
e, err := CreateEventFromBytes(bytes) e, err := api.BytesToEventable(bytes)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
return e return e
}, },
@ -85,7 +86,7 @@ func (suite *SupportTestSuite) TestToEventSimplified_recurrence() {
name: "Test recurrenceTimeZone: Unspecified", name: "Test recurrenceTimeZone: Unspecified",
event: func() models.Eventable { event: func() models.Eventable {
bytes := exchMock.EventWithRecurrenceBytes(subject, `null`) bytes := exchMock.EventWithRecurrenceBytes(subject, `null`)
e, err := CreateEventFromBytes(bytes) e, err := api.BytesToEventable(bytes)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
return e return e
}, },
@ -98,7 +99,7 @@ func (suite *SupportTestSuite) TestToEventSimplified_recurrence() {
name: "Test recurrenceTimeZone: Empty", name: "Test recurrenceTimeZone: Empty",
event: func() models.Eventable { event: func() models.Eventable {
bytes := exchMock.EventWithRecurrenceBytes(subject, `""`) bytes := exchMock.EventWithRecurrenceBytes(subject, `""`)
event, err := CreateEventFromBytes(bytes) event, err := api.BytesToEventable(bytes)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
return event return event
}, },
@ -111,7 +112,7 @@ func (suite *SupportTestSuite) TestToEventSimplified_recurrence() {
name: "Test recurrenceTimeZone: Valid", name: "Test recurrenceTimeZone: Valid",
event: func() models.Eventable { event: func() models.Eventable {
bytes := exchMock.EventWithRecurrenceBytes(subject, `"Pacific Standard Time"`) bytes := exchMock.EventWithRecurrenceBytes(subject, `"Pacific Standard Time"`)
event, err := CreateEventFromBytes(bytes) event, err := api.BytesToEventable(bytes)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
return event return event
}, },
@ -125,7 +126,7 @@ func (suite *SupportTestSuite) TestToEventSimplified_recurrence() {
for _, test := range tests { for _, test := range tests {
suite.Run(test.name, func() { suite.Run(test.name, func() {
event := test.event() event := test.event()
newEvent := ToEventSimplified(event) newEvent := toEventSimplified(event)
assert.True(t, test.validateOutput(newEvent), test.name) assert.True(t, test.validateOutput(newEvent), test.name)
}) })
} }
@ -148,7 +149,7 @@ func makeMockContent(c string, ct models.BodyType) mockContenter {
return mockContenter{&c, &ct} return mockContenter{&c, &ct}
} }
func (suite *SupportTestSuite) TestInsertStringToBody() { func (suite *TransformUnitTest) TestInsertStringToBody() {
nilTextContent := makeMockContent("", models.TEXT_BODYTYPE) nilTextContent := makeMockContent("", models.TEXT_BODYTYPE)
nilTextContent.content = nil nilTextContent.content = nil
nilHTMLContent := makeMockContent("", models.HTML_BODYTYPE) nilHTMLContent := makeMockContent("", models.HTML_BODYTYPE)

View File

@ -18,7 +18,6 @@ import (
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/onedrive" "github.com/alcionai/corso/src/internal/connector/onedrive"
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata" "github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
@ -582,7 +581,7 @@ func compareExchangeEmail(
return return
} }
itemMessage, err := support.CreateMessageFromBytes(itemData) itemMessage, err := api.BytesToMessageable(itemData)
if !assert.NoError(t, err, "deserializing backed up message", clues.ToCore(err)) { if !assert.NoError(t, err, "deserializing backed up message", clues.ToCore(err)) {
return return
} }
@ -592,7 +591,7 @@ func compareExchangeEmail(
return return
} }
expectedMessage, err := support.CreateMessageFromBytes(expectedBytes) expectedMessage, err := api.BytesToMessageable(expectedBytes)
assert.NoError(t, err, "deserializing source message", clues.ToCore(err)) assert.NoError(t, err, "deserializing source message", clues.ToCore(err))
checkMessage(t, expectedMessage, itemMessage) checkMessage(t, expectedMessage, itemMessage)
@ -609,7 +608,7 @@ func compareExchangeContact(
return return
} }
itemContact, err := support.CreateContactFromBytes(itemData) itemContact, err := api.BytesToContactable(itemData)
if !assert.NoError(t, err, "deserializing backed up contact", clues.ToCore(err)) { if !assert.NoError(t, err, "deserializing backed up contact", clues.ToCore(err)) {
return return
} }
@ -619,7 +618,7 @@ func compareExchangeContact(
return return
} }
expectedContact, err := support.CreateContactFromBytes(expectedBytes) expectedContact, err := api.BytesToContactable(expectedBytes)
if !assert.NoError(t, err, "deserializing source contact") { if !assert.NoError(t, err, "deserializing source contact") {
return return
} }
@ -637,7 +636,7 @@ func compareExchangeEvent(
return return
} }
itemEvent, err := support.CreateEventFromBytes(itemData) itemEvent, err := api.BytesToEventable(itemData)
if !assert.NoError(t, err, "deserializing backed up contact", clues.ToCore(err)) { if !assert.NoError(t, err, "deserializing backed up contact", clues.ToCore(err)) {
return return
} }
@ -647,7 +646,7 @@ func compareExchangeEvent(
return return
} }
expectedEvent, err := support.CreateEventFromBytes(expectedBytes) expectedEvent, err := api.BytesToEventable(expectedBytes)
assert.NoError(t, err, "deserializing source contact", clues.ToCore(err)) assert.NoError(t, err, "deserializing source contact", clues.ToCore(err))
checkEvent(t, expectedEvent, itemEvent) checkEvent(t, expectedEvent, itemEvent)

View File

@ -12,7 +12,6 @@ import (
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
betamodels "github.com/alcionai/corso/src/internal/connector/graph/betasdk/models" betamodels "github.com/alcionai/corso/src/internal/connector/graph/betasdk/models"
betasites "github.com/alcionai/corso/src/internal/connector/graph/betasdk/sites" betasites "github.com/alcionai/corso/src/internal/connector/graph/betasdk/sites"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/diagnostics" "github.com/alcionai/corso/src/internal/diagnostics"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
@ -190,7 +189,7 @@ func RestoreSitePage(
} }
// Hydrate Page // Hydrate Page
page, err := support.CreatePageFromBytes(byteArray) page, err := CreatePageFromBytes(byteArray)
if err != nil { if err != nil {
return dii, clues.Wrap(err, "creating Page object").WithClues(ctx) return dii, clues.Wrap(err, "creating Page object").WithClues(ctx)
} }

View File

@ -0,0 +1,212 @@
package api
import (
"strings"
"github.com/alcionai/clues"
"github.com/microsoft/kiota-abstractions-go/serialization"
kjson "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/alcionai/corso/src/internal/common/ptr"
betamodels "github.com/alcionai/corso/src/internal/connector/graph/betasdk/models"
)
// createFromBytes generates an m365 object form bytes.
func createFromBytes(
bytes []byte,
createFunc serialization.ParsableFactory,
) (serialization.Parsable, error) {
parseNode, err := kjson.NewJsonParseNodeFactory().GetRootParseNode("application/json", bytes)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes into base m365 object")
}
v, err := parseNode.GetObjectValue(createFunc)
if err != nil {
return nil, clues.Wrap(err, "parsing m365 object factory")
}
return v, nil
}
func CreateListFromBytes(bytes []byte) (models.Listable, error) {
parsable, err := createFromBytes(bytes, models.CreateListFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to sharepoint list")
}
list := parsable.(models.Listable)
return list, nil
}
func CreatePageFromBytes(bytes []byte) (betamodels.SitePageable, error) {
parsable, err := createFromBytes(bytes, betamodels.CreateSitePageFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to sharepoint page")
}
page := parsable.(betamodels.SitePageable)
return page, nil
}
// ToListable utility function to encapsulate stored data for restoration.
// New Listable omits trackable fields such as `id` or `ETag` and other read-only
// objects that are prevented upon upload. Additionally, read-Only columns are
// not attached in this method.
// ListItems are not included in creation of new list, and have to be restored
// in separate call.
func ToListable(orig models.Listable, displayName string) models.Listable {
newList := models.NewList()
newList.SetContentTypes(orig.GetContentTypes())
newList.SetCreatedBy(orig.GetCreatedBy())
newList.SetCreatedByUser(orig.GetCreatedByUser())
newList.SetCreatedDateTime(orig.GetCreatedDateTime())
newList.SetDescription(orig.GetDescription())
newList.SetDisplayName(&displayName)
newList.SetLastModifiedBy(orig.GetLastModifiedBy())
newList.SetLastModifiedByUser(orig.GetLastModifiedByUser())
newList.SetLastModifiedDateTime(orig.GetLastModifiedDateTime())
newList.SetList(orig.GetList())
newList.SetOdataType(orig.GetOdataType())
newList.SetParentReference(orig.GetParentReference())
columns := make([]models.ColumnDefinitionable, 0)
leg := map[string]struct{}{
"Attachments": {},
"Edit": {},
"Content Type": {},
}
for _, cd := range orig.GetColumns() {
var (
displayName string
readOnly bool
)
if name, ok := ptr.ValOK(cd.GetDisplayName()); ok {
displayName = name
}
if ro, ok := ptr.ValOK(cd.GetReadOnly()); ok {
readOnly = ro
}
_, isLegacy := leg[displayName]
// Skips columns that cannot be uploaded for models.ColumnDefinitionable:
// - ReadOnly, Title, or Legacy columns: Attachments, Edit, or Content Type
if readOnly || displayName == "Title" || isLegacy {
continue
}
columns = append(columns, cloneColumnDefinitionable(cd))
}
newList.SetColumns(columns)
return newList
}
// cloneColumnDefinitionable utility function for encapsulating models.ColumnDefinitionable data
// into new object for upload.
func cloneColumnDefinitionable(orig models.ColumnDefinitionable) models.ColumnDefinitionable {
newColumn := models.NewColumnDefinition()
newColumn.SetAdditionalData(orig.GetAdditionalData())
newColumn.SetBoolean(orig.GetBoolean())
newColumn.SetCalculated(orig.GetCalculated())
newColumn.SetChoice(orig.GetChoice())
newColumn.SetColumnGroup(orig.GetColumnGroup())
newColumn.SetContentApprovalStatus(orig.GetContentApprovalStatus())
newColumn.SetCurrency(orig.GetCurrency())
newColumn.SetDateTime(orig.GetDateTime())
newColumn.SetDefaultValue(orig.GetDefaultValue())
newColumn.SetDescription(orig.GetDescription())
newColumn.SetDisplayName(orig.GetDisplayName())
newColumn.SetEnforceUniqueValues(orig.GetEnforceUniqueValues())
newColumn.SetGeolocation(orig.GetGeolocation())
newColumn.SetHidden(orig.GetHidden())
newColumn.SetHyperlinkOrPicture(orig.GetHyperlinkOrPicture())
newColumn.SetIndexed(orig.GetIndexed())
newColumn.SetIsDeletable(orig.GetIsDeletable())
newColumn.SetIsReorderable(orig.GetIsReorderable())
newColumn.SetIsSealed(orig.GetIsSealed())
newColumn.SetLookup(orig.GetLookup())
newColumn.SetName(orig.GetName())
newColumn.SetNumber(orig.GetNumber())
newColumn.SetOdataType(orig.GetOdataType())
newColumn.SetPersonOrGroup(orig.GetPersonOrGroup())
newColumn.SetPropagateChanges(orig.GetPropagateChanges())
newColumn.SetReadOnly(orig.GetReadOnly())
newColumn.SetRequired(orig.GetRequired())
newColumn.SetSourceColumn(orig.GetSourceColumn())
newColumn.SetSourceContentType(orig.GetSourceContentType())
newColumn.SetTerm(orig.GetTerm())
newColumn.SetText(orig.GetText())
newColumn.SetThumbnail(orig.GetThumbnail())
newColumn.SetType(orig.GetType())
newColumn.SetValidation(orig.GetValidation())
return newColumn
}
// CloneListItem creates a new `SharePoint.ListItem` and stores the original item's
// M365 data into it set fields.
// - https://learn.microsoft.com/en-us/graph/api/resources/listitem?view=graph-rest-1.0
func CloneListItem(orig models.ListItemable) models.ListItemable {
newItem := models.NewListItem()
newFieldData := retrieveFieldData(orig.GetFields())
newItem.SetAdditionalData(orig.GetAdditionalData())
newItem.SetAnalytics(orig.GetAnalytics())
newItem.SetContentType(orig.GetContentType())
newItem.SetCreatedBy(orig.GetCreatedBy())
newItem.SetCreatedByUser(orig.GetCreatedByUser())
newItem.SetCreatedDateTime(orig.GetCreatedDateTime())
newItem.SetDescription(orig.GetDescription())
// ETag cannot be carried forward
newItem.SetFields(newFieldData)
newItem.SetLastModifiedBy(orig.GetLastModifiedBy())
newItem.SetLastModifiedByUser(orig.GetLastModifiedByUser())
newItem.SetLastModifiedDateTime(orig.GetLastModifiedDateTime())
newItem.SetOdataType(orig.GetOdataType())
// parentReference and SharePointIDs cause error on upload.
// POST Command will link items to the created list.
newItem.SetVersions(orig.GetVersions())
return newItem
}
// retrieveFieldData utility function to clone raw listItem data from the embedded
// additionalData map
// Further details on FieldValueSets:
// - https://learn.microsoft.com/en-us/graph/api/resources/fieldvalueset?view=graph-rest-1.0
func retrieveFieldData(orig models.FieldValueSetable) models.FieldValueSetable {
fields := models.NewFieldValueSet()
additionalData := make(map[string]any)
fieldData := orig.GetAdditionalData()
// M365 Book keeping values removed during new Item Creation
// Removed Values:
// -- Prefixes -> @odata.context : absolute path to previous list
// . -> @odata.etag : Embedded link to Prior M365 ID
// -- String Match: Read-Only Fields
// -> id : previous un
for key, value := range fieldData {
if strings.HasPrefix(key, "_") || strings.HasPrefix(key, "@") ||
key == "Edit" || key == "Created" || key == "Modified" ||
strings.Contains(key, "LookupId") || strings.Contains(key, "ChildCount") || strings.Contains(key, "LinkTitle") {
continue
}
additionalData[key] = value
}
fields.SetAdditionalData(additionalData)
return fields
}

View File

@ -0,0 +1,126 @@
package api
import (
"testing"
"github.com/alcionai/clues"
kioser "github.com/microsoft/kiota-serialization-json-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
bmodels "github.com/alcionai/corso/src/internal/connector/graph/betasdk/models"
spMock "github.com/alcionai/corso/src/internal/connector/sharepoint/mock"
"github.com/alcionai/corso/src/internal/tester"
)
type SerializationUnitSuite struct {
tester.Suite
}
func TestDataSupportSuite(t *testing.T) {
suite.Run(t, &SerializationUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *SerializationUnitSuite) TestCreateListFromBytes() {
listBytes, err := spMock.ListBytes("DataSupportSuite")
require.NoError(suite.T(), err)
tests := []struct {
name string
byteArray []byte
checkError assert.ErrorAssertionFunc
isNil assert.ValueAssertionFunc
}{
{
name: "empty bytes",
byteArray: make([]byte, 0),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: "invalid bytes",
byteArray: []byte("Invalid byte stream \"subject:\" Not going to work"),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: "Valid List",
byteArray: listBytes,
checkError: assert.NoError,
isNil: assert.NotNil,
},
}
for _, test := range tests {
suite.Run(test.name, func() {
t := suite.T()
result, err := CreateListFromBytes(test.byteArray)
test.checkError(t, err, clues.ToCore(err))
test.isNil(t, result)
})
}
}
func (suite *SerializationUnitSuite) TestCreatePageFromBytes() {
tests := []struct {
name string
checkError assert.ErrorAssertionFunc
isNil assert.ValueAssertionFunc
getBytes func(t *testing.T) []byte
}{
{
"empty bytes",
assert.Error,
assert.Nil,
func(t *testing.T) []byte {
return make([]byte, 0)
},
},
{
"invalid bytes",
assert.Error,
assert.Nil,
func(t *testing.T) []byte {
return []byte("snarf")
},
},
{
"Valid Page",
assert.NoError,
assert.NotNil,
func(t *testing.T) []byte {
pg := bmodels.NewSitePage()
title := "Tested"
pg.SetTitle(&title)
pg.SetName(&title)
pg.SetWebUrl(&title)
writer := kioser.NewJsonSerializationWriter()
err := writer.WriteObjectValue("", pg)
require.NoError(t, err, clues.ToCore(err))
byteArray, err := writer.GetSerializedContent()
require.NoError(t, err, clues.ToCore(err))
return byteArray
},
},
}
for _, test := range tests {
suite.Run(test.name, func() {
t := suite.T()
result, err := CreatePageFromBytes(test.getBytes(t))
test.checkError(t, err)
test.isNil(t, result)
if result != nil {
assert.Equal(t, "Tested", *result.GetName(), "name")
assert.Equal(t, "Tested", *result.GetTitle(), "title")
assert.Equal(t, "Tested", *result.GetWebUrl(), "webURL")
}
})
}
}

View File

@ -15,7 +15,6 @@ import (
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/sharepoint/api" "github.com/alcionai/corso/src/internal/connector/sharepoint/api"
spMock "github.com/alcionai/corso/src/internal/connector/sharepoint/mock" spMock "github.com/alcionai/corso/src/internal/connector/sharepoint/mock"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"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"
@ -131,7 +130,7 @@ func (suite *SharePointCollectionSuite) TestCollection_Items() {
}, },
getItem: func(t *testing.T, itemName string) *Item { getItem: func(t *testing.T, itemName string) *Item {
byteArray := spMock.Page(itemName) byteArray := spMock.Page(itemName)
page, err := support.CreatePageFromBytes(byteArray) page, err := api.CreatePageFromBytes(byteArray)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
data := &Item{ data := &Item{

View File

@ -9,7 +9,7 @@ import (
"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/support" "github.com/alcionai/corso/src/internal/connector/sharepoint/api"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
) )
@ -39,7 +39,7 @@ func (suite *MockSuite) TestMockByteHydration() {
bytes, err := writer.GetSerializedContent() bytes, err := writer.GetSerializedContent()
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
_, err = support.CreateListFromBytes(bytes) _, err = api.CreateListFromBytes(bytes)
return err return err
}, },
@ -49,7 +49,7 @@ func (suite *MockSuite) TestMockByteHydration() {
transformation: func(t *testing.T) error { transformation: func(t *testing.T) error {
bytes, err := ListBytes(subject) bytes, err := ListBytes(subject)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
_, err = support.CreateListFromBytes(bytes) _, err = api.CreateListFromBytes(bytes)
return err return err
}, },
}, },
@ -57,7 +57,7 @@ func (suite *MockSuite) TestMockByteHydration() {
name: "SharePoint: Page", name: "SharePoint: Page",
transformation: func(t *testing.T) error { transformation: func(t *testing.T) error {
bytes := Page(subject) bytes := Page(subject)
_, err := support.CreatePageFromBytes(bytes) _, err := api.CreatePageFromBytes(bytes)
return err return err
}, },

View File

@ -160,7 +160,7 @@ func restoreListItem(
return dii, clues.Wrap(err, "reading backup data").WithClues(ctx) return dii, clues.Wrap(err, "reading backup data").WithClues(ctx)
} }
oldList, err := support.CreateListFromBytes(byteArray) oldList, err := betaAPI.CreateListFromBytes(byteArray)
if err != nil { if err != nil {
return dii, clues.Wrap(err, "creating item").WithClues(ctx) return dii, clues.Wrap(err, "creating item").WithClues(ctx)
} }
@ -171,12 +171,12 @@ func restoreListItem(
var ( var (
newName = fmt.Sprintf("%s_%s", destName, listName) newName = fmt.Sprintf("%s_%s", destName, listName)
newList = support.ToListable(oldList, newName) newList = betaAPI.ToListable(oldList, newName)
contents = make([]models.ListItemable, 0) contents = make([]models.ListItemable, 0)
) )
for _, itm := range oldList.GetItems() { for _, itm := range oldList.GetItems() {
temp := support.CloneListItem(itm) temp := betaAPI.CloneListItem(itm)
contents = append(contents, temp) contents = append(contents, temp)
} }

View File

@ -1,87 +0,0 @@
package support
import (
"github.com/alcionai/clues"
"github.com/microsoft/kiota-abstractions-go/serialization"
kjson "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models"
betamodels "github.com/alcionai/corso/src/internal/connector/graph/betasdk/models"
)
// CreateFromBytes helper function to initialize m365 object form bytes.
// @param bytes -> source, createFunc -> abstract function for initialization
func CreateFromBytes(bytes []byte, createFunc serialization.ParsableFactory) (serialization.Parsable, error) {
parseNode, err := kjson.NewJsonParseNodeFactory().GetRootParseNode("application/json", bytes)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes into base m365 object")
}
anObject, err := parseNode.GetObjectValue(createFunc)
if err != nil {
return nil, clues.Wrap(err, "parsing m365 object factory")
}
return anObject, nil
}
// CreateMessageFromBytes function to transform bytes into Messageable object
func CreateMessageFromBytes(bytes []byte) (models.Messageable, error) {
aMessage, err := CreateFromBytes(bytes, models.CreateMessageFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to exchange message")
}
message := aMessage.(models.Messageable)
return message, nil
}
// CreateContactFromBytes function to transform bytes into Contactable object
// Error returned if ParsableFactory function does not accept given bytes
func CreateContactFromBytes(bytes []byte) (models.Contactable, error) {
parsable, err := CreateFromBytes(bytes, models.CreateContactFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to exchange contact")
}
contact := parsable.(models.Contactable)
return contact, nil
}
// CreateEventFromBytes transforms given bytes into models.Eventable object
func CreateEventFromBytes(bytes []byte) (models.Eventable, error) {
parsable, err := CreateFromBytes(bytes, models.CreateEventFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to exchange event")
}
event := parsable.(models.Eventable)
return event, nil
}
// CreateListFromBytes transforms given bytes into models.Listable object
func CreateListFromBytes(bytes []byte) (models.Listable, error) {
parsable, err := CreateFromBytes(bytes, models.CreateListFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to sharepoint list")
}
list := parsable.(models.Listable)
return list, nil
}
// CreatePageFromBytes transforms given bytes in models.SitePageable object
func CreatePageFromBytes(bytes []byte) (betamodels.SitePageable, error) {
parsable, err := CreateFromBytes(bytes, betamodels.CreateSitePageFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to sharepoint page")
}
page := parsable.(betamodels.SitePageable)
return page, nil
}

View File

@ -1,241 +0,0 @@
package support
import (
"testing"
"github.com/alcionai/clues"
kioser "github.com/microsoft/kiota-serialization-json-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
bmodels "github.com/alcionai/corso/src/internal/connector/graph/betasdk/models"
spMock "github.com/alcionai/corso/src/internal/connector/sharepoint/mock"
"github.com/alcionai/corso/src/internal/tester"
)
type DataSupportSuite struct {
tester.Suite
}
func TestDataSupportSuite(t *testing.T) {
suite.Run(t, &DataSupportSuite{Suite: tester.NewUnitSuite(t)})
}
var (
empty = "Empty Bytes"
invalid = "Invalid Bytes"
)
// TestCreateMessageFromBytes verifies approved mockdata bytes can
// be successfully transformed into M365 Message data.
func (suite *DataSupportSuite) TestCreateMessageFromBytes() {
table := []struct {
name string
byteArray []byte
checkError assert.ErrorAssertionFunc
checkObject assert.ValueAssertionFunc
}{
{
name: "Empty Bytes",
byteArray: make([]byte, 0),
checkError: assert.Error,
checkObject: assert.Nil,
},
{
name: "aMessage bytes",
byteArray: exchMock.MessageBytes("m365 mail support test"),
checkError: assert.NoError,
checkObject: assert.NotNil,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
result, err := CreateMessageFromBytes(test.byteArray)
test.checkError(t, err, clues.ToCore(err))
test.checkObject(t, result)
})
}
}
// TestCreateContactFromBytes verifies behavior of CreateContactFromBytes
// by ensuring correct error and object output.
func (suite *DataSupportSuite) TestCreateContactFromBytes() {
table := []struct {
name string
byteArray []byte
checkError assert.ErrorAssertionFunc
isNil assert.ValueAssertionFunc
}{
{
name: empty,
byteArray: make([]byte, 0),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: invalid,
byteArray: []byte("A random sentence doesn't make an object"),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: "Valid Contact",
byteArray: exchMock.ContactBytes("Support Test"),
checkError: assert.NoError,
isNil: assert.NotNil,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
result, err := CreateContactFromBytes(test.byteArray)
test.checkError(t, err, clues.ToCore(err))
test.isNil(t, result)
})
}
}
func (suite *DataSupportSuite) TestCreateEventFromBytes() {
tests := []struct {
name string
byteArray []byte
checkError assert.ErrorAssertionFunc
isNil assert.ValueAssertionFunc
}{
{
name: empty,
byteArray: make([]byte, 0),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: invalid,
byteArray: []byte("Invalid byte stream \"subject:\" Not going to work"),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: "Valid Event",
byteArray: exchMock.EventBytes("Event Test"),
checkError: assert.NoError,
isNil: assert.NotNil,
},
}
for _, test := range tests {
suite.Run(test.name, func() {
t := suite.T()
result, err := CreateEventFromBytes(test.byteArray)
test.checkError(t, err, clues.ToCore(err))
test.isNil(t, result)
})
}
}
func (suite *DataSupportSuite) TestCreateListFromBytes() {
listBytes, err := spMock.ListBytes("DataSupportSuite")
require.NoError(suite.T(), err)
tests := []struct {
name string
byteArray []byte
checkError assert.ErrorAssertionFunc
isNil assert.ValueAssertionFunc
}{
{
name: empty,
byteArray: make([]byte, 0),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: invalid,
byteArray: []byte("Invalid byte stream \"subject:\" Not going to work"),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: "Valid List",
byteArray: listBytes,
checkError: assert.NoError,
isNil: assert.NotNil,
},
}
for _, test := range tests {
suite.Run(test.name, func() {
t := suite.T()
result, err := CreateListFromBytes(test.byteArray)
test.checkError(t, err, clues.ToCore(err))
test.isNil(t, result)
})
}
}
func (suite *DataSupportSuite) TestCreatePageFromBytes() {
tests := []struct {
name string
checkError assert.ErrorAssertionFunc
isNil assert.ValueAssertionFunc
getBytes func(t *testing.T) []byte
}{
{
empty,
assert.Error,
assert.Nil,
func(t *testing.T) []byte {
return make([]byte, 0)
},
},
{
invalid,
assert.Error,
assert.Nil,
func(t *testing.T) []byte {
return []byte("snarf")
},
},
{
"Valid Page",
assert.NoError,
assert.NotNil,
func(t *testing.T) []byte {
pg := bmodels.NewSitePage()
title := "Tested"
pg.SetTitle(&title)
pg.SetName(&title)
pg.SetWebUrl(&title)
writer := kioser.NewJsonSerializationWriter()
err := writer.WriteObjectValue("", pg)
require.NoError(t, err, clues.ToCore(err))
byteArray, err := writer.GetSerializedContent()
require.NoError(t, err, clues.ToCore(err))
return byteArray
},
},
}
for _, test := range tests {
suite.Run(test.name, func() {
t := suite.T()
result, err := CreatePageFromBytes(test.getBytes(t))
test.checkError(t, err)
test.isNil(t, result)
if result != nil {
assert.Equal(t, "Tested", *result.GetName(), "name")
assert.Equal(t, "Tested", *result.GetTitle(), "title")
assert.Equal(t, "Tested", *result.GetWebUrl(), "webURL")
}
})
}
}

View File

@ -29,7 +29,6 @@ import (
"github.com/alcionai/corso/src/internal/connector/onedrive" "github.com/alcionai/corso/src/internal/connector/onedrive"
odConsts "github.com/alcionai/corso/src/internal/connector/onedrive/consts" odConsts "github.com/alcionai/corso/src/internal/connector/onedrive/consts"
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata" "github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/events" "github.com/alcionai/corso/src/internal/events"
evmock "github.com/alcionai/corso/src/internal/events/mock" evmock "github.com/alcionai/corso/src/internal/events/mock"
@ -1136,7 +1135,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
switch category { switch category {
case path.EmailCategory: case path.EmailCategory:
_, itemData := generateItemData(t, category, uidn.ID(), mailDBF) _, itemData := generateItemData(t, category, uidn.ID(), mailDBF)
body, err := support.CreateMessageFromBytes(itemData) body, err := api.BytesToMessageable(itemData)
require.NoErrorf(t, err, "transforming mail bytes to messageable: %+v", clues.ToCore(err)) require.NoErrorf(t, err, "transforming mail bytes to messageable: %+v", clues.ToCore(err))
itm, err := ac.Mail().PostItem(ctx, uidn.ID(), containerID, body) itm, err := ac.Mail().PostItem(ctx, uidn.ID(), containerID, body)
@ -1149,7 +1148,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
case path.ContactsCategory: case path.ContactsCategory:
_, itemData := generateItemData(t, category, uidn.ID(), contactDBF) _, itemData := generateItemData(t, category, uidn.ID(), contactDBF)
body, err := support.CreateContactFromBytes(itemData) body, err := api.BytesToContactable(itemData)
require.NoErrorf(t, err, "transforming contact bytes to contactable: %+v", clues.ToCore(err)) require.NoErrorf(t, err, "transforming contact bytes to contactable: %+v", clues.ToCore(err))
itm, err := ac.Contacts().PostItem(ctx, uidn.ID(), containerID, body) itm, err := ac.Contacts().PostItem(ctx, uidn.ID(), containerID, body)
@ -1162,7 +1161,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
case path.EventsCategory: case path.EventsCategory:
_, itemData := generateItemData(t, category, uidn.ID(), eventDBF) _, itemData := generateItemData(t, category, uidn.ID(), eventDBF)
body, err := support.CreateEventFromBytes(itemData) body, err := api.BytesToEventable(itemData)
require.NoErrorf(t, err, "transforming event bytes to eventable: %+v", clues.ToCore(err)) require.NoErrorf(t, err, "transforming event bytes to eventable: %+v", clues.ToCore(err))
itm, err := ac.Events().PostItem(ctx, uidn.ID(), containerID, body) itm, err := ac.Events().PostItem(ctx, uidn.ID(), containerID, body)

View File

@ -11,7 +11,6 @@ import (
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock" exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"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"
) )
@ -67,7 +66,7 @@ func (suite *ExchangeServiceSuite) TestHasAttachments() {
"This is testing", "This is testing",
"This is testing", "This is testing",
) )
message, err := support.CreateMessageFromBytes(byteArray) message, err := BytesToMessageable(byteArray)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
return message.GetBody() return message.GetBody()
}, },
@ -77,7 +76,7 @@ func (suite *ExchangeServiceSuite) TestHasAttachments() {
hasAttachment: assert.True, hasAttachment: assert.True,
getBodyable: func(t *testing.T) models.ItemBodyable { getBodyable: func(t *testing.T) models.ItemBodyable {
byteArray := exchMock.MessageWithOneDriveAttachment("Test legacy") byteArray := exchMock.MessageWithOneDriveAttachment("Test legacy")
message, err := support.CreateMessageFromBytes(byteArray) message, err := BytesToMessageable(byteArray)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
return message.GetBody() return message.GetBody()
}, },

View File

@ -38,13 +38,17 @@ type Contacts struct {
// If successful, returns the created folder object. // If successful, returns the created folder object.
func (c Contacts) CreateContactFolder( func (c Contacts) CreateContactFolder(
ctx context.Context, ctx context.Context,
user, folderName string, userID, containerName string,
) (models.ContactFolderable, error) { ) (models.ContactFolderable, error) {
requestBody := models.NewContactFolder() body := models.NewContactFolder()
temp := folderName body.SetDisplayName(ptr.To(containerName))
requestBody.SetDisplayName(&temp)
mdl, err := c.Stable.Client().Users().ByUserId(user).ContactFolders().Post(ctx, requestBody, nil) mdl, err := c.Stable.
Client().
Users().
ByUserId(userID).
ContactFolders().
Post(ctx, body, nil)
if err != nil { if err != nil {
return nil, graph.Wrap(ctx, err, "creating contact folder") return nil, graph.Wrap(ctx, err, "creating contact folder")
} }
@ -55,7 +59,7 @@ func (c Contacts) CreateContactFolder(
// DeleteContainer deletes the ContactFolder associated with the M365 ID if permissions are valid. // DeleteContainer deletes the ContactFolder associated with the M365 ID if permissions are valid.
func (c Contacts) DeleteContainer( func (c Contacts) DeleteContainer(
ctx context.Context, ctx context.Context,
user, folderID string, userID, containerID string,
) error { ) error {
// deletes require unique http clients // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707 // https://github.com/alcionai/corso/issues/2707
@ -64,7 +68,13 @@ func (c Contacts) DeleteContainer(
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }
err = srv.Client().Users().ByUserId(user).ContactFolders().ByContactFolderId(folderID).Delete(ctx, nil) err = srv.
Client().
Users().
ByUserId(userID).
ContactFolders().
ByContactFolderId(containerID).
Delete(ctx, nil)
if err != nil { if err != nil {
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }
@ -79,18 +89,14 @@ func (c Contacts) GetFolder(
ctx context.Context, ctx context.Context,
userID, containerID string, userID, containerID string,
) (models.ContactFolderable, error) { ) (models.ContactFolderable, error) {
service, err := c.Service()
if err != nil {
return nil, graph.Stack(ctx, err)
}
config := &users.ItemContactFoldersContactFolderItemRequestBuilderGetRequestConfiguration{ config := &users.ItemContactFoldersContactFolderItemRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemContactFoldersContactFolderItemRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemContactFoldersContactFolderItemRequestBuilderGetQueryParameters{
Select: idAnd(displayName, parentFolderID), Select: idAnd(displayName, parentFolderID),
}, },
} }
resp, err := service.Client(). resp, err := c.Stable.
Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
ContactFolders(). ContactFolders().
@ -106,9 +112,9 @@ func (c Contacts) GetFolder(
// interface-compliant wrapper of GetFolder // interface-compliant wrapper of GetFolder
func (c Contacts) GetContainerByID( func (c Contacts) GetContainerByID(
ctx context.Context, ctx context.Context,
userID, dirID string, userID, containerID string,
) (graph.Container, error) { ) (graph.Container, error) {
return c.GetFolder(ctx, userID, dirID) return c.GetFolder(ctx, userID, containerID)
} }
func (c Contacts) PatchFolder( func (c Contacts) PatchFolder(
@ -116,12 +122,8 @@ func (c Contacts) PatchFolder(
userID, containerID string, userID, containerID string,
body models.ContactFolderable, body models.ContactFolderable,
) error { ) error {
service, err := c.Service() _, err := c.Stable.
if err != nil { Client().
return graph.Stack(ctx, err)
}
_, err = service.Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
ContactFolders(). ContactFolders().
@ -145,15 +147,10 @@ func (c Contacts) PatchFolder(
// not contain historical data. // not contain historical data.
func (c Contacts) EnumerateContainers( func (c Contacts) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, baseDirID string, userID, baseContainerID string,
fn func(graph.CachedContainer) error, fn func(graph.CachedContainer) error,
errs *fault.Bus, errs *fault.Bus,
) error { ) error {
service, err := c.Service()
if err != nil {
return graph.Stack(ctx, err)
}
config := &users.ItemContactFoldersItemChildFoldersRequestBuilderGetRequestConfiguration{ config := &users.ItemContactFoldersItemChildFoldersRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemContactFoldersItemChildFoldersRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemContactFoldersItemChildFoldersRequestBuilderGetQueryParameters{
Select: idAnd(displayName, parentFolderID), Select: idAnd(displayName, parentFolderID),
@ -161,11 +158,12 @@ func (c Contacts) EnumerateContainers(
} }
el := errs.Local() el := errs.Local()
builder := service.Client(). builder := c.Stable.
Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
ContactFolders(). ContactFolders().
ByContactFolderId(baseDirID). ByContactFolderId(baseContainerID).
ChildFolders() ChildFolders()
for { for {
@ -205,7 +203,7 @@ func (c Contacts) EnumerateContainers(
break break
} }
builder = users.NewItemContactFoldersItemChildFoldersRequestBuilder(link, service.Adapter()) builder = users.NewItemContactFoldersItemChildFoldersRequestBuilder(link, c.Stable.Adapter())
} }
return el.Failure() return el.Failure()
@ -218,7 +216,7 @@ func (c Contacts) EnumerateContainers(
// GetItem retrieves a Contactable item. // GetItem retrieves a Contactable item.
func (c Contacts) GetItem( func (c Contacts) GetItem(
ctx context.Context, ctx context.Context,
user, itemID string, userID, itemID string,
immutableIDs bool, immutableIDs bool,
_ *fault.Bus, // no attachments to iterate over, so this goes unused _ *fault.Bus, // no attachments to iterate over, so this goes unused
) (serialization.Parsable, *details.ExchangeInfo, error) { ) (serialization.Parsable, *details.ExchangeInfo, error) {
@ -226,7 +224,13 @@ func (c Contacts) GetItem(
Headers: newPreferHeaders(preferImmutableIDs(immutableIDs)), Headers: newPreferHeaders(preferImmutableIDs(immutableIDs)),
} }
cont, err := c.Stable.Client().Users().ByUserId(user).Contacts().ByContactId(itemID).Get(ctx, options) cont, err := c.Stable.
Client().
Users().
ByUserId(userID).
Contacts().
ByContactId(itemID).
Get(ctx, options)
if err != nil { if err != nil {
return nil, nil, graph.Stack(ctx, err) return nil, nil, graph.Stack(ctx, err)
} }
@ -239,12 +243,8 @@ func (c Contacts) PostItem(
userID, containerID string, userID, containerID string,
body models.Contactable, body models.Contactable,
) (models.Contactable, error) { ) (models.Contactable, error) {
service, err := c.Service() itm, err := c.Stable.
if err != nil { Client().
return nil, graph.Stack(ctx, err)
}
itm, err := service.Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
ContactFolders(). ContactFolders().
@ -264,12 +264,13 @@ func (c Contacts) DeleteItem(
) error { ) error {
// deletes require unique http clients // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707 // https://github.com/alcionai/corso/issues/2707
service, err := c.Service() srv, err := c.Service()
if err != nil { if err != nil {
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }
err = service.Client(). err = srv.
Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
Contacts(). Contacts().
@ -297,7 +298,7 @@ type contactPager struct {
func NewContactPager( func NewContactPager(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user, directoryID string, userID, containerID string,
immutableIDs bool, immutableIDs bool,
) itemPager { ) itemPager {
config := &users.ItemContactFoldersItemContactsRequestBuilderGetRequestConfiguration{ config := &users.ItemContactFoldersItemContactsRequestBuilderGetRequestConfiguration{
@ -307,11 +308,12 @@ func NewContactPager(
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)), Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
} }
builder := gs.Client(). builder := gs.
Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
ContactFolders(). ContactFolders().
ByContactFolderId(directoryID). ByContactFolderId(containerID).
Contacts() Contacts()
return &contactPager{gs, builder, config} return &contactPager{gs, builder, config}
@ -345,8 +347,8 @@ var _ itemPager = &contactDeltaPager{}
type contactDeltaPager struct { type contactDeltaPager struct {
gs graph.Servicer gs graph.Servicer
user string userID string
directoryID string containerID string
builder *users.ItemContactFoldersItemContactsDeltaRequestBuilder builder *users.ItemContactFoldersItemContactsDeltaRequestBuilder
options *users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration options *users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration
} }
@ -354,18 +356,17 @@ type contactDeltaPager struct {
func getContactDeltaBuilder( func getContactDeltaBuilder(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, userID, containerID string,
directoryID string,
options *users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration, options *users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration,
) *users.ItemContactFoldersItemContactsDeltaRequestBuilder { ) *users.ItemContactFoldersItemContactsDeltaRequestBuilder {
builder := gs.Client().Users().ByUserId(user).ContactFolders().ByContactFolderId(directoryID).Contacts().Delta() builder := gs.Client().Users().ByUserId(userID).ContactFolders().ByContactFolderId(containerID).Contacts().Delta()
return builder return builder
} }
func NewContactDeltaPager( func NewContactDeltaPager(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user, directoryID, deltaURL string, userID, containerID, oldDelta string,
immutableIDs bool, immutableIDs bool,
) itemPager { ) itemPager {
options := &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration{ options := &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration{
@ -376,13 +377,13 @@ func NewContactDeltaPager(
} }
var builder *users.ItemContactFoldersItemContactsDeltaRequestBuilder var builder *users.ItemContactFoldersItemContactsDeltaRequestBuilder
if deltaURL != "" { if oldDelta != "" {
builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(deltaURL, gs.Adapter()) builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, gs.Adapter())
} else { } else {
builder = getContactDeltaBuilder(ctx, gs, user, directoryID, options) builder = getContactDeltaBuilder(ctx, gs, userID, containerID, options)
} }
return &contactDeltaPager{gs, user, directoryID, builder, options} return &contactDeltaPager{gs, userID, containerID, builder, options}
} }
func (p *contactDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error) { func (p *contactDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
@ -399,7 +400,7 @@ func (p *contactDeltaPager) setNext(nextLink string) {
} }
func (p *contactDeltaPager) reset(ctx context.Context) { func (p *contactDeltaPager) reset(ctx context.Context) {
p.builder = getContactDeltaBuilder(ctx, p.gs, p.user, p.directoryID, p.options) p.builder = getContactDeltaBuilder(ctx, p.gs, p.userID, p.containerID, p.options)
} }
func (p *contactDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) { func (p *contactDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
@ -408,35 +409,38 @@ func (p *contactDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
func (c Contacts) GetAddedAndRemovedItemIDs( func (c Contacts) GetAddedAndRemovedItemIDs(
ctx context.Context, ctx context.Context,
user, directoryID, oldDelta string, userID, containerID, oldDelta string,
immutableIDs bool, immutableIDs bool,
canMakeDeltaQueries bool, canMakeDeltaQueries bool,
) ([]string, []string, DeltaUpdate, error) { ) ([]string, []string, DeltaUpdate, error) {
service, err := c.Service()
if err != nil {
return nil, nil, DeltaUpdate{}, graph.Stack(ctx, err)
}
ctx = clues.Add( ctx = clues.Add(
ctx, ctx,
"category", selectors.ExchangeContact, "category", selectors.ExchangeContact,
"container_id", directoryID) "container_id", containerID)
pager := NewContactPager(ctx, service, user, directoryID, immutableIDs) pager := NewContactPager(ctx, c.Stable, userID, containerID, immutableIDs)
deltaPager := NewContactDeltaPager(ctx, service, user, directoryID, oldDelta, immutableIDs) deltaPager := NewContactDeltaPager(ctx, c.Stable, userID, containerID, oldDelta, immutableIDs)
return getAddedAndRemovedItemIDs(ctx, service, pager, deltaPager, oldDelta, canMakeDeltaQueries) return getAddedAndRemovedItemIDs(ctx, c.Stable, pager, deltaPager, oldDelta, canMakeDeltaQueries)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Serialization // Serialization
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Serialize rserializes the item into a byte slice. func BytesToContactable(bytes []byte) (models.Contactable, error) {
v, err := createFromBytes(bytes, models.CreateContactFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to contact")
}
return v.(models.Contactable), nil
}
func (c Contacts) Serialize( func (c Contacts) Serialize(
ctx context.Context, ctx context.Context,
item serialization.Parsable, item serialization.Parsable,
user, itemID string, userID, itemID string,
) ([]byte, error) { ) ([]byte, error) {
contact, ok := item.(models.Contactable) contact, ok := item.(models.Contactable)
if !ok { if !ok {
@ -444,15 +448,11 @@ func (c Contacts) Serialize(
} }
ctx = clues.Add(ctx, "item_id", ptr.Val(contact.GetId())) ctx = clues.Add(ctx, "item_id", ptr.Val(contact.GetId()))
writer := kjson.NewJsonSerializationWriter()
var (
err error
writer = kjson.NewJsonSerializationWriter()
)
defer writer.Close() defer writer.Close()
if err = writer.WriteObjectValue("", contact); err != nil { if err := writer.WriteObjectValue("", contact); err != nil {
return nil, graph.Stack(ctx, err) return nil, graph.Stack(ctx, err)
} }

View File

@ -4,10 +4,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
) )
@ -66,3 +68,40 @@ func (suite *ContactsAPIUnitSuite) TestContactInfo() {
}) })
} }
} }
func (suite *ContactsAPIUnitSuite) TestBytesToContactable() {
table := []struct {
name string
byteArray []byte
checkError assert.ErrorAssertionFunc
isNil assert.ValueAssertionFunc
}{
{
name: "empty bytes",
byteArray: make([]byte, 0),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: "invalid bytes",
byteArray: []byte("A random sentence doesn't make an object"),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: "Valid Contact",
byteArray: exchMock.ContactBytes("Support Test"),
checkError: assert.NoError,
isNil: assert.NotNil,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
result, err := BytesToContactable(test.byteArray)
test.checkError(t, err, clues.ToCore(err))
test.isNil(t, result)
})
}
}

View File

@ -42,12 +42,17 @@ type Events struct {
// Reference: https://docs.microsoft.com/en-us/graph/api/user-post-calendars?view=graph-rest-1.0&tabs=go // Reference: https://docs.microsoft.com/en-us/graph/api/user-post-calendars?view=graph-rest-1.0&tabs=go
func (c Events) CreateCalendar( func (c Events) CreateCalendar(
ctx context.Context, ctx context.Context,
user, calendarName string, userID, containerName string,
) (models.Calendarable, error) { ) (models.Calendarable, error) {
requestbody := models.NewCalendar() body := models.NewCalendar()
requestbody.SetName(&calendarName) body.SetName(&containerName)
mdl, err := c.Stable.Client().Users().ByUserId(user).Calendars().Post(ctx, requestbody, nil) mdl, err := c.Stable.
Client().
Users().
ByUserId(userID).
Calendars().
Post(ctx, body, nil)
if err != nil { if err != nil {
return nil, graph.Wrap(ctx, err, "creating calendar") return nil, graph.Wrap(ctx, err, "creating calendar")
} }
@ -59,7 +64,7 @@ func (c Events) CreateCalendar(
// Reference: https://docs.microsoft.com/en-us/graph/api/calendar-delete?view=graph-rest-1.0&tabs=go // Reference: https://docs.microsoft.com/en-us/graph/api/calendar-delete?view=graph-rest-1.0&tabs=go
func (c Events) DeleteContainer( func (c Events) DeleteContainer(
ctx context.Context, ctx context.Context,
user, calendarID string, userID, containerID string,
) error { ) error {
// deletes require unique http clients // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707 // https://github.com/alcionai/corso/issues/2707
@ -68,7 +73,12 @@ func (c Events) DeleteContainer(
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }
err = srv.Client().Users().ByUserId(user).Calendars().ByCalendarId(calendarID).Delete(ctx, nil) err = srv.Client().
Users().
ByUserId(userID).
Calendars().
ByCalendarId(containerID).
Delete(ctx, nil)
if err != nil { if err != nil {
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }
@ -83,18 +93,14 @@ func (c Events) GetCalendar(
ctx context.Context, ctx context.Context,
userID, containerID string, userID, containerID string,
) (models.Calendarable, error) { ) (models.Calendarable, error) {
service, err := c.Service()
if err != nil {
return nil, graph.Stack(ctx, err)
}
config := &users.ItemCalendarsCalendarItemRequestBuilderGetRequestConfiguration{ config := &users.ItemCalendarsCalendarItemRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemCalendarsCalendarItemRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemCalendarsCalendarItemRequestBuilderGetQueryParameters{
Select: idAnd("name", "owner"), Select: idAnd("name", "owner"),
}, },
} }
resp, err := service.Client(). resp, err := c.Stable.
Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
Calendars(). Calendars().
@ -110,9 +116,9 @@ func (c Events) GetCalendar(
// interface-compliant wrapper of GetCalendar // interface-compliant wrapper of GetCalendar
func (c Events) GetContainerByID( func (c Events) GetContainerByID(
ctx context.Context, ctx context.Context,
userID, dirID string, userID, containerID string,
) (graph.Container, error) { ) (graph.Container, error) {
cal, err := c.GetCalendar(ctx, userID, dirID) cal, err := c.GetCalendar(ctx, userID, containerID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -123,18 +129,23 @@ func (c Events) GetContainerByID(
// GetContainerByName fetches a calendar by name // GetContainerByName fetches a calendar by name
func (c Events) GetContainerByName( func (c Events) GetContainerByName(
ctx context.Context, ctx context.Context,
userID, name string, userID, containerName string,
) (models.Calendarable, error) { ) (models.Calendarable, error) {
filter := fmt.Sprintf("name eq '%s'", name) filter := fmt.Sprintf("name eq '%s'", containerName)
options := &users.ItemCalendarsRequestBuilderGetRequestConfiguration{ options := &users.ItemCalendarsRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemCalendarsRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemCalendarsRequestBuilderGetQueryParameters{
Filter: &filter, Filter: &filter,
}, },
} }
ctx = clues.Add(ctx, "calendar_name", name) ctx = clues.Add(ctx, "calendar_name", containerName)
resp, err := c.Stable.Client().Users().ByUserId(userID).Calendars().Get(ctx, options) resp, err := c.Stable.
Client().
Users().
ByUserId(userID).
Calendars().
Get(ctx, options)
if err != nil { if err != nil {
return nil, graph.Stack(ctx, err).WithClues(ctx) return nil, graph.Stack(ctx, err).WithClues(ctx)
} }
@ -164,12 +175,8 @@ func (c Events) PatchCalendar(
userID, containerID string, userID, containerID string,
body models.Calendarable, body models.Calendarable,
) error { ) error {
service, err := c.Service() _, err := c.Stable.
if err != nil { Client().
return graph.Stack(ctx, err)
}
_, err = service.Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
Calendars(). Calendars().
@ -193,15 +200,10 @@ func (c Events) PatchCalendar(
// not contain historical data. // not contain historical data.
func (c Events) EnumerateContainers( func (c Events) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, baseDirID string, userID, baseContainerID string,
fn func(graph.CachedContainer) error, fn func(graph.CachedContainer) error,
errs *fault.Bus, errs *fault.Bus,
) error { ) error {
service, err := c.Service()
if err != nil {
return graph.Stack(ctx, err)
}
var ( var (
el = errs.Local() el = errs.Local()
config = &users.ItemCalendarsRequestBuilderGetRequestConfiguration{ config = &users.ItemCalendarsRequestBuilderGetRequestConfiguration{
@ -209,7 +211,8 @@ func (c Events) EnumerateContainers(
Select: idAnd("name"), Select: idAnd("name"),
}, },
} }
builder = service.Client(). builder = c.Stable.
Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
Calendars() Calendars()
@ -256,7 +259,7 @@ func (c Events) EnumerateContainers(
break break
} }
builder = users.NewItemCalendarsRequestBuilder(link, service.Adapter()) builder = users.NewItemCalendarsRequestBuilder(link, c.Stable.Adapter())
} }
return el.Failure() return el.Failure()
@ -273,7 +276,7 @@ const (
// GetItem retrieves an Eventable item. // GetItem retrieves an Eventable item.
func (c Events) GetItem( func (c Events) GetItem(
ctx context.Context, ctx context.Context,
user, itemID string, userID, itemID string,
immutableIDs bool, immutableIDs bool,
errs *fault.Bus, errs *fault.Bus,
) (serialization.Parsable, *details.ExchangeInfo, error) { ) (serialization.Parsable, *details.ExchangeInfo, error) {
@ -285,9 +288,10 @@ func (c Events) GetItem(
} }
) )
event, err = c.Stable.Client(). event, err = c.Stable.
Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
Events(). Events().
ByEventId(itemID). ByEventId(itemID).
Get(ctx, config) Get(ctx, config)
@ -306,7 +310,7 @@ func (c Events) GetItem(
attached, err := c.LargeItem. attached, err := c.LargeItem.
Client(). Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
Events(). Events().
ByEventId(itemID). ByEventId(itemID).
Attachments(). Attachments().
@ -326,12 +330,8 @@ func (c Events) PostItem(
userID, containerID string, userID, containerID string,
body models.Eventable, body models.Eventable,
) (models.Eventable, error) { ) (models.Eventable, error) {
service, err := c.Service() itm, err := c.Stable.
if err != nil { Client().
return nil, graph.Stack(ctx, err)
}
itm, err := service.Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
Calendars(). Calendars().
@ -351,12 +351,13 @@ func (c Events) DeleteItem(
) error { ) error {
// deletes require unique http clients // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707 // https://github.com/alcionai/corso/issues/2707
service, err := c.Service() srv, err := c.Service()
if err != nil { if err != nil {
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }
err = service.Client(). err = srv.
Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
Events(). Events().
@ -374,12 +375,8 @@ func (c Events) PostSmallAttachment(
userID, containerID, parentItemID string, userID, containerID, parentItemID string,
body models.Attachmentable, body models.Attachmentable,
) error { ) error {
service, err := c.Service() _, err := c.Stable.
if err != nil { Client().
return graph.Stack(ctx, err)
}
_, err = service.Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
Calendars(). Calendars().
@ -397,7 +394,7 @@ func (c Events) PostSmallAttachment(
func (c Events) PostLargeAttachment( func (c Events) PostLargeAttachment(
ctx context.Context, ctx context.Context,
userID, containerID, parentItemID, name string, userID, containerID, parentItemID, itemName string,
size int64, size int64,
body models.Attachmentable, body models.Attachmentable,
) (models.UploadSessionable, error) { ) (models.UploadSessionable, error) {
@ -407,7 +404,7 @@ func (c Events) PostLargeAttachment(
} }
session := users.NewItemCalendarEventsItemAttachmentsCreateUploadSessionPostRequestBody() session := users.NewItemCalendarEventsItemAttachmentsCreateUploadSessionPostRequestBody()
session.SetAttachmentItem(makeSessionAttachment(name, size)) session.SetAttachmentItem(makeSessionAttachment(itemName, size))
us, err := c.LargeItem. us, err := c.LargeItem.
Client(). Client().
@ -451,14 +448,20 @@ type eventPager struct {
func NewEventPager( func NewEventPager(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user, calendarID string, userID, containerID string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemPager, error) {
options := &users.ItemCalendarsItemEventsRequestBuilderGetRequestConfiguration{ options := &users.ItemCalendarsItemEventsRequestBuilderGetRequestConfiguration{
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)), Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
} }
builder := gs.Client().Users().ByUserId(user).Calendars().ByCalendarId(calendarID).Events() builder := gs.
Client().
Users().
ByUserId(userID).
Calendars().
ByCalendarId(containerID).
Events()
return &eventPager{gs, builder, options}, nil return &eventPager{gs, builder, options}, nil
} }
@ -490,17 +493,17 @@ func (p *eventPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
var _ itemPager = &eventDeltaPager{} var _ itemPager = &eventDeltaPager{}
type eventDeltaPager struct { type eventDeltaPager struct {
gs graph.Servicer gs graph.Servicer
user string userID string
calendarID string containerID string
builder *users.ItemCalendarsItemEventsDeltaRequestBuilder builder *users.ItemCalendarsItemEventsDeltaRequestBuilder
options *users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration options *users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration
} }
func NewEventDeltaPager( func NewEventDeltaPager(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user, calendarID, deltaURL string, userID, containerID, oldDelta string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemPager, error) {
options := &users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration{ options := &users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration{
@ -509,20 +512,19 @@ func NewEventDeltaPager(
var builder *users.ItemCalendarsItemEventsDeltaRequestBuilder var builder *users.ItemCalendarsItemEventsDeltaRequestBuilder
if deltaURL == "" { if oldDelta == "" {
builder = getEventDeltaBuilder(ctx, gs, user, calendarID, options) builder = getEventDeltaBuilder(ctx, gs, userID, containerID, options)
} else { } else {
builder = users.NewItemCalendarsItemEventsDeltaRequestBuilder(deltaURL, gs.Adapter()) builder = users.NewItemCalendarsItemEventsDeltaRequestBuilder(oldDelta, gs.Adapter())
} }
return &eventDeltaPager{gs, user, calendarID, builder, options}, nil return &eventDeltaPager{gs, userID, containerID, builder, options}, nil
} }
func getEventDeltaBuilder( func getEventDeltaBuilder(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, userID, containerID string,
calendarID string,
options *users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration, options *users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration,
) *users.ItemCalendarsItemEventsDeltaRequestBuilder { ) *users.ItemCalendarsItemEventsDeltaRequestBuilder {
// Graph SDK only supports delta queries against events on the beta version, so we're // Graph SDK only supports delta queries against events on the beta version, so we're
@ -533,7 +535,7 @@ func getEventDeltaBuilder(
// response body parses properly into the v1.0 structs and complies with our wanted interfaces. // response body parses properly into the v1.0 structs and complies with our wanted interfaces.
// Likewise, the NextLink and DeltaLink odata tags carry our hack forward, so the rest of the code // Likewise, the NextLink and DeltaLink odata tags carry our hack forward, so the rest of the code
// works as intended (until, at least, we want to _not_ call the beta anymore). // works as intended (until, at least, we want to _not_ call the beta anymore).
rawURL := fmt.Sprintf(eventBetaDeltaURLTemplate, user, calendarID) rawURL := fmt.Sprintf(eventBetaDeltaURLTemplate, userID, containerID)
builder := users.NewItemCalendarsItemEventsDeltaRequestBuilder(rawURL, gs.Adapter()) builder := users.NewItemCalendarsItemEventsDeltaRequestBuilder(rawURL, gs.Adapter())
return builder return builder
@ -553,7 +555,7 @@ func (p *eventDeltaPager) setNext(nextLink string) {
} }
func (p *eventDeltaPager) reset(ctx context.Context) { func (p *eventDeltaPager) reset(ctx context.Context) {
p.builder = getEventDeltaBuilder(ctx, p.gs, p.user, p.calendarID, p.options) p.builder = getEventDeltaBuilder(ctx, p.gs, p.userID, p.containerID, p.options)
} }
func (p *eventDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) { func (p *eventDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
@ -562,41 +564,42 @@ func (p *eventDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
func (c Events) GetAddedAndRemovedItemIDs( func (c Events) GetAddedAndRemovedItemIDs(
ctx context.Context, ctx context.Context,
user, calendarID, oldDelta string, userID, containerID, oldDelta string,
immutableIDs bool, immutableIDs bool,
canMakeDeltaQueries bool, canMakeDeltaQueries bool,
) ([]string, []string, DeltaUpdate, error) { ) ([]string, []string, DeltaUpdate, error) {
service, err := c.Service() ctx = clues.Add(ctx, "container_id", containerID)
if err != nil {
return nil, nil, DeltaUpdate{}, err
}
ctx = clues.Add( pager, err := NewEventPager(ctx, c.Stable, userID, containerID, immutableIDs)
ctx,
"container_id", calendarID)
pager, err := NewEventPager(ctx, service, user, calendarID, immutableIDs)
if err != nil { if err != nil {
return nil, nil, DeltaUpdate{}, graph.Wrap(ctx, err, "creating non-delta pager") return nil, nil, DeltaUpdate{}, graph.Wrap(ctx, err, "creating non-delta pager")
} }
deltaPager, err := NewEventDeltaPager(ctx, service, user, calendarID, oldDelta, immutableIDs) deltaPager, err := NewEventDeltaPager(ctx, c.Stable, userID, containerID, oldDelta, immutableIDs)
if err != nil { if err != nil {
return nil, nil, DeltaUpdate{}, graph.Wrap(ctx, err, "creating delta pager") return nil, nil, DeltaUpdate{}, graph.Wrap(ctx, err, "creating delta pager")
} }
return getAddedAndRemovedItemIDs(ctx, service, pager, deltaPager, oldDelta, canMakeDeltaQueries) return getAddedAndRemovedItemIDs(ctx, c.Stable, pager, deltaPager, oldDelta, canMakeDeltaQueries)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Serialization // Serialization
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Serialize transforms the event into a byte slice. func BytesToEventable(body []byte) (models.Eventable, error) {
v, err := createFromBytes(body, models.CreateEventFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to event")
}
return v.(models.Eventable), nil
}
func (c Events) Serialize( func (c Events) Serialize(
ctx context.Context, ctx context.Context,
item serialization.Parsable, item serialization.Parsable,
user, itemID string, userID, itemID string,
) ([]byte, error) { ) ([]byte, error) {
event, ok := item.(models.Eventable) event, ok := item.(models.Eventable)
if !ok { if !ok {
@ -605,14 +608,10 @@ func (c Events) Serialize(
ctx = clues.Add(ctx, "item_id", ptr.Val(event.GetId())) ctx = clues.Add(ctx, "item_id", ptr.Val(event.GetId()))
var ( writer := kjson.NewJsonSerializationWriter()
err error
writer = kjson.NewJsonSerializationWriter()
)
defer writer.Close() defer writer.Close()
if err = writer.WriteObjectValue("", event); err != nil { if err := writer.WriteObjectValue("", event); err != nil {
return nil, graph.Stack(ctx, err) return nil, graph.Stack(ctx, err)
} }

View File

@ -12,7 +12,6 @@ import (
"github.com/alcionai/corso/src/internal/common/dttm" "github.com/alcionai/corso/src/internal/common/dttm"
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock" exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
"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/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
) )
@ -127,7 +126,7 @@ func (suite *EventsAPIUnitSuite) TestEventInfo() {
future = time.Now().UTC().AddDate(0, 0, 1) future = time.Now().UTC().AddDate(0, 0, 1)
eventTime = time.Date(future.Year(), future.Month(), future.Day(), future.Hour(), 0, 0, 0, time.UTC) eventTime = time.Date(future.Year(), future.Month(), future.Day(), future.Hour(), 0, 0, 0, time.UTC)
eventEndTime = eventTime.Add(30 * time.Minute) eventEndTime = eventTime.Add(30 * time.Minute)
event, err = support.CreateEventFromBytes(bytes) event, err = BytesToEventable(bytes)
) )
require.NoError(suite.T(), err, clues.ToCore(err)) require.NoError(suite.T(), err, clues.ToCore(err))
@ -176,3 +175,40 @@ func (suite *EventsAPIUnitSuite) TestEventInfo() {
}) })
} }
} }
func (suite *EventsAPIUnitSuite) TestBytesToEventable() {
tests := []struct {
name string
byteArray []byte
checkError assert.ErrorAssertionFunc
isNil assert.ValueAssertionFunc
}{
{
name: "empty bytes",
byteArray: make([]byte, 0),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: "invalid bytes",
byteArray: []byte("Invalid byte stream \"subject:\" Not going to work"),
checkError: assert.Error,
isNil: assert.Nil,
},
{
name: "Valid Event",
byteArray: exchMock.EventBytes("Event Test"),
checkError: assert.NoError,
isNil: assert.NotNil,
},
}
for _, test := range tests {
suite.Run(test.name, func() {
t := suite.T()
result, err := BytesToEventable(test.byteArray)
test.checkError(t, err, clues.ToCore(err))
test.isNil(t, result)
})
}
}

View File

@ -12,7 +12,6 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"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/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
) )
@ -50,7 +49,7 @@ func (suite *ItemSerializationUnitSuite) TestConcurrentItemSerialization() {
return bs return bs
}, },
deserializeAndGetField: func(t *testing.T, bs []byte) string { deserializeAndGetField: func(t *testing.T, bs []byte) string {
item, err := support.CreateMessageFromBytes(bs) item, err := api.BytesToMessageable(bs)
require.NoError( require.NoError(
t, t,
err, err,
@ -75,7 +74,7 @@ func (suite *ItemSerializationUnitSuite) TestConcurrentItemSerialization() {
return bs return bs
}, },
deserializeAndGetField: func(t *testing.T, bs []byte) string { deserializeAndGetField: func(t *testing.T, bs []byte) string {
item, err := support.CreateEventFromBytes(bs) item, err := api.BytesToEventable(bs)
require.NoError( require.NoError(
t, t,
err, err,
@ -100,7 +99,7 @@ func (suite *ItemSerializationUnitSuite) TestConcurrentItemSerialization() {
return bs return bs
}, },
deserializeAndGetField: func(t *testing.T, bs []byte) string { deserializeAndGetField: func(t *testing.T, bs []byte) string {
item, err := support.CreateContactFromBytes(bs) item, err := api.BytesToContactable(bs)
require.NoError( require.NoError(
t, t,
err, err,

View File

@ -45,14 +45,18 @@ type Mail struct {
// Reference: https://docs.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http // Reference: https://docs.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http
func (c Mail) CreateMailFolder( func (c Mail) CreateMailFolder(
ctx context.Context, ctx context.Context,
user, folder string, userID, containerName string,
) (models.MailFolderable, error) { ) (models.MailFolderable, error) {
isHidden := false isHidden := false
requestBody := models.NewMailFolder() body := models.NewMailFolder()
requestBody.SetDisplayName(&folder) body.SetDisplayName(&containerName)
requestBody.SetIsHidden(&isHidden) body.SetIsHidden(&isHidden)
mdl, err := c.Stable.Client().Users().ByUserId(user).MailFolders().Post(ctx, requestBody, nil) mdl, err := c.Stable.Client().
Users().
ByUserId(userID).
MailFolders().
Post(ctx, body, nil)
if err != nil { if err != nil {
return nil, graph.Wrap(ctx, err, "creating mail folder") return nil, graph.Wrap(ctx, err, "creating mail folder")
} }
@ -62,26 +66,21 @@ func (c Mail) CreateMailFolder(
func (c Mail) CreateMailFolderWithParent( func (c Mail) CreateMailFolderWithParent(
ctx context.Context, ctx context.Context,
user, folder, parentID string, userID, containerName, parentContainerID string,
) (models.MailFolderable, error) { ) (models.MailFolderable, error) {
service, err := c.Service()
if err != nil {
return nil, graph.Stack(ctx, err)
}
isHidden := false isHidden := false
requestBody := models.NewMailFolder() body := models.NewMailFolder()
requestBody.SetDisplayName(&folder) body.SetDisplayName(&containerName)
requestBody.SetIsHidden(&isHidden) body.SetIsHidden(&isHidden)
mdl, err := service. mdl, err := c.Stable.
Client(). Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
MailFolders(). MailFolders().
ByMailFolderId(parentID). ByMailFolderId(parentContainerID).
ChildFolders(). ChildFolders().
Post(ctx, requestBody, nil) Post(ctx, body, nil)
if err != nil { if err != nil {
return nil, graph.Wrap(ctx, err, "creating nested mail folder") return nil, graph.Wrap(ctx, err, "creating nested mail folder")
} }
@ -93,7 +92,7 @@ func (c Mail) CreateMailFolderWithParent(
// Reference: https://docs.microsoft.com/en-us/graph/api/mailfolder-delete?view=graph-rest-1.0&tabs=http // Reference: https://docs.microsoft.com/en-us/graph/api/mailfolder-delete?view=graph-rest-1.0&tabs=http
func (c Mail) DeleteContainer( func (c Mail) DeleteContainer(
ctx context.Context, ctx context.Context,
user, folderID string, userID, containerID string,
) error { ) error {
// deletes require unique http clients // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707 // https://github.com/alcionai/corso/issues/2707
@ -104,9 +103,9 @@ func (c Mail) DeleteContainer(
err = srv.Client(). err = srv.Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
MailFolders(). MailFolders().
ByMailFolderId(folderID). ByMailFolderId(containerID).
Delete(ctx, nil) Delete(ctx, nil)
if err != nil { if err != nil {
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
@ -122,18 +121,14 @@ func (c Mail) GetFolder(
ctx context.Context, ctx context.Context,
userID, containerID string, userID, containerID string,
) (models.MailFolderable, error) { ) (models.MailFolderable, error) {
service, err := c.Service()
if err != nil {
return nil, graph.Stack(ctx, err)
}
config := &users.ItemMailFoldersMailFolderItemRequestBuilderGetRequestConfiguration{ config := &users.ItemMailFoldersMailFolderItemRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemMailFoldersMailFolderItemRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemMailFoldersMailFolderItemRequestBuilderGetQueryParameters{
Select: idAnd(displayName, parentFolderID), Select: idAnd(displayName, parentFolderID),
}, },
} }
resp, err := service.Client(). resp, err := c.Stable.
Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
MailFolders(). MailFolders().
@ -149,9 +144,9 @@ func (c Mail) GetFolder(
// interface-compliant wrapper of GetFolder // interface-compliant wrapper of GetFolder
func (c Mail) GetContainerByID( func (c Mail) GetContainerByID(
ctx context.Context, ctx context.Context,
userID, dirID string, userID, containerID string,
) (graph.Container, error) { ) (graph.Container, error) {
return c.GetFolder(ctx, userID, dirID) return c.GetFolder(ctx, userID, containerID)
} }
func (c Mail) MoveContainer( func (c Mail) MoveContainer(
@ -159,12 +154,7 @@ func (c Mail) MoveContainer(
userID, containerID string, userID, containerID string,
body users.ItemMailFoldersItemMovePostRequestBodyable, body users.ItemMailFoldersItemMovePostRequestBodyable,
) error { ) error {
service, err := c.Service() _, err := c.Stable.
if err != nil {
return graph.Stack(ctx, err)
}
_, err = service.
Client(). Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
@ -184,12 +174,8 @@ func (c Mail) PatchFolder(
userID, containerID string, userID, containerID string,
body models.MailFolderable, body models.MailFolderable,
) error { ) error {
service, err := c.Service() _, err := c.Stable.
if err != nil { Client().
return graph.Stack(ctx, err)
}
_, err = service.Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
MailFolders(). MailFolders().
@ -211,9 +197,9 @@ type mailFolderPager struct {
builder *users.ItemMailFoldersRequestBuilder builder *users.ItemMailFoldersRequestBuilder
} }
func NewMailFolderPager(service graph.Servicer, user string) mailFolderPager { func NewMailFolderPager(service graph.Servicer, userID string) mailFolderPager {
// v1.0 non delta /mailFolders endpoint does not return any of the nested folders // v1.0 non delta /mailFolders endpoint does not return any of the nested folders
rawURL := fmt.Sprintf(mailFoldersBetaURLTemplate, user) rawURL := fmt.Sprintf(mailFoldersBetaURLTemplate, userID)
builder := users.NewItemMailFoldersRequestBuilder(rawURL, service.Adapter()) builder := users.NewItemMailFoldersRequestBuilder(rawURL, service.Adapter())
return mailFolderPager{service, builder} return mailFolderPager{service, builder}
@ -250,18 +236,12 @@ func (p *mailFolderPager) valuesIn(pl PageLinker) ([]models.MailFolderable, erro
// not contain historical data. // not contain historical data.
func (c Mail) EnumerateContainers( func (c Mail) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, baseDirID string, userID, baseContainerID string,
fn func(graph.CachedContainer) error, fn func(graph.CachedContainer) error,
errs *fault.Bus, errs *fault.Bus,
) error { ) error {
service, err := c.Service()
if err != nil {
return graph.Stack(ctx, err)
}
el := errs.Local() el := errs.Local()
pgr := NewMailFolderPager(c.Stable, userID)
pgr := NewMailFolderPager(service, userID)
for { for {
if el.Failure() != nil { if el.Failure() != nil {
@ -319,7 +299,7 @@ func (c Mail) EnumerateContainers(
// attachment is also downloaded. // attachment is also downloaded.
func (c Mail) GetItem( func (c Mail) GetItem(
ctx context.Context, ctx context.Context,
user, itemID string, userID, itemID string,
immutableIDs bool, immutableIDs bool,
errs *fault.Bus, errs *fault.Bus,
) (serialization.Parsable, *details.ExchangeInfo, error) { ) (serialization.Parsable, *details.ExchangeInfo, error) {
@ -331,9 +311,10 @@ func (c Mail) GetItem(
} }
) )
mail, err := c.Stable.Client(). mail, err := c.Stable.
Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
Messages(). Messages().
ByMessageId(itemID). ByMessageId(itemID).
Get(ctx, config) Get(ctx, config)
@ -363,7 +344,7 @@ func (c Mail) GetItem(
attached, err := c.LargeItem. attached, err := c.LargeItem.
Client(). Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
Messages(). Messages().
ByMessageId(itemID). ByMessageId(itemID).
Attachments(). Attachments().
@ -396,7 +377,7 @@ func (c Mail) GetItem(
attachments, err := c.LargeItem. attachments, err := c.LargeItem.
Client(). Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
Messages(). Messages().
ByMessageId(itemID). ByMessageId(itemID).
Attachments(). Attachments().
@ -418,7 +399,7 @@ func (c Mail) GetItem(
att, err := c.Stable. att, err := c.Stable.
Client(). Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
Messages(). Messages().
ByMessageId(itemID). ByMessageId(itemID).
Attachments(). Attachments().
@ -444,12 +425,7 @@ func (c Mail) PostItem(
userID, containerID string, userID, containerID string,
body models.Messageable, body models.Messageable,
) (models.Messageable, error) { ) (models.Messageable, error) {
service, err := c.Service() itm, err := c.Stable.
if err != nil {
return nil, graph.Stack(ctx, err)
}
itm, err := service.
Client(). Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
@ -474,12 +450,12 @@ func (c Mail) DeleteItem(
) error { ) error {
// deletes require unique http clients // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707 // https://github.com/alcionai/corso/issues/2707
service, err := c.Service() srv, err := NewService(c.Credentials)
if err != nil { if err != nil {
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }
err = service. err = srv.
Client(). Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
@ -498,12 +474,7 @@ func (c Mail) PostSmallAttachment(
userID, containerID, parentItemID string, userID, containerID, parentItemID string,
body models.Attachmentable, body models.Attachmentable,
) error { ) error {
service, err := c.Service() _, err := c.Stable.
if err != nil {
return graph.Stack(ctx, err)
}
_, err = service.
Client(). Client().
Users(). Users().
ByUserId(userID). ByUserId(userID).
@ -522,7 +493,7 @@ func (c Mail) PostSmallAttachment(
func (c Mail) PostLargeAttachment( func (c Mail) PostLargeAttachment(
ctx context.Context, ctx context.Context,
userID, containerID, parentItemID, name string, userID, containerID, parentItemID, itemName string,
size int64, size int64,
body models.Attachmentable, body models.Attachmentable,
) (models.UploadSessionable, error) { ) (models.UploadSessionable, error) {
@ -532,7 +503,7 @@ func (c Mail) PostLargeAttachment(
} }
session := users.NewItemMailFoldersItemMessagesItemAttachmentsCreateUploadSessionPostRequestBody() session := users.NewItemMailFoldersItemMessagesItemAttachmentsCreateUploadSessionPostRequestBody()
session.SetAttachmentItem(makeSessionAttachment(name, size)) session.SetAttachmentItem(makeSessionAttachment(itemName, size))
us, err := c.LargeItem. us, err := c.LargeItem.
Client(). Client().
@ -576,7 +547,7 @@ type mailPager struct {
func NewMailPager( func NewMailPager(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user, directoryID string, userID, containerID string,
immutableIDs bool, immutableIDs bool,
) itemPager { ) itemPager {
config := &users.ItemMailFoldersItemMessagesRequestBuilderGetRequestConfiguration{ config := &users.ItemMailFoldersItemMessagesRequestBuilderGetRequestConfiguration{
@ -586,11 +557,12 @@ func NewMailPager(
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)), Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
} }
builder := gs.Client(). builder := gs.
Client().
Users(). Users().
ByUserId(user). ByUserId(userID).
MailFolders(). MailFolders().
ByMailFolderId(directoryID). ByMailFolderId(containerID).
Messages() Messages()
return &mailPager{gs, builder, config} return &mailPager{gs, builder, config}
@ -624,8 +596,8 @@ var _ itemPager = &mailDeltaPager{}
type mailDeltaPager struct { type mailDeltaPager struct {
gs graph.Servicer gs graph.Servicer
user string userID string
directoryID string containerID string
builder *users.ItemMailFoldersItemMessagesDeltaRequestBuilder builder *users.ItemMailFoldersItemMessagesDeltaRequestBuilder
options *users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetRequestConfiguration options *users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetRequestConfiguration
} }
@ -633,15 +605,15 @@ type mailDeltaPager struct {
func getMailDeltaBuilder( func getMailDeltaBuilder(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, user, containerID string,
directoryID string,
options *users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetRequestConfiguration, options *users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetRequestConfiguration,
) *users.ItemMailFoldersItemMessagesDeltaRequestBuilder { ) *users.ItemMailFoldersItemMessagesDeltaRequestBuilder {
builder := gs.Client(). builder := gs.
Client().
Users(). Users().
ByUserId(user). ByUserId(user).
MailFolders(). MailFolders().
ByMailFolderId(directoryID). ByMailFolderId(containerID).
Messages(). Messages().
Delta() Delta()
@ -651,7 +623,7 @@ func getMailDeltaBuilder(
func NewMailDeltaPager( func NewMailDeltaPager(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user, directoryID, oldDelta string, userID, containerID, oldDelta string,
immutableIDs bool, immutableIDs bool,
) itemPager { ) itemPager {
config := &users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetRequestConfiguration{ config := &users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetRequestConfiguration{
@ -666,10 +638,10 @@ func NewMailDeltaPager(
if len(oldDelta) > 0 { if len(oldDelta) > 0 {
builder = users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, gs.Adapter()) builder = users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, gs.Adapter())
} else { } else {
builder = getMailDeltaBuilder(ctx, gs, user, directoryID, config) builder = getMailDeltaBuilder(ctx, gs, userID, containerID, config)
} }
return &mailDeltaPager{gs, user, directoryID, builder, config} return &mailDeltaPager{gs, userID, containerID, builder, config}
} }
func (p *mailDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error) { func (p *mailDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
@ -686,11 +658,12 @@ func (p *mailDeltaPager) setNext(nextLink string) {
} }
func (p *mailDeltaPager) reset(ctx context.Context) { func (p *mailDeltaPager) reset(ctx context.Context) {
p.builder = p.gs.Client(). p.builder = p.gs.
Client().
Users(). Users().
ByUserId(p.user). ByUserId(p.userID).
MailFolders(). MailFolders().
ByMailFolderId(p.directoryID). ByMailFolderId(p.containerID).
Messages(). Messages().
Delta() Delta()
} }
@ -701,31 +674,34 @@ func (p *mailDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
func (c Mail) GetAddedAndRemovedItemIDs( func (c Mail) GetAddedAndRemovedItemIDs(
ctx context.Context, ctx context.Context,
user, directoryID, oldDelta string, userID, containerID, oldDelta string,
immutableIDs bool, immutableIDs bool,
canMakeDeltaQueries bool, canMakeDeltaQueries bool,
) ([]string, []string, DeltaUpdate, error) { ) ([]string, []string, DeltaUpdate, error) {
service, err := c.Service()
if err != nil {
return nil, nil, DeltaUpdate{}, err
}
ctx = clues.Add( ctx = clues.Add(
ctx, ctx,
"category", selectors.ExchangeMail, "category", selectors.ExchangeMail,
"container_id", directoryID) "container_id", containerID)
pager := NewMailPager(ctx, service, user, directoryID, immutableIDs) pager := NewMailPager(ctx, c.Stable, userID, containerID, immutableIDs)
deltaPager := NewMailDeltaPager(ctx, service, user, directoryID, oldDelta, immutableIDs) deltaPager := NewMailDeltaPager(ctx, c.Stable, userID, containerID, oldDelta, immutableIDs)
return getAddedAndRemovedItemIDs(ctx, service, pager, deltaPager, oldDelta, canMakeDeltaQueries) return getAddedAndRemovedItemIDs(ctx, c.Stable, pager, deltaPager, oldDelta, canMakeDeltaQueries)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Serialization // Serialization
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Serialize transforms the mail item into a byte slice. func BytesToMessageable(body []byte) (models.Messageable, error) {
v, err := createFromBytes(body, models.CreateMessageFromDiscriminatorValue)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes to message")
}
return v.(models.Messageable), nil
}
func (c Mail) Serialize( func (c Mail) Serialize(
ctx context.Context, ctx context.Context,
item serialization.Parsable, item serialization.Parsable,
@ -737,15 +713,11 @@ func (c Mail) Serialize(
} }
ctx = clues.Add(ctx, "item_id", ptr.Val(msg.GetId())) ctx = clues.Add(ctx, "item_id", ptr.Val(msg.GetId()))
writer := kjson.NewJsonSerializationWriter()
var (
err error
writer = kjson.NewJsonSerializationWriter()
)
defer writer.Close() defer writer.Close()
if err = writer.WriteObjectValue("", msg); err != nil { if err := writer.WriteObjectValue("", msg); err != nil {
return nil, graph.Stack(ctx, err) return nil, graph.Stack(ctx, err)
} }

View File

@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
exchMock "github.com/alcionai/corso/src/internal/connector/exchange/mock"
"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"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
@ -157,6 +158,37 @@ func (suite *MailAPIUnitSuite) TestMailInfo() {
} }
} }
func (suite *MailAPIUnitSuite) TestBytesToMessagable() {
table := []struct {
name string
byteArray []byte
checkError assert.ErrorAssertionFunc
checkObject assert.ValueAssertionFunc
}{
{
name: "Empty Bytes",
byteArray: make([]byte, 0),
checkError: assert.Error,
checkObject: assert.Nil,
},
{
name: "aMessage bytes",
byteArray: exchMock.MessageBytes("m365 mail support test"),
checkError: assert.NoError,
checkObject: assert.NotNil,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
result, err := api.BytesToMessageable(test.byteArray)
test.checkError(t, err, clues.ToCore(err))
test.checkObject(t, result)
})
}
}
type MailAPIIntgSuite struct { type MailAPIIntgSuite struct {
tester.Suite tester.Suite
credentials account.M365Config credentials account.M365Config

View File

@ -0,0 +1,25 @@
package api
import (
"github.com/alcionai/clues"
"github.com/microsoft/kiota-abstractions-go/serialization"
kjson "github.com/microsoft/kiota-serialization-json-go"
)
// createFromBytes generates an m365 object form bytes.
func createFromBytes(
bytes []byte,
createFunc serialization.ParsableFactory,
) (serialization.Parsable, error) {
parseNode, err := kjson.NewJsonParseNodeFactory().GetRootParseNode("application/json", bytes)
if err != nil {
return nil, clues.Wrap(err, "deserializing bytes into base m365 object")
}
v, err := parseNode.GetObjectValue(createFunc)
if err != nil {
return nil, clues.Wrap(err, "parsing m365 object factory")
}
return v, nil
}