centralize contact location in cache resolver

moves all handling of the "contacts" root folder presence into
the contacts container cache.  This ensures we can
remove any one-off code specifically targeting contacts'
peculiar root folder handling out of generic code paths and into
a data-specific owner.

The addition of a "defaultRootLocation" api is necessary otherwise
the cache lookup on restore always fails (or worse, gets confused
by the well known folder and an identically named subfolder),
and we end up with 409 responses and other issues.
This commit is contained in:
ryanfkeepers 2023-09-01 15:56:03 -06:00
parent 8d2070409f
commit 135c638324
8 changed files with 51 additions and 40 deletions

View File

@ -91,12 +91,10 @@ func (mg mockGetter) GetAddedAndRemovedItemIDs(
var _ graph.ContainerResolver = &mockResolver{}
type (
mockResolver struct {
items []graph.CachedContainer
added map[string]string
}
)
type mockResolver struct {
items []graph.CachedContainer
added map[string]string
}
func newMockResolver(items ...mockContainer) mockResolver {
is := make([]graph.CachedContainer, 0, len(items))
@ -125,9 +123,10 @@ func (m mockResolver) DestinationNameToID(dest string) string { return m.added[d
func (m mockResolver) IDToPath(context.Context, string) (*path.Builder, *path.Builder, error) {
return nil, nil, nil
}
func (m mockResolver) PathInCache(string) (string, bool) { return "", false }
func (m mockResolver) LocationInCache(string) (string, bool) { return "", false }
func (m mockResolver) Populate(context.Context, *fault.Bus, string, ...string) error { return nil }
func (m mockResolver) PathInCache(string) (string, bool) { return "", false }
func (m mockResolver) LocationInCache(string) (string, bool) { return "", false }
func (m mockResolver) Populate(context.Context, *fault.Bus, string) error { return nil }
func (m mockResolver) DefaultRootLocation() string { return "" }
// ---------------------------------------------------------------------------
// Unit tests

View File

@ -37,25 +37,27 @@ func (r *contactRefresher) refreshContainer(
type contactContainerCache struct {
*containerResolver
enumer containersEnumerator
getter containerGetter
userID string
enumer containersEnumerator
getter containerGetter
userID string
rootLocation string
}
func (cfc *contactContainerCache) populateContactRoot(
ctx context.Context,
directoryID string,
baseContainerPath []string,
) error {
f, err := cfc.getter.GetContainerByID(ctx, cfc.userID, directoryID)
if err != nil {
return clues.Wrap(err, "fetching root folder")
}
cfc.rootLocation = ptr.Val(f.GetDisplayName())
temp := graph.NewCacheFolder(
f,
path.Builder{}.Append(ptr.Val(f.GetId())), // path of IDs
path.Builder{}.Append(baseContainerPath...)) // display location
path.Builder{}.Append(ptr.Val(f.GetId())), // path of IDs
path.Builder{}.Append(cfc.rootLocation)) // display location
if err := cfc.addFolder(&temp); err != nil {
return clues.Wrap(err, "adding resolver dir").WithClues(ctx)
}
@ -71,9 +73,8 @@ func (cfc *contactContainerCache) Populate(
ctx context.Context,
errs *fault.Bus,
baseID string,
baseContainerPath ...string,
) error {
if err := cfc.init(ctx, baseID, baseContainerPath); err != nil {
if err := cfc.init(ctx, baseID); err != nil {
return clues.Wrap(err, "initializing")
}
@ -92,7 +93,6 @@ func (cfc *contactContainerCache) Populate(
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)
@ -105,5 +105,9 @@ func (cfc *contactContainerCache) init(
})
}
return cfc.populateContactRoot(ctx, baseNode, baseContainerPath)
return cfc.populateContactRoot(ctx, baseNode)
}
func (cfc *contactContainerCache) DefaultRootLocation() string {
return cfc.rootLocation
}

View File

@ -719,16 +719,15 @@ func (suite *ContainerResolverSuite) TestPopulate() {
}
tests := []struct {
name, folderInCache, root, basePath string
resolverFunc func(t *testing.T) graph.ContainerResolver
canFind assert.BoolAssertionFunc
name, folderInCache, root string
resolverFunc func(t *testing.T) graph.ContainerResolver
canFind assert.BoolAssertionFunc
}{
{
name: "Default Event Cache",
// Fine as long as this isn't running against a migrated Exchange server.
folderInCache: api.DefaultCalendar,
root: api.DefaultCalendar,
basePath: api.DefaultCalendar,
resolverFunc: eventFunc,
canFind: assert.True,
},
@ -750,7 +749,6 @@ func (suite *ContainerResolverSuite) TestPopulate() {
name: "Default Contact Cache",
folderInCache: api.DefaultContacts,
root: api.DefaultContacts,
basePath: api.DefaultContacts,
canFind: assert.True,
resolverFunc: contactFunc,
},
@ -778,7 +776,7 @@ func (suite *ContainerResolverSuite) TestPopulate() {
resolver := test.resolverFunc(t)
err := resolver.Populate(ctx, fault.New(true), test.root, test.basePath)
err := resolver.Populate(ctx, fault.New(true), test.root)
require.NoError(t, err, clues.ToCore(err))
_, isFound := resolver.LocationInCache(test.folderInCache)

View File

@ -64,7 +64,6 @@ func (ecc *eventContainerCache) Populate(
ctx context.Context,
errs *fault.Bus,
baseID string,
baseContainerPath ...string,
) error {
if err := ecc.init(ctx); err != nil {
return clues.Wrap(err, "initializing")
@ -112,3 +111,7 @@ func (ecc *eventContainerCache) AddToCache(ctx context.Context, f graph.Containe
return nil
}
func (ecc *eventContainerCache) DefaultRootLocation() string {
return ""
}

View File

@ -94,7 +94,6 @@ func (mc *mailContainerCache) Populate(
ctx context.Context,
errs *fault.Bus,
baseID string,
baseContainerPath ...string,
) error {
if err := mc.init(ctx); err != nil {
return clues.Wrap(err, "initializing")
@ -111,3 +110,7 @@ func (mc *mailContainerCache) Populate(
return nil
}
func (mc *mailContainerCache) DefaultRootLocation() string {
return ""
}

View File

@ -69,11 +69,6 @@ func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() {
name: "Node Root",
root: topFolderID,
},
{
name: "Node Root Non-empty Path",
root: topFolderID,
path: []string{"some", "leading", "path"},
},
}
userID := tconfig.M365UserID(suite.T())
@ -95,7 +90,7 @@ func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() {
getter: acm,
}
err = mfc.Populate(ctx, fault.New(true), test.root, test.path...)
err = mfc.Populate(ctx, fault.New(true), test.root)
require.NoError(t, err, clues.ToCore(err))
p, l, err := mfc.IDToPath(ctx, testFolderID)

View File

@ -135,11 +135,15 @@ func CreateDestination(
errs *fault.Bus,
) (string, graph.ContainerResolver, error) {
var (
cache = gcr
restoreLoc = &path.Builder{}
containerParentID string
restoreLoc = &path.Builder{}
containerParentID string
defaultRootLocation = gcr.DefaultRootLocation()
)
if len(defaultRootLocation) > 0 {
restoreLoc = restoreLoc.Append(defaultRootLocation)
}
for _, container := range destination.Elements() {
restoreLoc = restoreLoc.Append(container)
@ -152,14 +156,14 @@ func CreateDestination(
containerID, err := getOrPopulateContainer(
ictx,
ca,
cache,
gcr,
restoreLoc,
resourceID,
containerParentID,
container,
errs)
if err != nil {
return "", cache, clues.Stack(err)
return "", gcr, clues.Stack(err)
}
containerParentID = containerID
@ -167,7 +171,7 @@ func CreateDestination(
// containerParentID now identifies the last created container,
// not its parent.
return containerParentID, cache, nil
return containerParentID, gcr, nil
}
func getOrPopulateContainer(

View File

@ -64,7 +64,7 @@ type ContainerResolver interface {
// @param ctx is necessary param for Graph API tracing
// @param baseFolderID represents the M365ID base that the resolver will
// conclude its search. Default input is "".
Populate(ctx context.Context, errs *fault.Bus, baseFolderID string, baseContainerPath ...string) error
Populate(ctx context.Context, errs *fault.Bus, baseFolderID string) error
// PathInCache performs a look up of a path representation
// and returns the m365ID of directory iff the pathString
@ -81,6 +81,11 @@ type ContainerResolver interface {
// Items returns the containers in the cache.
Items() []CachedContainer
// DefaultRootLocation provides implementations which hard-code a parent
// folder root in their location path with a way to give downstream comparators
// that same location name.
DefaultRootLocation() string
}
// ======================================