extract container transforms from the api layer (#4478)

removes the cache container transformation funcs
from the exchange api container enumeration params. This ensures a cleaner separation of ownership where the api is only responsible for enumerating containers, and the caller is responsible for transforming them.

---

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

- [x]  No

#### Type of change

- [ ] 🧹 Tech Debt/Cleanup


#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-10-24 17:44:08 -06:00 committed by GitHub
parent 89f59eea91
commit 7f8becb8f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 128 additions and 195 deletions

View File

@ -21,9 +21,6 @@ import (
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
) )
// CreateCollections - utility function that retrieves M365
// IDs through Microsoft Graph API. The selectors.ExchangeScope
// determines the type of collections that are retrieved.
func CreateCollections( func CreateCollections(
ctx context.Context, ctx context.Context,
bpc inject.BackupProducerConfig, bpc inject.BackupProducerConfig,

View File

@ -796,53 +796,25 @@ func (suite *BackupIntgSuite) TestContactSerializationRegression() {
// TestEventsSerializationRegression ensures functionality of createCollections // TestEventsSerializationRegression ensures functionality of createCollections
// to be able to successfully query, download and restore event objects // to be able to successfully query, download and restore event objects
func (suite *BackupIntgSuite) TestEventsSerializationRegression() { func (suite *BackupIntgSuite) TestEventsSerializationRegression() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
var ( var (
users = []string{suite.user} users = []string{suite.user}
handlers = BackupHandlers(suite.ac) handlers = BackupHandlers(suite.ac)
calID string
bdayID string
) )
fn := func(gcc graph.CachedContainer) error {
if ptr.Val(gcc.GetDisplayName()) == api.DefaultCalendar {
calID = ptr.Val(gcc.GetId())
}
if ptr.Val(gcc.GetDisplayName()) == "Birthdays" {
bdayID = ptr.Val(gcc.GetId())
}
return nil
}
err := suite.ac.Events().EnumerateContainers(
ctx,
suite.user,
"",
false,
fn,
fault.New(true))
require.NoError(t, err, clues.ToCore(err))
tests := []struct { tests := []struct {
name, expected string name, expectedContainerName string
scope selectors.ExchangeScope scope selectors.ExchangeScope
}{ }{
{ {
name: "Default Event Calendar", name: "Default Event Calendar",
expected: calID, expectedContainerName: api.DefaultCalendar,
scope: selectors.NewExchangeBackup(users).EventCalendars( scope: selectors.NewExchangeBackup(users).EventCalendars(
[]string{api.DefaultCalendar}, []string{api.DefaultCalendar},
selectors.PrefixMatch())[0], selectors.PrefixMatch())[0],
}, },
{ {
name: "Birthday Calendar", name: "Birthday Calendar",
expected: bdayID, expectedContainerName: "Birthdays",
scope: selectors.NewExchangeBackup(users).EventCalendars( scope: selectors.NewExchangeBackup(users).EventCalendars(
[]string{"Birthdays"}, []string{"Birthdays"},
selectors.PrefixMatch())[0], selectors.PrefixMatch())[0],
@ -879,13 +851,16 @@ func (suite *BackupIntgSuite) TestEventsSerializationRegression() {
wg.Add(len(collections)) wg.Add(len(collections))
for _, edc := range collections { for _, edc := range collections {
dlp, isDLP := edc.(data.LocationPather)
var isMetadata bool var isMetadata bool
if edc.FullPath().Service() != path.ExchangeMetadataService { if edc.FullPath().Service() == path.ExchangeService {
isMetadata = true require.True(t, isDLP, "must be a location pather")
assert.Equal(t, test.expected, edc.FullPath().Folder(false)) assert.Contains(t, dlp.LocationPath().Elements(), test.expectedContainerName)
} else { } else {
assert.Equal(t, "", edc.FullPath().Folder(false)) isMetadata = true
assert.Empty(t, edc.FullPath().Folder(false))
} }
for item := range edc.Items(ctx, fault.New(true)) { for item := range edc.Items(ctx, fault.New(true)) {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"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/m365/graph" "github.com/alcionai/corso/src/internal/m365/graph"
@ -37,11 +38,30 @@ func (r *contactRefresher) refreshContainer(
type contactContainerCache struct { type contactContainerCache struct {
*containerResolver *containerResolver
enumer containersEnumerator enumer containersEnumerator[models.ContactFolderable]
getter containerGetter getter containerGetter
userID string userID string
} }
func (cfc *contactContainerCache) init(
ctx context.Context,
baseNode string,
baseContainerPath []string,
) error {
if len(baseNode) == 0 {
return clues.New("m365 folderID required for base contact folder").WithClues(ctx)
}
if cfc.containerResolver == nil {
cfc.containerResolver = newContainerResolver(&contactRefresher{
userID: cfc.userID,
getter: cfc.getter,
})
}
return cfc.populateContactRoot(ctx, baseNode, baseContainerPath)
}
func (cfc *contactContainerCache) populateContactRoot( func (cfc *contactContainerCache) populateContactRoot(
ctx context.Context, ctx context.Context,
directoryID string, directoryID string,
@ -77,39 +97,35 @@ func (cfc *contactContainerCache) Populate(
return clues.Wrap(err, "initializing") return clues.Wrap(err, "initializing")
} }
err := cfc.enumer.EnumerateContainers( el := errs.Local()
containers, err := cfc.enumer.EnumerateContainers(
ctx, ctx,
cfc.userID, cfc.userID,
baseID, baseID,
false, false)
cfc.addFolder,
errs)
if err != nil { if err != nil {
return clues.Wrap(err, "enumerating containers") return clues.Wrap(err, "enumerating containers")
} }
for _, c := range containers {
if el.Failure() != nil {
return el.Failure()
}
cacheFolder := graph.NewCacheFolder(c, nil, nil)
err := cfc.addFolder(&cacheFolder)
if err != nil {
errs.AddRecoverable(
ctx,
graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
}
}
if err := cfc.populatePaths(ctx, errs); err != nil { if err := cfc.populatePaths(ctx, errs); err != nil {
return clues.Wrap(err, "populating paths") return clues.Wrap(err, "populating paths")
} }
return nil return el.Failure()
}
func (cfc *contactContainerCache) init(
ctx context.Context,
baseNode string,
baseContainerPath []string,
) error {
if len(baseNode) == 0 {
return clues.New("m365 folderID required for base contact folder").WithClues(ctx)
}
if cfc.containerResolver == nil {
cfc.containerResolver = newContainerResolver(&contactRefresher{
userID: cfc.userID,
getter: cfc.getter,
})
}
return cfc.populateContactRoot(ctx, baseNode, baseContainerPath)
} }

View File

@ -23,14 +23,12 @@ type containerGetter interface {
) (graph.Container, error) ) (graph.Container, error)
} }
type containersEnumerator interface { type containersEnumerator[T any] interface {
EnumerateContainers( EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, baseDirID string, userID, baseDirID string,
immutableIDs bool, immutableIDs bool,
fn func(graph.CachedContainer) error, ) ([]T, error)
errs *fault.Bus,
) error
} }
type containerRefresher interface { type containerRefresher interface {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"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/m365/graph" "github.com/alcionai/corso/src/internal/m365/graph"
@ -16,7 +17,7 @@ var _ graph.ContainerResolver = &eventContainerCache{}
type eventContainerCache struct { type eventContainerCache struct {
*containerResolver *containerResolver
enumer containersEnumerator enumer containersEnumerator[models.Calendarable]
getter containerGetter getter containerGetter
userID string userID string
} }
@ -70,22 +71,40 @@ func (ecc *eventContainerCache) Populate(
return clues.Wrap(err, "initializing") return clues.Wrap(err, "initializing")
} }
err := ecc.enumer.EnumerateContainers( el := errs.Local()
containers, err := ecc.enumer.EnumerateContainers(
ctx, ctx,
ecc.userID, ecc.userID,
"", "",
false, false)
ecc.addFolder,
errs)
if err != nil { if err != nil {
return clues.Wrap(err, "enumerating containers") return clues.Wrap(err, "enumerating containers")
} }
if err := ecc.populatePaths(ctx, errs); err != nil { for _, c := range containers {
return clues.Wrap(err, "establishing calendar paths") if el.Failure() != nil {
return el.Failure()
}
cacheFolder := graph.NewCacheFolder(
api.CalendarDisplayable{Calendarable: c},
path.Builder{}.Append(ptr.Val(c.GetId())),
path.Builder{}.Append(ptr.Val(c.GetName())))
err := ecc.addFolder(&cacheFolder)
if err != nil {
errs.AddRecoverable(
ctx,
graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
}
} }
return nil if err := ecc.populatePaths(ctx, errs); err != nil {
return clues.Wrap(err, "populating paths")
}
return el.Failure()
} }
// AddToCache adds container to map in field 'cache' // AddToCache adds container to map in field 'cache'

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/internal/m365/graph"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
@ -40,7 +41,7 @@ func (r *mailRefresher) refreshContainer(
// nameLookup map: Key: DisplayName Value: ID // nameLookup map: Key: DisplayName Value: ID
type mailContainerCache struct { type mailContainerCache struct {
*containerResolver *containerResolver
enumer containersEnumerator enumer containersEnumerator[models.MailFolderable]
getter containerGetter getter containerGetter
userID string userID string
} }
@ -100,20 +101,35 @@ func (mc *mailContainerCache) Populate(
return clues.Wrap(err, "initializing") return clues.Wrap(err, "initializing")
} }
err := mc.enumer.EnumerateContainers( el := errs.Local()
containers, err := mc.enumer.EnumerateContainers(
ctx, ctx,
mc.userID, mc.userID,
"", "",
false, false)
mc.addFolder,
errs)
if err != nil { if err != nil {
return clues.Wrap(err, "enumerating containers") return clues.Wrap(err, "enumerating containers")
} }
for _, c := range containers {
if el.Failure() != nil {
return el.Failure()
}
cacheFolder := graph.NewCacheFolder(c, nil, nil)
err := mc.addFolder(&cacheFolder)
if err != nil {
errs.AddRecoverable(
ctx,
graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
}
}
if err := mc.populatePaths(ctx, errs); err != nil { if err := mc.populatePaths(ctx, errs); err != nil {
return clues.Wrap(err, "populating paths") return clues.Wrap(err, "populating paths")
} }
return nil return el.Failure()
} }

View File

@ -10,7 +10,6 @@ import (
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/internal/m365/graph"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -67,42 +66,14 @@ func (p *contactsFoldersPageCtrl) ValidModTimes() bool {
return true return true
} }
// EnumerateContainers iterates through all of the users current // EnumerateContainers retrieves all of the user's current contact folders.
// contacts folders, transforming each to a graph.CacheFolder, and calling
// fn(cf).
// Contact folders are represented in their current state, and do
// not contain historical data.
func (c Contacts) EnumerateContainers( func (c Contacts) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, baseContainerID string, userID, baseContainerID string,
immutableIDs bool, immutableIDs bool,
fn func(graph.CachedContainer) error, ) ([]models.ContactFolderable, error) {
errs *fault.Bus, containers, err := batchEnumerateItems(ctx, c.NewContactFoldersPager(userID, baseContainerID, immutableIDs))
) error { return containers, graph.Stack(ctx, err).OrNil()
var (
el = errs.Local()
pgr = c.NewContactFoldersPager(userID, baseContainerID, immutableIDs)
)
containers, err := batchEnumerateItems(ctx, pgr)
if err != nil {
return graph.Stack(ctx, err)
}
for _, c := range containers {
if el.Failure() != nil {
break
}
gncf := graph.NewCacheFolder(c, nil, nil)
if err := fn(&gncf); err != nil {
errs.AddRecoverable(ctx, graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
}
return el.Failure()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -11,7 +11,6 @@ import (
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/internal/m365/graph"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -68,45 +67,14 @@ func (p *eventsCalendarsPageCtrl) ValidModTimes() bool {
return true return true
} }
// EnumerateContainers iterates through all of the users current // EnumerateContainers retrieves all of the user's current mail folders.
// events calendars, transforming each to a graph.CacheFolder, and calling
// fn(cf).
// Calendars are represented in their current state, and do
// not contain historical data.
func (c Events) EnumerateContainers( func (c Events) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, _ string, // baseContainerID not needed userID, _ string, // baseContainerID not needed here
immutableIDs bool, immutableIDs bool,
fn func(graph.CachedContainer) error, ) ([]models.Calendarable, error) {
errs *fault.Bus, containers, err := batchEnumerateItems(ctx, c.NewEventCalendarsPager(userID, immutableIDs))
) error { return containers, graph.Stack(ctx, err).OrNil()
var (
el = errs.Local()
pgr = c.NewEventCalendarsPager(userID, immutableIDs)
)
containers, err := batchEnumerateItems(ctx, pgr)
if err != nil {
return graph.Stack(ctx, err)
}
for _, c := range containers {
if el.Failure() != nil {
break
}
gncf := graph.NewCacheFolder(
CalendarDisplayable{Calendarable: c},
path.Builder{}.Append(ptr.Val(c.GetId())),
path.Builder{}.Append(ptr.Val(c.GetName())))
if err := fn(&gncf); err != nil {
errs.AddRecoverable(ctx, graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
}
return el.Failure()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -13,13 +13,11 @@ import (
"github.com/alcionai/corso/src/internal/common/dttm" "github.com/alcionai/corso/src/internal/common/dttm"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/m365/graph"
exchMock "github.com/alcionai/corso/src/internal/m365/service/exchange/mock" exchMock "github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/internal/tester/tconfig" "github.com/alcionai/corso/src/internal/tester/tconfig"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control/testdata" "github.com/alcionai/corso/src/pkg/control/testdata"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
) )
@ -294,8 +292,8 @@ func (suite *EventsAPIIntgSuite) TestEvents_canFindNonStandardFolder() {
var ( var (
found bool found bool
calID = ptr.Val(cal.GetId()) calID = ptr.Val(cal.GetId())
findContainer = func(gcc graph.CachedContainer) error { findContainer = func(mc models.Calendarable) error {
if ptr.Val(gcc.GetId()) == calID { if ptr.Val(mc.GetId()) == calID {
found = true found = true
} }
@ -303,14 +301,18 @@ func (suite *EventsAPIIntgSuite) TestEvents_canFindNonStandardFolder() {
} }
) )
err = ac.EnumerateContainers( containers, err := ac.EnumerateContainers(
ctx, ctx,
suite.its.user.id, suite.its.user.id,
api.DefaultCalendar, api.DefaultCalendar,
false, false)
findContainer,
fault.New(true))
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
for _, c := range containers {
err := findContainer(c)
require.NoError(t, err, clues.ToCore(err))
}
require.True( require.True(
t, t,
found, found,

View File

@ -11,7 +11,6 @@ import (
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/internal/m365/graph"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -64,42 +63,14 @@ func (p *mailFoldersPageCtrl) ValidModTimes() bool {
return true return true
} }
// EnumerateContainers iterates through all of the users current // EnumerateContainers retrieves all of the user's current mail folders.
// mail folders, transforming each to a graph.CacheFolder, and calling
// fn(cf).
// Folder hierarchy is represented in its current state, and does
// not contain historical data.
func (c Mail) EnumerateContainers( func (c Mail) EnumerateContainers(
ctx context.Context, ctx context.Context,
userID, _ string, // baseContainerID not needed here userID, _ string, // baseContainerID not needed here
immutableIDs bool, immutableIDs bool,
fn func(graph.CachedContainer) error, ) ([]models.MailFolderable, error) {
errs *fault.Bus, containers, err := batchEnumerateItems(ctx, c.NewMailFoldersPager(userID, immutableIDs))
) error { return containers, graph.Stack(ctx, err).OrNil()
var (
el = errs.Local()
pgr = c.NewMailFoldersPager(userID, immutableIDs)
)
containers, err := batchEnumerateItems(ctx, pgr)
if err != nil {
return graph.Stack(ctx, err)
}
for _, c := range containers {
if el.Failure() != nil {
break
}
gncf := graph.NewCacheFolder(c, nil, nil)
if err := fn(&gncf); err != nil {
errs.AddRecoverable(ctx, graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
continue
}
}
return el.Failure()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------