Update attachments for events which drift from series master (#3644)
This updates any changes to attachments for individual event instances for the ones that differ from the series master.<!-- PR description--> --- #### Does this PR need a docs update or release note? - [x] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [x] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * fixes https://github.com/alcionai/corso/issues/2835 #### Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [ ] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
c8ae50cb2e
commit
b0305a5319
@ -25,9 +25,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Changed
|
### Changed
|
||||||
- Do not display all the items that we restored at the end if there are more than 15. You can override this with `--verbose`.
|
- Do not display all the items that we restored at the end if there are more than 15. You can override this with `--verbose`.
|
||||||
|
|
||||||
### Known Issues
|
|
||||||
- Changes to attachments in instances of recurring events compared to the series master aren't restored
|
|
||||||
|
|
||||||
## [v0.8.0] (beta) - 2023-05-15
|
## [v0.8.0] (beta) - 2023-05-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -116,7 +116,7 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error
|
|||||||
User, subject, body, body,
|
User, subject, body, body,
|
||||||
exchMock.NoOriginalStartDate, now, now,
|
exchMock.NoOriginalStartDate, now, now,
|
||||||
exchMock.NoRecurrence, exchMock.NoAttendees,
|
exchMock.NoRecurrence, exchMock.NoAttendees,
|
||||||
false, exchMock.NoCancelledOccurrences,
|
exchMock.NoAttachments, exchMock.NoCancelledOccurrences,
|
||||||
exchMock.NoExceptionOccurrences)
|
exchMock.NoExceptionOccurrences)
|
||||||
},
|
},
|
||||||
control.Defaults(),
|
control.Defaults(),
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package exchange
|
package exchange
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
)
|
)
|
||||||
@ -117,7 +119,15 @@ func (h eventRestoreHandler) restore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix up event instances in case we have a recurring event
|
// Fix up event instances in case we have a recurring event
|
||||||
err = updateRecurringEvents(ctx, h.ac, userID, destinationID, ptr.Val(item.GetId()), event)
|
err = updateRecurringEvents(
|
||||||
|
ctx,
|
||||||
|
h.ac,
|
||||||
|
userID,
|
||||||
|
destinationID,
|
||||||
|
ptr.Val(item.GetId()),
|
||||||
|
event,
|
||||||
|
errs,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, clues.Stack(err)
|
return nil, clues.Stack(err)
|
||||||
}
|
}
|
||||||
@ -133,6 +143,7 @@ func updateRecurringEvents(
|
|||||||
ac api.Events,
|
ac api.Events,
|
||||||
userID, containerID, itemID string,
|
userID, containerID, itemID string,
|
||||||
event models.Eventable,
|
event models.Eventable,
|
||||||
|
errs *fault.Bus,
|
||||||
) error {
|
) error {
|
||||||
if event.GetRecurrence() == nil {
|
if event.GetRecurrence() == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -149,7 +160,7 @@ func updateRecurringEvents(
|
|||||||
return clues.Wrap(err, "update cancelled occurrences")
|
return clues.Wrap(err, "update cancelled occurrences")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = updateExceptionOccurrences(ctx, ac, userID, itemID, exceptionOccurrences)
|
err = updateExceptionOccurrences(ctx, ac, userID, containerID, itemID, exceptionOccurrences, errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "update exception occurrences")
|
return clues.Wrap(err, "update exception occurrences")
|
||||||
}
|
}
|
||||||
@ -164,8 +175,10 @@ func updateExceptionOccurrences(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
ac api.Events,
|
ac api.Events,
|
||||||
userID string,
|
userID string,
|
||||||
|
containerID string,
|
||||||
itemID string,
|
itemID string,
|
||||||
exceptionOccurrences any,
|
exceptionOccurrences any,
|
||||||
|
errs *fault.Bus,
|
||||||
) error {
|
) error {
|
||||||
if exceptionOccurrences == nil {
|
if exceptionOccurrences == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -193,9 +206,11 @@ func updateExceptionOccurrences(
|
|||||||
startStr := dttm.FormatTo(start, dttm.DateOnly)
|
startStr := dttm.FormatTo(start, dttm.DateOnly)
|
||||||
endStr := dttm.FormatTo(start.Add(24*time.Hour), dttm.DateOnly)
|
endStr := dttm.FormatTo(start.Add(24*time.Hour), dttm.DateOnly)
|
||||||
|
|
||||||
|
ictx := clues.Add(ctx, "event_instance_id", ptr.Val(evt.GetId()), "event_instance_date", start)
|
||||||
|
|
||||||
// Get all instances on the day of the instance which should
|
// Get all instances on the day of the instance which should
|
||||||
// just the one we need to modify
|
// just the one we need to modify
|
||||||
evts, err := ac.GetItemInstances(ctx, userID, itemID, startStr, endStr)
|
instances, err := ac.GetItemInstances(ictx, userID, itemID, startStr, endStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "getting instances")
|
return clues.Wrap(err, "getting instances")
|
||||||
}
|
}
|
||||||
@ -203,27 +218,141 @@ func updateExceptionOccurrences(
|
|||||||
// Since the min recurrence interval is 1 day and we are
|
// Since the min recurrence interval is 1 day and we are
|
||||||
// querying for only a single day worth of instances, we
|
// querying for only a single day worth of instances, we
|
||||||
// should not have more than one instance here.
|
// should not have more than one instance here.
|
||||||
if len(evts) != 1 {
|
if len(instances) != 1 {
|
||||||
return clues.New("invalid number of instances for modified").
|
return clues.New("invalid number of instances for modified").
|
||||||
With("instances_count", len(evts), "search_start", startStr, "search_end", endStr)
|
With("instances_count", len(instances), "search_start", startStr, "search_end", endStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
evt = toEventSimplified(evt)
|
evt = toEventSimplified(evt)
|
||||||
|
|
||||||
// TODO(meain): Update attachments (might have to diff the
|
_, err = ac.PatchItem(ictx, userID, ptr.Val(instances[0].GetId()), evt)
|
||||||
// attachments using ids and delete or add). We will have
|
|
||||||
// to get the id of the existing attachments, diff them
|
|
||||||
// with what we need a then create/delete items kinda like
|
|
||||||
// permissions
|
|
||||||
_, err = ac.PatchItem(ctx, userID, ptr.Val(evts[0].GetId()), evt)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "updating event instance")
|
return clues.Wrap(err, "updating event instance")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We are creating event again from map as `toEventSimplified`
|
||||||
|
// removed the attachments and creating a clone from start of
|
||||||
|
// the event is non-trivial
|
||||||
|
evt, err = api.EventFromMap(instance)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "parsing event instance")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updateAttachments(ictx, ac, userID, containerID, ptr.Val(instances[0].GetId()), evt, errs)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "updating event instance attachments")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateAttachments updates the attachments of an event to match what
|
||||||
|
// is present in the backed up event. Ideally we could make use of the
|
||||||
|
// id of the series master event's attachments to see if we had
|
||||||
|
// added/removed any attachments, but as soon an event is modified,
|
||||||
|
// the id changes which makes the ids unusable. In this function, we
|
||||||
|
// use the name and content bytes to detect the changes. This function
|
||||||
|
// can be used to update the attachments of any event irrespective of
|
||||||
|
// whether they are event instances of a series master although for
|
||||||
|
// newer event, since we probably won't already have any events it
|
||||||
|
// would be better use Post[Small|Large]Attachment.
|
||||||
|
func updateAttachments(
|
||||||
|
ctx context.Context,
|
||||||
|
client api.Events,
|
||||||
|
userID, containerID, eventID string,
|
||||||
|
event models.Eventable,
|
||||||
|
errs *fault.Bus,
|
||||||
|
) error {
|
||||||
|
el := errs.Local()
|
||||||
|
|
||||||
|
attachments, err := client.GetAttachments(ctx, false, userID, eventID)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "getting attachments")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete attachments that are not present in the backup but are
|
||||||
|
// present in the event(ones that were automatically inherited
|
||||||
|
// from series master).
|
||||||
|
for _, att := range attachments {
|
||||||
|
if el.Failure() != nil {
|
||||||
|
return el.Failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
name := ptr.Val(att.GetName())
|
||||||
|
id := ptr.Val(att.GetId())
|
||||||
|
|
||||||
|
content, err := api.GetAttachmentContent(att)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "getting attachment").With("attachment_id", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
|
||||||
|
for _, nAtt := range event.GetAttachments() {
|
||||||
|
nName := ptr.Val(nAtt.GetName())
|
||||||
|
|
||||||
|
nContent, err := api.GetAttachmentContent(nAtt)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "getting attachment").With("attachment_id", ptr.Val(nAtt.GetId()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == nName && bytes.Equal(content, nContent) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
err = client.DeleteAttachment(ctx, userID, containerID, eventID, id)
|
||||||
|
if err != nil {
|
||||||
|
logger.CtxErr(ctx, err).With("attachment_name", name).Info("attachment delete failed")
|
||||||
|
el.AddRecoverable(ctx, clues.Wrap(err, "deleting event attachment").
|
||||||
|
WithClues(ctx).With("attachment_name", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload missing(attachments that are present in the individual
|
||||||
|
// instance but not in the series master event) attachments
|
||||||
|
for _, att := range event.GetAttachments() {
|
||||||
|
name := ptr.Val(att.GetName())
|
||||||
|
id := ptr.Val(att.GetId())
|
||||||
|
|
||||||
|
content, err := api.GetAttachmentContent(att)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "getting attachment").With("attachment_id", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
|
||||||
|
for _, nAtt := range attachments {
|
||||||
|
nName := ptr.Val(nAtt.GetName())
|
||||||
|
|
||||||
|
bContent, err := api.GetAttachmentContent(nAtt)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "getting attachment").With("attachment_id", ptr.Val(nAtt.GetId()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max size allowed for an outlook attachment is 150MB
|
||||||
|
if name == nName && bytes.Equal(content, bContent) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
err = uploadAttachment(ctx, client, userID, containerID, eventID, att)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "uploading attachment").
|
||||||
|
With("attachment_id", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return el.Failure()
|
||||||
|
}
|
||||||
|
|
||||||
// updateCancelledOccurrences get the cancelled occurrences which is a
|
// updateCancelledOccurrences get the cancelled occurrences which is a
|
||||||
// list of strings of the format "<id>.<date>", parses the date out of
|
// list of strings of the format "<id>.<date>", parses the date out of
|
||||||
// that and uses the to get the event instance at that date to delete.
|
// that and uses the to get the event instance at that date to delete.
|
||||||
@ -266,7 +395,7 @@ func updateCancelledOccurrences(
|
|||||||
|
|
||||||
// Get all instances on the day of the instance which should
|
// Get all instances on the day of the instance which should
|
||||||
// just the one we need to modify
|
// just the one we need to modify
|
||||||
evts, err := ac.GetItemInstances(ctx, userID, itemID, startStr, endStr)
|
instances, err := ac.GetItemInstances(ctx, userID, itemID, startStr, endStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "getting instances")
|
return clues.Wrap(err, "getting instances")
|
||||||
}
|
}
|
||||||
@ -274,12 +403,12 @@ func updateCancelledOccurrences(
|
|||||||
// Since the min recurrence interval is 1 day and we are
|
// Since the min recurrence interval is 1 day and we are
|
||||||
// querying for only a single day worth of instances, we
|
// querying for only a single day worth of instances, we
|
||||||
// should not have more than one instance here.
|
// should not have more than one instance here.
|
||||||
if len(evts) != 1 {
|
if len(instances) != 1 {
|
||||||
return clues.New("invalid number of instances for cancelled").
|
return clues.New("invalid number of instances for cancelled").
|
||||||
With("instances_count", len(evts), "search_start", startStr, "search_end", endStr)
|
With("instances_count", len(instances), "search_start", startStr, "search_end", endStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ac.DeleteItem(ctx, userID, ptr.Val(evts[0].GetId()))
|
err = ac.DeleteItem(ctx, userID, ptr.Val(instances[0].GetId()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "deleting event instance")
|
return clues.Wrap(err, "deleting event instance")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import (
|
|||||||
// 10. attendees
|
// 10. attendees
|
||||||
|
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
const (
|
var (
|
||||||
eventTmpl = `{
|
eventTmpl = `{
|
||||||
"categories":[],
|
"categories":[],
|
||||||
"changeKey":"0hATW1CAfUS+njw3hdxSGAAAJIxNug==",
|
"changeKey":"0hATW1CAfUS+njw3hdxSGAAAJIxNug==",
|
||||||
@ -98,8 +98,10 @@ const (
|
|||||||
defaultEventBody = "This meeting is to review the latest Tailspin Toys project proposal.<br>\\r\\nBut why not eat some sushi while we’re at it? :)"
|
defaultEventBody = "This meeting is to review the latest Tailspin Toys project proposal.<br>\\r\\nBut why not eat some sushi while we’re at it? :)"
|
||||||
defaultEventBodyPreview = "This meeting is to review the latest Tailspin Toys project proposal.\\r\\nBut why not eat some sushi while we’re at it? :)"
|
defaultEventBodyPreview = "This meeting is to review the latest Tailspin Toys project proposal.\\r\\nBut why not eat some sushi while we’re at it? :)"
|
||||||
defaultEventOrganizer = "foobar@8qzvrj.onmicrosoft.com"
|
defaultEventOrganizer = "foobar@8qzvrj.onmicrosoft.com"
|
||||||
eventAttachment = "\"attachments\":[{\"id\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAACLjfLQAAABEgAQAHoI0xBbBBVEh6bFMU78ZUo=\",\"@odata.type\":\"#microsoft.graph.fileAttachment\"," +
|
|
||||||
"\"@odata.mediaContentType\":\"application/octet-stream\",\"contentType\":\"application/octet-stream\",\"isInline\":false,\"lastModifiedDateTime\":\"2022-10-26T15:19:42Z\",\"name\":\"database.db\",\"size\":11418," +
|
NoAttachments = ""
|
||||||
|
eventAttachmentFormat = "{\"id\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAENAADSEBNbUIB9RL6ePDeF3FIYAACLjfLQAAABEgAQAHoI0xBbBBVEh6bFMU78ZUo=\",\"@odata.type\":\"#microsoft.graph.fileAttachment\"," +
|
||||||
|
"\"@odata.mediaContentType\":\"application/octet-stream\",\"contentType\":\"application/octet-stream\",\"isInline\":false,\"lastModifiedDateTime\":\"2022-10-26T15:19:42Z\",\"name\":\"%s\",\"size\":11418," +
|
||||||
"\"contentBytes\":\"U1FMaXRlIGZvcm1hdCAzAAQAAQEAQCAgAAAATQAAAAsAAAAEAAAACAAAAAsAAAAEAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNAC3mBw0DZwACAg8AAxUCDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
"\"contentBytes\":\"U1FMaXRlIGZvcm1hdCAzAAQAAQEAQCAgAAAATQAAAAsAAAAEAAAACAAAAAsAAAAEAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNAC3mBw0DZwACAg8AAxUCDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCAwMHFxUVAYNpdGFibGVkYXRhZGF0YQJDUkVBVEUgVEFCTEUgZGF0YSAoCiAgICAgICAgIGlkIGludGVnZXIgcHJpbWFyeSBrZXkgYXV0b2luY3JlbWVudCwKICAgICAgICAgbWVhbiB0ZXh0IG5vdCBudWxsLAogICAgICAgICBtYXggdGV4dCBub3QgbnVsbCwKICAgICAgICAgbWluIHRleHQgbm90IG51bGwsCiAgICAgICAgIGRhdGEgdGV" +
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCAwMHFxUVAYNpdGFibGVkYXRhZGF0YQJDUkVBVEUgVEFCTEUgZGF0YSAoCiAgICAgICAgIGlkIGludGVnZXIgcHJpbWFyeSBrZXkgYXV0b2luY3JlbWVudCwKICAgICAgICAgbWVhbiB0ZXh0IG5vdCBudWxsLAogICAgICAgICBtYXggdGV4dCBub3QgbnVsbCwKICAgICAgICAgbWluIHRleHQgbm90IG51bGwsCiAgICAgICAgIGRhdGEgdGV" +
|
||||||
@ -146,7 +148,8 @@ const (
|
|||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
|
||||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"}],"
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"}"
|
||||||
|
defaultEventAttachments = "\"attachments\":[" + fmt.Sprintf(eventAttachmentFormat, "database.db") + "],"
|
||||||
|
|
||||||
originalStartDateFormat = `"originalStart": "%s",`
|
originalStartDateFormat = `"originalStart": "%s",`
|
||||||
NoOriginalStartDate = ``
|
NoOriginalStartDate = ``
|
||||||
@ -226,37 +229,43 @@ func EventBytes(subject string) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EventWithSubjectBytes(subject string) []byte {
|
func EventWithSubjectBytes(subject string) []byte {
|
||||||
tomorrow := time.Now().UTC().AddDate(0, 0, 1)
|
var (
|
||||||
at := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
tomorrow = time.Now().UTC().AddDate(0, 0, 1)
|
||||||
atTime := dttm.Format(at)
|
at = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
||||||
endTime := dttm.Format(at.Add(30 * time.Minute))
|
atTime = dttm.Format(at)
|
||||||
|
endTime = dttm.Format(at.Add(30 * time.Minute))
|
||||||
|
)
|
||||||
|
|
||||||
return EventWith(
|
return EventWith(
|
||||||
defaultEventOrganizer, subject,
|
defaultEventOrganizer, subject,
|
||||||
defaultEventBody, defaultEventBodyPreview,
|
defaultEventBody, defaultEventBodyPreview,
|
||||||
NoOriginalStartDate, atTime, endTime, NoRecurrence, NoAttendees,
|
NoOriginalStartDate, atTime, endTime, NoRecurrence, NoAttendees,
|
||||||
false, NoCancelledOccurrences, NoExceptionOccurrences,
|
NoAttachments, NoCancelledOccurrences, NoExceptionOccurrences,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EventWithAttachment(subject string) []byte {
|
func EventWithAttachment(subject string) []byte {
|
||||||
tomorrow := time.Now().UTC().AddDate(0, 0, 1)
|
var (
|
||||||
at := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
tomorrow = time.Now().UTC().AddDate(0, 0, 1)
|
||||||
atTime := dttm.Format(at)
|
at = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
||||||
|
atTime = dttm.Format(at)
|
||||||
|
)
|
||||||
|
|
||||||
return EventWith(
|
return EventWith(
|
||||||
defaultEventOrganizer, subject,
|
defaultEventOrganizer, subject,
|
||||||
defaultEventBody, defaultEventBodyPreview,
|
defaultEventBody, defaultEventBodyPreview,
|
||||||
NoOriginalStartDate, atTime, atTime, NoRecurrence, NoAttendees,
|
NoOriginalStartDate, atTime, atTime, NoRecurrence, NoAttendees,
|
||||||
true, NoCancelledOccurrences, NoExceptionOccurrences,
|
defaultEventAttachments, NoCancelledOccurrences, NoExceptionOccurrences,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EventWithRecurrenceBytes(subject, recurrenceTimeZone string) []byte {
|
func EventWithRecurrenceBytes(subject, recurrenceTimeZone string) []byte {
|
||||||
tomorrow := time.Now().UTC().AddDate(0, 0, 1)
|
var (
|
||||||
at := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
tomorrow = time.Now().UTC().AddDate(0, 0, 1)
|
||||||
atTime := dttm.Format(at)
|
at = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
||||||
timeSlice := strings.Split(atTime, "T")
|
atTime = dttm.Format(at)
|
||||||
|
timeSlice = strings.Split(atTime, "T")
|
||||||
|
)
|
||||||
|
|
||||||
recurrence := string(fmt.Sprintf(
|
recurrence := string(fmt.Sprintf(
|
||||||
recurrenceTmpl,
|
recurrenceTmpl,
|
||||||
@ -270,16 +279,18 @@ func EventWithRecurrenceBytes(subject, recurrenceTimeZone string) []byte {
|
|||||||
defaultEventOrganizer, subject,
|
defaultEventOrganizer, subject,
|
||||||
defaultEventBody, defaultEventBodyPreview,
|
defaultEventBody, defaultEventBodyPreview,
|
||||||
NoOriginalStartDate, atTime, atTime, recurrence, attendeesTmpl,
|
NoOriginalStartDate, atTime, atTime, recurrence, attendeesTmpl,
|
||||||
true, NoCancelledOccurrences, NoExceptionOccurrences,
|
NoAttachments, NoCancelledOccurrences, NoExceptionOccurrences,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EventWithRecurrenceAndCancellationBytes(subject string) []byte {
|
func EventWithRecurrenceAndCancellationBytes(subject string) []byte {
|
||||||
tomorrow := time.Now().UTC().AddDate(0, 0, 1)
|
var (
|
||||||
at := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
tomorrow = time.Now().UTC().AddDate(0, 0, 1)
|
||||||
atTime := dttm.Format(at)
|
at = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
||||||
timeSlice := strings.Split(atTime, "T")
|
atTime = dttm.Format(at)
|
||||||
nextYear := tomorrow.AddDate(1, 0, 0)
|
timeSlice = strings.Split(atTime, "T")
|
||||||
|
nextYear = tomorrow.AddDate(1, 0, 0)
|
||||||
|
)
|
||||||
|
|
||||||
recurrence := string(fmt.Sprintf(
|
recurrence := string(fmt.Sprintf(
|
||||||
recurrenceTmpl,
|
recurrenceTmpl,
|
||||||
@ -296,17 +307,19 @@ func EventWithRecurrenceAndCancellationBytes(subject string) []byte {
|
|||||||
defaultEventOrganizer, subject,
|
defaultEventOrganizer, subject,
|
||||||
defaultEventBody, defaultEventBodyPreview,
|
defaultEventBody, defaultEventBodyPreview,
|
||||||
NoOriginalStartDate, atTime, atTime, recurrence, attendeesTmpl,
|
NoOriginalStartDate, atTime, atTime, recurrence, attendeesTmpl,
|
||||||
true, cancelledOccurrences, NoExceptionOccurrences,
|
defaultEventAttachments, cancelledOccurrences, NoExceptionOccurrences,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EventWithRecurrenceAndExceptionBytes(subject string) []byte {
|
func EventWithRecurrenceAndExceptionBytes(subject string) []byte {
|
||||||
tomorrow := time.Now().UTC().AddDate(0, 0, 1)
|
var (
|
||||||
at := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
tomorrow = time.Now().UTC().AddDate(0, 0, 1)
|
||||||
atTime := dttm.Format(at)
|
at = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
||||||
timeSlice := strings.Split(atTime, "T")
|
atTime = dttm.Format(at)
|
||||||
newTime := dttm.Format(tomorrow.AddDate(0, 0, 1))
|
timeSlice = strings.Split(atTime, "T")
|
||||||
originalStartDate := dttm.FormatTo(at, dttm.TabularOutput)
|
newTime = dttm.Format(tomorrow.AddDate(0, 0, 1))
|
||||||
|
originalStartDate = dttm.FormatTo(at, dttm.TabularOutput)
|
||||||
|
)
|
||||||
|
|
||||||
recurrence := string(fmt.Sprintf(
|
recurrence := string(fmt.Sprintf(
|
||||||
recurrenceTmpl,
|
recurrenceTmpl,
|
||||||
@ -321,7 +334,43 @@ func EventWithRecurrenceAndExceptionBytes(subject string) []byte {
|
|||||||
defaultEventBody, defaultEventBodyPreview,
|
defaultEventBody, defaultEventBodyPreview,
|
||||||
fmt.Sprintf(originalStartDateFormat, originalStartDate),
|
fmt.Sprintf(originalStartDateFormat, originalStartDate),
|
||||||
newTime, newTime, NoRecurrence, attendeesTmpl,
|
newTime, newTime, NoRecurrence, attendeesTmpl,
|
||||||
false, NoCancelledOccurrences, NoExceptionOccurrences,
|
NoAttachments, NoCancelledOccurrences, NoExceptionOccurrences,
|
||||||
|
)
|
||||||
|
exceptionOccurrences := fmt.Sprintf(exceptionOccurrencesFormat, exceptionEvent)
|
||||||
|
|
||||||
|
return EventWith(
|
||||||
|
defaultEventOrganizer, subject,
|
||||||
|
defaultEventBody, defaultEventBodyPreview,
|
||||||
|
NoOriginalStartDate, atTime, atTime, recurrence, attendeesTmpl,
|
||||||
|
defaultEventAttachments, NoCancelledOccurrences, exceptionOccurrences,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EventWithRecurrenceAndExceptionAndAttachmentBytes(subject string) []byte {
|
||||||
|
var (
|
||||||
|
tomorrow = time.Now().UTC().AddDate(0, 0, 1)
|
||||||
|
at = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
||||||
|
atTime = dttm.Format(at)
|
||||||
|
timeSlice = strings.Split(atTime, "T")
|
||||||
|
newTime = dttm.Format(tomorrow.AddDate(0, 0, 1))
|
||||||
|
originalStartDate = dttm.FormatTo(at, dttm.TabularOutput)
|
||||||
|
)
|
||||||
|
|
||||||
|
recurrence := string(fmt.Sprintf(
|
||||||
|
recurrenceTmpl,
|
||||||
|
strconv.Itoa(int(at.Month())),
|
||||||
|
strconv.Itoa(at.Day()),
|
||||||
|
timeSlice[0],
|
||||||
|
`"UTC"`,
|
||||||
|
))
|
||||||
|
|
||||||
|
exceptionEvent := EventWith(
|
||||||
|
defaultEventOrganizer, subject+"(modified)",
|
||||||
|
defaultEventBody, defaultEventBodyPreview,
|
||||||
|
fmt.Sprintf(originalStartDateFormat, originalStartDate),
|
||||||
|
newTime, newTime, NoRecurrence, attendeesTmpl,
|
||||||
|
"\"attachments\":["+fmt.Sprintf(eventAttachmentFormat, "exception-database.db")+"],",
|
||||||
|
NoCancelledOccurrences, NoExceptionOccurrences,
|
||||||
)
|
)
|
||||||
exceptionOccurrences := fmt.Sprintf(
|
exceptionOccurrences := fmt.Sprintf(
|
||||||
exceptionOccurrencesFormat,
|
exceptionOccurrencesFormat,
|
||||||
@ -332,20 +381,22 @@ func EventWithRecurrenceAndExceptionBytes(subject string) []byte {
|
|||||||
defaultEventOrganizer, subject,
|
defaultEventOrganizer, subject,
|
||||||
defaultEventBody, defaultEventBodyPreview,
|
defaultEventBody, defaultEventBodyPreview,
|
||||||
NoOriginalStartDate, atTime, atTime, recurrence, attendeesTmpl,
|
NoOriginalStartDate, atTime, atTime, recurrence, attendeesTmpl,
|
||||||
true, NoCancelledOccurrences, exceptionOccurrences,
|
defaultEventAttachments, NoCancelledOccurrences, exceptionOccurrences,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EventWithAttendeesBytes(subject string) []byte {
|
func EventWithAttendeesBytes(subject string) []byte {
|
||||||
tomorrow := time.Now().UTC().AddDate(0, 0, 1)
|
var (
|
||||||
at := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
tomorrow = time.Now().UTC().AddDate(0, 0, 1)
|
||||||
atTime := dttm.Format(at)
|
at = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), tomorrow.Hour(), 0, 0, 0, time.UTC)
|
||||||
|
atTime = dttm.Format(at)
|
||||||
|
)
|
||||||
|
|
||||||
return EventWith(
|
return EventWith(
|
||||||
defaultEventOrganizer, subject,
|
defaultEventOrganizer, subject,
|
||||||
defaultEventBody, defaultEventBodyPreview,
|
defaultEventBody, defaultEventBodyPreview,
|
||||||
NoOriginalStartDate, atTime, atTime, NoRecurrence, attendeesTmpl,
|
NoOriginalStartDate, atTime, atTime, NoRecurrence, attendeesTmpl,
|
||||||
true, NoCancelledOccurrences, NoExceptionOccurrences,
|
defaultEventAttachments, NoCancelledOccurrences, NoExceptionOccurrences,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,13 +408,9 @@ func EventWithAttendeesBytes(subject string) []byte {
|
|||||||
func EventWith(
|
func EventWith(
|
||||||
organizer, subject, body, bodyPreview,
|
organizer, subject, body, bodyPreview,
|
||||||
originalStartDate, startDateTime, endDateTime, recurrence, attendees string,
|
originalStartDate, startDateTime, endDateTime, recurrence, attendees string,
|
||||||
hasAttachments bool, cancelledOccurrences, exceptionOccurrences string,
|
attachments string, cancelledOccurrences, exceptionOccurrences string,
|
||||||
) []byte {
|
) []byte {
|
||||||
var attachments string
|
hasAttachments := len(attachments) > 0
|
||||||
if hasAttachments {
|
|
||||||
attachments = eventAttachment
|
|
||||||
}
|
|
||||||
|
|
||||||
startDateTime = strings.TrimSuffix(startDateTime, "Z")
|
startDateTime = strings.TrimSuffix(startDateTime, "Z")
|
||||||
endDateTime = strings.TrimSuffix(endDateTime, "Z")
|
endDateTime = strings.TrimSuffix(endDateTime, "Z")
|
||||||
|
|
||||||
|
|||||||
@ -124,6 +124,10 @@ func (suite *RestoreIntgSuite) TestRestoreEvent() {
|
|||||||
name: "Test exceptionOccurrences",
|
name: "Test exceptionOccurrences",
|
||||||
bytes: exchMock.EventWithRecurrenceAndExceptionBytes(subject),
|
bytes: exchMock.EventWithRecurrenceAndExceptionBytes(subject),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Test exceptionOccurrences with different attachments",
|
||||||
|
bytes: exchMock.EventWithRecurrenceAndExceptionAndAttachmentBytes(subject),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -369,3 +373,70 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreIntgSuite) TestRestoreAndBackupEvent_recurringInstancesWithAttachments() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
var (
|
||||||
|
userID = tester.M365UserID(t)
|
||||||
|
subject = testdata.DefaultRestoreConfig("event").Location
|
||||||
|
handler = newEventRestoreHandler(suite.ac)
|
||||||
|
)
|
||||||
|
|
||||||
|
calendar, err := handler.ac.CreateContainer(ctx, userID, subject, "")
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
calendarID := ptr.Val(calendar.GetId())
|
||||||
|
|
||||||
|
bytes := exchMock.EventWithRecurrenceAndExceptionAndAttachmentBytes("Reoccurring event restore and backup test")
|
||||||
|
info, err := handler.restore(
|
||||||
|
ctx,
|
||||||
|
bytes,
|
||||||
|
userID, calendarID,
|
||||||
|
fault.New(true))
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
assert.NotNil(t, info, "event item info")
|
||||||
|
|
||||||
|
ec, err := handler.ac.Stable.
|
||||||
|
Client().
|
||||||
|
Users().
|
||||||
|
ByUserId(userID).
|
||||||
|
Calendars().
|
||||||
|
ByCalendarId(calendarID).
|
||||||
|
Events().
|
||||||
|
Get(ctx, nil)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
evts := ec.GetValue()
|
||||||
|
assert.Len(t, evts, 1, "count of events")
|
||||||
|
|
||||||
|
sp, info, err := suite.ac.Events().GetItem(ctx, userID, ptr.Val(evts[0].GetId()), false, fault.New(true))
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
assert.NotNil(t, info, "event item info")
|
||||||
|
|
||||||
|
body, err := suite.ac.Events().Serialize(ctx, sp, userID, ptr.Val(evts[0].GetId()))
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
event, err := api.BytesToEventable(body)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
assert.NotNil(t, event.GetRecurrence(), "recurrence")
|
||||||
|
eo := event.GetAdditionalData()["exceptionOccurrences"]
|
||||||
|
assert.NotNil(t, eo, "exceptionOccurrences")
|
||||||
|
|
||||||
|
assert.NotEqual(
|
||||||
|
t,
|
||||||
|
ptr.Val(event.GetSubject()),
|
||||||
|
ptr.Val(eo.([]any)[0].(map[string]any)["subject"].(*string)),
|
||||||
|
"name equal")
|
||||||
|
|
||||||
|
atts := eo.([]any)[0].(map[string]any)["attachments"]
|
||||||
|
assert.NotEqual(
|
||||||
|
t,
|
||||||
|
ptr.Val(event.GetAttachments()[0].GetName()),
|
||||||
|
ptr.Val(atts.([]any)[0].(map[string]any)["name"].(*string)),
|
||||||
|
"attachment name equal")
|
||||||
|
}
|
||||||
|
|||||||
@ -815,7 +815,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
suite.user, subject, body, body,
|
suite.user, subject, body, body,
|
||||||
exchMock.NoOriginalStartDate, now, now,
|
exchMock.NoOriginalStartDate, now, now,
|
||||||
exchMock.NoRecurrence, exchMock.NoAttendees,
|
exchMock.NoRecurrence, exchMock.NoAttendees,
|
||||||
false, exchMock.NoCancelledOccurrences,
|
exchMock.NoAttachments, exchMock.NoCancelledOccurrences,
|
||||||
exchMock.NoExceptionOccurrences)
|
exchMock.NoExceptionOccurrences)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -241,10 +241,13 @@ func (c Events) GetItem(
|
|||||||
return nil, nil, clues.Wrap(err, "fixup exception occurrences")
|
return nil, nil, clues.Wrap(err, "fixup exception occurrences")
|
||||||
}
|
}
|
||||||
|
|
||||||
attachments, err := c.getAttachments(ctx, event, immutableIDs, userID, itemID)
|
var attachments []models.Attachmentable
|
||||||
|
if ptr.Val(event.GetHasAttachments()) || HasAttachments(event.GetBody()) {
|
||||||
|
attachments, err = c.GetAttachments(ctx, immutableIDs, userID, itemID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event.SetAttachments(attachments)
|
event.SetAttachments(attachments)
|
||||||
|
|
||||||
@ -286,10 +289,14 @@ func fixupExceptionOccurrences(
|
|||||||
|
|
||||||
// OPTIMIZATION: We don't have to store any of the
|
// OPTIMIZATION: We don't have to store any of the
|
||||||
// attachments that carry over from the original
|
// attachments that carry over from the original
|
||||||
attachments, err := client.getAttachments(ctx, evt, immutableIDs, userID, ptr.Val(evt.GetId()))
|
|
||||||
|
var attachments []models.Attachmentable
|
||||||
|
if ptr.Val(event.GetHasAttachments()) || HasAttachments(event.GetBody()) {
|
||||||
|
attachments, err = client.GetAttachments(ctx, immutableIDs, userID, ptr.Val(evt.GetId()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "getting exception attachments").
|
return clues.Wrap(err, "getting event instance attachments").
|
||||||
With("exception_event_id", ptr.Val(evt.GetId()))
|
With("event_instance_id", ptr.Val(evt.GetId()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This odd roundabout way of doing this is required as
|
// This odd roundabout way of doing this is required as
|
||||||
@ -370,17 +377,12 @@ func parseableToMap(att serialization.Parsable) (map[string]any, error) {
|
|||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Events) getAttachments(
|
func (c Events) GetAttachments(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
event models.Eventable,
|
|
||||||
immutableIDs bool,
|
immutableIDs bool,
|
||||||
userID string,
|
userID string,
|
||||||
itemID string,
|
itemID string,
|
||||||
) ([]models.Attachmentable, error) {
|
) ([]models.Attachmentable, error) {
|
||||||
if !ptr.Val(event.GetHasAttachments()) && !HasAttachments(event.GetBody()) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &users.ItemEventsItemAttachmentsRequestBuilderGetRequestConfiguration{
|
config := &users.ItemEventsItemAttachmentsRequestBuilderGetRequestConfiguration{
|
||||||
QueryParameters: &users.ItemEventsItemAttachmentsRequestBuilderGetQueryParameters{
|
QueryParameters: &users.ItemEventsItemAttachmentsRequestBuilderGetQueryParameters{
|
||||||
Expand: []string{"microsoft.graph.itemattachment/item"},
|
Expand: []string{"microsoft.graph.itemattachment/item"},
|
||||||
@ -403,6 +405,23 @@ func (c Events) getAttachments(
|
|||||||
return attached.GetValue(), nil
|
return attached.GetValue(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Events) DeleteAttachment(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, calendarID, eventID, attachmentID string,
|
||||||
|
) error {
|
||||||
|
return c.Stable.
|
||||||
|
Client().
|
||||||
|
Users().
|
||||||
|
ByUserId(userID).
|
||||||
|
Calendars().
|
||||||
|
ByCalendarId(calendarID).
|
||||||
|
Events().
|
||||||
|
ByEventId(eventID).
|
||||||
|
Attachments().
|
||||||
|
ByAttachmentId(attachmentID).
|
||||||
|
Delete(ctx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (c Events) GetItemInstances(
|
func (c Events) GetItemInstances(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, itemID string,
|
userID, itemID string,
|
||||||
|
|||||||
@ -26,5 +26,3 @@ included in backup and restore.
|
|||||||
* SharePoint document library data can't be restored after the library has been deleted.
|
* SharePoint document library data can't be restored after the library has been deleted.
|
||||||
|
|
||||||
* Sharing information of items in OneDrive/SharePoint using sharing links aren't backed up and restored.
|
* Sharing information of items in OneDrive/SharePoint using sharing links aren't backed up and restored.
|
||||||
|
|
||||||
* Changes to attachments in instances of recurring events compared to the series master aren't restored
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user