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:
parent
d5ffcae94e
commit
874e3c3a50
@ -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
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package support
|
package exchange
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
@ -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)
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
212
src/internal/connector/sharepoint/api/serialization.go
Normal file
212
src/internal/connector/sharepoint/api/serialization.go
Normal 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
|
||||||
|
}
|
||||||
126
src/internal/connector/sharepoint/api/serialization_test.go
Normal file
126
src/internal/connector/sharepoint/api/serialization_test.go
Normal 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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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{
|
||||||
|
|||||||
@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -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")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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()
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
25
src/pkg/services/m365/api/serialization.go
Normal file
25
src/pkg/services/m365/api/serialization.go
Normal 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
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user