Do not backup shared calendars (#5207)
Skip backup of shared calendars. These will be backed up with the resource that owns the calendar. --- #### Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [x] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature - [x] 🐛 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. --> * #<issue> #### Test Plan <!-- How will this be tested prior to merging.--> - [x] 💪 Manual - [ ] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
8502e1fee6
commit
b3b52c0dfc
@ -3,11 +3,13 @@ package exchange
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"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"
|
||||||
@ -1017,6 +1019,210 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestAddToCache() {
|
|||||||
assert.Equal(t, m.expectedLocation, l.String(), "location path")
|
assert.Equal(t, m.expectedLocation, l.String(), "location path")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// EventContainerCache unit tests
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var _ containerGetter = mockEventContainerGetter{}
|
||||||
|
|
||||||
|
type mockEventContainerGetter struct {
|
||||||
|
// containerGetter returns graph.CalendarDisplayable, unlike containersEnumerator
|
||||||
|
// which returns models.Calendarable.
|
||||||
|
idToCalendar map[string]graph.CalendarDisplayable
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockEventContainerGetter) GetContainerByID(
|
||||||
|
ctx context.Context,
|
||||||
|
userID string,
|
||||||
|
dirID string,
|
||||||
|
) (graph.Container, error) {
|
||||||
|
return m.idToCalendar[dirID], m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ containersEnumerator[models.Calendarable] = mockEventContainersEnumerator{}
|
||||||
|
|
||||||
|
type mockEventContainersEnumerator struct {
|
||||||
|
containers []models.Calendarable
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockEventContainersEnumerator) EnumerateContainers(
|
||||||
|
ctx context.Context,
|
||||||
|
userID string,
|
||||||
|
baseDirID string,
|
||||||
|
) ([]models.Calendarable, error) {
|
||||||
|
return m.containers, m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventsContainerUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventsContainerUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &EventsContainerUnitSuite{
|
||||||
|
Suite: tester.NewUnitSuite(t),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCalendar(
|
||||||
|
id, name, ownerEmail string,
|
||||||
|
isDefault bool,
|
||||||
|
) *models.Calendar {
|
||||||
|
c := models.NewCalendar()
|
||||||
|
|
||||||
|
c.SetId(ptr.To(id))
|
||||||
|
c.SetName(ptr.To(name))
|
||||||
|
c.SetIsDefaultCalendar(ptr.To(isDefault))
|
||||||
|
|
||||||
|
if len(ownerEmail) > 0 {
|
||||||
|
email := models.NewEmailAddress()
|
||||||
|
|
||||||
|
email.SetAddress(ptr.To(ownerEmail))
|
||||||
|
// Set crc as the name for keeping this func simple.
|
||||||
|
eName := fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(ownerEmail)))
|
||||||
|
email.SetName(ptr.To(eName))
|
||||||
|
c.SetOwner(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if we skip backup of shared calendars. These will be backed up for
|
||||||
|
// the resource owner that owns the calendar.
|
||||||
|
func (suite *EventsContainerUnitSuite) TestPopulate_SkipSharedCalendars() {
|
||||||
|
// map of calendars
|
||||||
|
calendars := map[string]models.Calendarable{
|
||||||
|
// Default calendars Dx
|
||||||
|
"D0": makeCalendar(api.DefaultCalendar, api.DefaultCalendar, "owner@bar.com", true),
|
||||||
|
// Atypical, but creating another default calendar for testing purposes.
|
||||||
|
"D1": makeCalendar("D1", "D1", "owner@bar.com", true),
|
||||||
|
// Shared calendars Sx
|
||||||
|
"S0": makeCalendar("S0", "S0", "sharer@bar.com", false),
|
||||||
|
// Owned calendars, not default Ox
|
||||||
|
"O0": makeCalendar("O0", "O0", "owner@bar.com", false),
|
||||||
|
// Calendars with missing owner informaton
|
||||||
|
"M0": makeCalendar("M0", "M0", "", false),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always return default calendar from the getter.
|
||||||
|
getContainersByID := func() map[string]graph.CalendarDisplayable {
|
||||||
|
return map[string]graph.CalendarDisplayable{
|
||||||
|
api.DefaultCalendar: *graph.CreateCalendarDisplayable(calendars["D0"], "parentID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
enumerateContainers func() []models.Calendarable
|
||||||
|
expectErr assert.ErrorAssertionFunc
|
||||||
|
assertFunc func(t *testing.T, ecc *eventContainerCache)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "one default calendar, one shared",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["D0"],
|
||||||
|
calendars["S0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 1, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2 default calendars, 1 shared",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["D0"],
|
||||||
|
calendars["D1"],
|
||||||
|
calendars["S0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 2, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
assert.NotNil(t, ecc.cache["D1"], "missing default calendar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1 default, 1 additional owned, 1 shared",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["D0"],
|
||||||
|
calendars["O0"],
|
||||||
|
calendars["S0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 2, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
assert.NotNil(t, ecc.cache["O0"], "missing owned calendar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1 default, 1 with missing owner information",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["D0"],
|
||||||
|
calendars["M0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 2, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
assert.NotNil(t, ecc.cache["M0"], "missing calendar with missing owner info")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Unlikely to happen, but we should back up the calendar if the default owner
|
||||||
|
// cannot be determined, i.e. default calendar is missing.
|
||||||
|
name: "default owner info missing",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["S0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 2, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
assert.NotNil(t, ecc.cache["S0"], "missing additional calendar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
ecc := &eventContainerCache{
|
||||||
|
userID: "test",
|
||||||
|
enumer: mockEventContainersEnumerator{containers: test.enumerateContainers()},
|
||||||
|
getter: mockEventContainerGetter{idToCalendar: getContainersByID()},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ecc.Populate(ctx, fault.New(true), "root", "root")
|
||||||
|
test.expectErr(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
test.assertFunc(t, ecc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// container resolver integration suite
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
type ContainerResolverIntgSuite struct {
|
type ContainerResolverIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
m365 its.M365IntgTestSetup
|
m365 its.M365IntgTestSetup
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package exchange
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
@ -60,6 +61,16 @@ func (ecc *eventContainerCache) populateEventRoot(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSharedCalendar(defaultCalendarOwner string, c models.Calendarable) bool {
|
||||||
|
// If we can't determine the owner, assume the calendar is owned by the
|
||||||
|
// user.
|
||||||
|
if len(defaultCalendarOwner) == 0 || c.GetOwner() == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return !strings.EqualFold(defaultCalendarOwner, ptr.Val(c.GetOwner().GetAddress()))
|
||||||
|
}
|
||||||
|
|
||||||
// Populate utility function for populating eventCalendarCache.
|
// Populate utility function for populating eventCalendarCache.
|
||||||
// Executes 1 additional Graph Query
|
// Executes 1 additional Graph Query
|
||||||
// @param baseID: ignored. Present to conform to interface
|
// @param baseID: ignored. Present to conform to interface
|
||||||
@ -89,11 +100,39 @@ func (ecc *eventContainerCache) Populate(
|
|||||||
return clues.WrapWC(ctx, err, "enumerating containers")
|
return clues.WrapWC(ctx, err, "enumerating containers")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultCalendarOwner string
|
||||||
|
|
||||||
|
// Determine the owner for the default calendar. We'll use this to detect and
|
||||||
|
// skip shared calendars that are not owned by this user.
|
||||||
|
for _, c := range containers {
|
||||||
|
if ptr.Val(c.GetIsDefaultCalendar()) && c.GetOwner() != nil {
|
||||||
|
defaultCalendarOwner = ptr.Val(c.GetOwner().GetAddress())
|
||||||
|
ctx = clues.Add(ctx, "default_calendar_owner", defaultCalendarOwner)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
if el.Failure() != nil {
|
if el.Failure() != nil {
|
||||||
return el.Failure()
|
return el.Failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip shared calendars if we have enough information to determine the owner
|
||||||
|
if isSharedCalendar(defaultCalendarOwner, c) {
|
||||||
|
var ownerEmail string
|
||||||
|
if c.GetOwner() != nil {
|
||||||
|
ownerEmail = ptr.Val(c.GetOwner().GetAddress())
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Ctx(ctx).Infow(
|
||||||
|
"skipping shared calendar",
|
||||||
|
"name", ptr.Val(c.GetName()),
|
||||||
|
"owner", ownerEmail)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
cacheFolder := graph.NewCacheFolder(
|
cacheFolder := graph.NewCacheFolder(
|
||||||
api.CalendarDisplayable{Calendarable: c},
|
api.CalendarDisplayable{Calendarable: c},
|
||||||
path.Builder{}.Append(ptr.Val(c.GetId())),
|
path.Builder{}.Append(ptr.Val(c.GetId())),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user