Add restore path generation code (#3362)

In preparation for switching to folder IDs,
add logic to generate the restore path based
on prefix information from the RepoRef and
LocationRef of items

Contains fallback code (and tests) to handle
older details versions that may not have had
LocationRef

Manually tested restore from old backup that
didn't have any LocationRef information

Manually tested restore checking that
calendar names are shown instead of IDs in
progress bar

---

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

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #3197
* fixes #3218

#### Test Plan

- [x] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
ashmrtn 2023-05-10 19:49:32 -07:00 committed by GitHub
parent c5b388a721
commit 2e4fc71310
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 632 additions and 101 deletions

View File

@ -0,0 +1,180 @@
package pathtransformer
import (
"context"
"github.com/alcionai/clues"
"github.com/alcionai/corso/src/internal/version"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path"
)
func locationRef(
ent *details.Entry,
repoRef path.Path,
backupVersion int,
) (*path.Builder, error) {
loc := ent.LocationRef
// At this backup version all data types should populate LocationRef.
if len(loc) > 0 || backupVersion >= version.OneDrive7LocationRef {
return path.Builder{}.SplitUnescapeAppend(loc)
}
// We could get an empty LocationRef either because it wasn't populated or it
// was in the root of the data type.
elems := repoRef.Folders()
if ent.OneDrive != nil || ent.SharePoint != nil {
dp, err := path.ToDrivePath(repoRef)
if err != nil {
return nil, clues.Wrap(err, "fallback for LocationRef")
}
elems = append([]string{dp.Root}, dp.Folders...)
}
return path.Builder{}.Append(elems...), nil
}
func basicLocationPath(repoRef path.Path, locRef *path.Builder) (path.Path, error) {
if len(locRef.Elements()) == 0 {
res, err := path.ServicePrefix(
repoRef.Tenant(),
repoRef.ResourceOwner(),
repoRef.Service(),
repoRef.Category())
if err != nil {
return nil, clues.Wrap(err, "getting prefix for empty location")
}
return res, nil
}
return locRef.ToDataLayerPath(
repoRef.Tenant(),
repoRef.ResourceOwner(),
repoRef.Service(),
repoRef.Category(),
false)
}
func drivePathMerge(
ent *details.Entry,
repoRef path.Path,
locRef *path.Builder,
) (path.Path, error) {
// Try getting the drive ID from the item. Not all details versions had it
// though.
var driveID string
if ent.SharePoint != nil {
driveID = ent.SharePoint.DriveID
} else if ent.OneDrive != nil {
driveID = ent.OneDrive.DriveID
}
// Fallback to trying to get from RepoRef.
if len(driveID) == 0 {
odp, err := path.ToDrivePath(repoRef)
if err != nil {
return nil, clues.Wrap(err, "fallback getting DriveID")
}
driveID = odp.DriveID
}
return basicLocationPath(
repoRef,
path.BuildDriveLocation(driveID, locRef.Elements()...))
}
func makeRestorePathsForEntry(
ctx context.Context,
backupVersion int,
ent *details.Entry,
) (path.RestorePaths, error) {
res := path.RestorePaths{}
repoRef, err := path.FromDataLayerPath(ent.RepoRef, true)
if err != nil {
err = clues.Wrap(err, "parsing RepoRef").
WithClues(ctx).
With("repo_ref", clues.Hide(ent.RepoRef), "location_ref", clues.Hide(ent.LocationRef))
return res, err
}
res.StoragePath = repoRef
ctx = clues.Add(ctx, "repo_ref", repoRef)
// Get the LocationRef so we can munge it onto our path.
locRef, err := locationRef(ent, repoRef, backupVersion)
if err != nil {
err = clues.Wrap(err, "parsing LocationRef after reduction").
WithClues(ctx).
With("location_ref", clues.Hide(ent.LocationRef))
return res, err
}
ctx = clues.Add(ctx, "location_ref", locRef)
// Now figure out what type of ent it is and munge the path accordingly.
// Eventually we're going to need munging for:
// * Exchange Calendars (different folder handling)
// * Exchange Email/Contacts
// * OneDrive/SharePoint (needs drive information)
if ent.Exchange != nil {
// TODO(ashmrtn): Eventually make Events have it's own function to handle
// setting the restore destination properly.
res.RestorePath, err = basicLocationPath(repoRef, locRef)
} else if ent.OneDrive != nil ||
(ent.SharePoint != nil && ent.SharePoint.ItemType == details.SharePointLibrary) ||
(ent.SharePoint != nil && ent.SharePoint.ItemType == details.OneDriveItem) {
res.RestorePath, err = drivePathMerge(ent, repoRef, locRef)
} else {
return res, clues.New("unknown entry type").WithClues(ctx)
}
if err != nil {
return res, clues.Wrap(err, "generating RestorePath").WithClues(ctx)
}
return res, nil
}
// GetPaths takes a set of filtered details entries and returns a set of
// RestorePaths for the entries.
func GetPaths(
ctx context.Context,
backupVersion int,
items []*details.Entry,
errs *fault.Bus,
) ([]path.RestorePaths, error) {
var (
paths = make([]path.RestorePaths, len(items))
el = errs.Local()
)
for i, ent := range items {
if el.Failure() != nil {
break
}
restorePaths, err := makeRestorePathsForEntry(ctx, backupVersion, ent)
if err != nil {
el.AddRecoverable(clues.Wrap(err, "getting restore paths"))
continue
}
paths[i] = restorePaths
}
logger.Ctx(ctx).Infof("found %d details entries to restore", len(paths))
return paths, el.Failure()
}

View File

@ -0,0 +1,340 @@
package pathtransformer_test
import (
"testing"
"github.com/alcionai/clues"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/operations/pathtransformer"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/internal/version"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/backup/details/testdata"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
)
type RestorePathTransformerUnitSuite struct {
tester.Suite
}
func TestRestorePathTransformerUnitSuite(t *testing.T) {
suite.Run(t, &RestorePathTransformerUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *RestorePathTransformerUnitSuite) TestGetPaths() {
type expectPaths struct {
storage string
restore string
isRestorePrefix bool
}
toRestore := func(
repoRef path.Path,
unescapedFolders ...string,
) string {
return path.Builder{}.
Append(
repoRef.Tenant(),
repoRef.Service().String(),
repoRef.ResourceOwner(),
repoRef.Category().String()).
Append(unescapedFolders...).
String()
}
var (
driveID = "some-drive-id"
extraItemName = "some-item"
SharePointRootItemPath = testdata.SharePointRootPath.MustAppend(extraItemName, true)
)
table := []struct {
name string
backupVersion int
input []*details.Entry
expectErr assert.ErrorAssertionFunc
expected []expectPaths
}{
{
name: "SharePoint List Errors",
// No version bump for the change so we always have to check for this.
backupVersion: version.All8MigrateUserPNToID,
input: []*details.Entry{
{
RepoRef: SharePointRootItemPath.RR.String(),
LocationRef: SharePointRootItemPath.Loc.String(),
ItemInfo: details.ItemInfo{
SharePoint: &details.SharePointInfo{
ItemType: details.SharePointList,
},
},
},
},
expectErr: assert.Error,
},
{
name: "SharePoint Page Errors",
// No version bump for the change so we always have to check for this.
backupVersion: version.All8MigrateUserPNToID,
input: []*details.Entry{
{
RepoRef: SharePointRootItemPath.RR.String(),
LocationRef: SharePointRootItemPath.Loc.String(),
ItemInfo: details.ItemInfo{
SharePoint: &details.SharePointInfo{
ItemType: details.SharePointPage,
},
},
},
},
expectErr: assert.Error,
},
{
name: "SharePoint old format, item in root",
// No version bump for the change so we always have to check for this.
backupVersion: version.All8MigrateUserPNToID,
input: []*details.Entry{
{
RepoRef: SharePointRootItemPath.RR.String(),
LocationRef: SharePointRootItemPath.Loc.String(),
ItemInfo: details.ItemInfo{
SharePoint: &details.SharePointInfo{
ItemType: details.OneDriveItem,
DriveID: driveID,
},
},
},
},
expectErr: assert.NoError,
expected: []expectPaths{
{
storage: SharePointRootItemPath.RR.String(),
restore: toRestore(
SharePointRootItemPath.RR,
append(
[]string{"drives", driveID},
SharePointRootItemPath.Loc.Elements()...)...),
},
},
},
{
name: "SharePoint, no LocationRef, no DriveID, item in root",
backupVersion: version.OneDrive6NameInMeta,
input: []*details.Entry{
{
RepoRef: SharePointRootItemPath.RR.String(),
ItemInfo: details.ItemInfo{
SharePoint: &details.SharePointInfo{
ItemType: details.SharePointLibrary,
},
},
},
},
expectErr: assert.NoError,
expected: []expectPaths{
{
storage: SharePointRootItemPath.RR.String(),
restore: toRestore(
SharePointRootItemPath.RR,
append(
[]string{"drives"},
// testdata path has '.d' on the drives folder we need to remove.
SharePointRootItemPath.RR.Folders()[1:]...)...),
},
},
},
{
name: "OneDrive, nested item",
backupVersion: version.All8MigrateUserPNToID,
input: []*details.Entry{
{
RepoRef: testdata.OneDriveItemPath2.RR.String(),
LocationRef: testdata.OneDriveItemPath2.Loc.String(),
ItemInfo: details.ItemInfo{
OneDrive: &details.OneDriveInfo{
ItemType: details.OneDriveItem,
DriveID: driveID,
},
},
},
},
expectErr: assert.NoError,
expected: []expectPaths{
{
storage: testdata.OneDriveItemPath2.RR.String(),
restore: toRestore(
testdata.OneDriveItemPath2.RR,
append(
[]string{"drives", driveID},
testdata.OneDriveItemPath2.Loc.Elements()...)...),
},
},
},
{
name: "Exchange Email, extra / in path",
backupVersion: version.All8MigrateUserPNToID,
input: []*details.Entry{
{
RepoRef: testdata.ExchangeEmailItemPath3.RR.String(),
LocationRef: testdata.ExchangeEmailItemPath3.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
},
},
},
},
expectErr: assert.NoError,
expected: []expectPaths{
{
storage: testdata.ExchangeEmailItemPath3.RR.String(),
restore: toRestore(
testdata.ExchangeEmailItemPath3.RR,
testdata.ExchangeEmailItemPath3.Loc.Elements()...),
},
},
},
{
name: "Exchange Email, no LocationRef, extra / in path",
backupVersion: version.OneDrive7LocationRef,
input: []*details.Entry{
{
RepoRef: testdata.ExchangeEmailItemPath3.RR.String(),
LocationRef: testdata.ExchangeEmailItemPath3.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
},
},
},
},
expectErr: assert.NoError,
expected: []expectPaths{
{
storage: testdata.ExchangeEmailItemPath3.RR.String(),
restore: toRestore(
testdata.ExchangeEmailItemPath3.RR,
testdata.ExchangeEmailItemPath3.Loc.Elements()...),
},
},
},
{
name: "Exchange Contact",
backupVersion: version.All8MigrateUserPNToID,
input: []*details.Entry{
{
RepoRef: testdata.ExchangeContactsItemPath1.RR.String(),
LocationRef: testdata.ExchangeContactsItemPath1.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeContact,
},
},
},
},
expectErr: assert.NoError,
expected: []expectPaths{
{
storage: testdata.ExchangeContactsItemPath1.RR.String(),
restore: toRestore(
testdata.ExchangeContactsItemPath1.RR,
testdata.ExchangeContactsItemPath1.Loc.Elements()...),
},
},
},
{
name: "Exchange Contact, root dir",
backupVersion: version.All8MigrateUserPNToID,
input: []*details.Entry{
{
RepoRef: testdata.ExchangeContactsItemPath1.RR.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeContact,
},
},
},
},
expectErr: assert.NoError,
expected: []expectPaths{
{
storage: testdata.ExchangeContactsItemPath1.RR.String(),
restore: toRestore(testdata.ExchangeContactsItemPath1.RR, "tmp"),
isRestorePrefix: true,
},
},
},
{
name: "Exchange Event",
backupVersion: version.All8MigrateUserPNToID,
input: []*details.Entry{
{
RepoRef: testdata.ExchangeEmailItemPath3.RR.String(),
LocationRef: testdata.ExchangeEmailItemPath3.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
},
},
},
},
expectErr: assert.NoError,
expected: []expectPaths{
{
storage: testdata.ExchangeEmailItemPath3.RR.String(),
restore: toRestore(
testdata.ExchangeEmailItemPath3.RR,
testdata.ExchangeEmailItemPath3.Loc.Elements()...),
},
},
},
}
for _, test := range table {
suite.Run(test.name, func() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
paths, err := pathtransformer.GetPaths(
ctx,
test.backupVersion,
test.input,
fault.New(true))
test.expectErr(t, err, clues.ToCore(err))
if err != nil {
return
}
expected := make([]path.RestorePaths, 0, len(test.expected))
for _, e := range test.expected {
tmp := path.RestorePaths{}
p, err := path.FromDataLayerPath(e.storage, true)
require.NoError(t, err, "parsing expected storage path", clues.ToCore(err))
tmp.StoragePath = p
p, err = path.FromDataLayerPath(e.restore, false)
require.NoError(t, err, "parsing expected restore path", clues.ToCore(err))
if e.isRestorePrefix {
p, err = p.Dir()
require.NoError(t, err, "getting service prefix", clues.ToCore(err))
}
tmp.RestorePath = p
expected = append(expected, tmp)
}
assert.ElementsMatch(t, expected, paths)
})
}
}

View File

@ -18,6 +18,7 @@ import (
"github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/internal/observe"
"github.com/alcionai/corso/src/internal/operations/inject"
"github.com/alcionai/corso/src/internal/operations/pathtransformer"
"github.com/alcionai/corso/src/internal/stats"
"github.com/alcionai/corso/src/internal/streamstore"
"github.com/alcionai/corso/src/pkg/account"
@ -355,41 +356,9 @@ func formatDetailsForRestoration(
return nil, err
}
var (
fdsPaths = fds.Paths()
paths = make([]path.RestorePaths, len(fdsPaths))
shortRefs = make([]string, len(fdsPaths))
el = errs.Local()
)
for i := range fdsPaths {
if el.Failure() != nil {
break
}
p, err := path.FromDataLayerPath(fdsPaths[i], true)
if err != nil {
el.AddRecoverable(clues.
Wrap(err, "parsing details path after reduction").
WithMap(clues.In(ctx)).
With("path", fdsPaths[i]))
continue
}
dir, err := p.Dir()
if err != nil {
el.AddRecoverable(clues.
Wrap(err, "getting restore directory after reduction").
WithClues(ctx).
With("path", fdsPaths[i]))
continue
}
paths[i].StoragePath = p
paths[i].RestorePath = dir
shortRefs[i] = p.ShortRef()
paths, err := pathtransformer.GetPaths(ctx, backupVersion, fds.Items(), errs)
if err != nil {
return nil, clues.Wrap(err, "getting restore paths")
}
if sel.Service == selectors.ServiceOneDrive {
@ -399,7 +368,5 @@ func formatDetailsForRestoration(
}
}
logger.Ctx(ctx).With("short_refs", shortRefs).Infof("found %d details entries to restore", len(shortRefs))
return paths, el.Failure()
return paths, nil
}

View File

@ -54,10 +54,10 @@ func locFromRepo(rr path.Path, isItem bool) *path.Builder {
type repoRefAndLocRef struct {
RR path.Path
loc *path.Builder
Loc *path.Builder
}
func (p repoRefAndLocRef) mustAppend(newElement string, isItem bool) repoRefAndLocRef {
func (p repoRefAndLocRef) MustAppend(newElement string, isItem bool) repoRefAndLocRef {
e := newElement + folderSuffix
if isItem {
@ -68,7 +68,7 @@ func (p repoRefAndLocRef) mustAppend(newElement string, isItem bool) repoRefAndL
RR: mustAppendPath(p.RR, e, isItem),
}
res.loc = locFromRepo(res.RR, isItem)
res.Loc = locFromRepo(res.RR, isItem)
return res
}
@ -85,7 +85,7 @@ func (p repoRefAndLocRef) FolderLocation() string {
lastElem = f[len(f)-2]
}
return p.loc.Append(strings.TrimSuffix(lastElem, folderSuffix)).String()
return p.Loc.Append(strings.TrimSuffix(lastElem, folderSuffix)).String()
}
func mustPathRep(ref string, isItem bool) repoRefAndLocRef {
@ -115,7 +115,7 @@ func mustPathRep(ref string, isItem bool) repoRefAndLocRef {
}
res.RR = rr
res.loc = locFromRepo(rr, isItem)
res.Loc = locFromRepo(rr, isItem)
return res
}
@ -138,12 +138,12 @@ var (
Time4 = time.Date(2023, 10, 21, 10, 0, 0, 0, time.UTC)
ExchangeEmailInboxPath = mustPathRep("tenant-id/exchange/user-id/email/Inbox", false)
ExchangeEmailBasePath = ExchangeEmailInboxPath.mustAppend("subfolder", false)
ExchangeEmailBasePath2 = ExchangeEmailInboxPath.mustAppend("othersubfolder/", false)
ExchangeEmailBasePath3 = ExchangeEmailBasePath2.mustAppend("subsubfolder", false)
ExchangeEmailItemPath1 = ExchangeEmailBasePath.mustAppend(ItemName1, true)
ExchangeEmailItemPath2 = ExchangeEmailBasePath2.mustAppend(ItemName2, true)
ExchangeEmailItemPath3 = ExchangeEmailBasePath3.mustAppend(ItemName3, true)
ExchangeEmailBasePath = ExchangeEmailInboxPath.MustAppend("subfolder", false)
ExchangeEmailBasePath2 = ExchangeEmailInboxPath.MustAppend("othersubfolder/", false)
ExchangeEmailBasePath3 = ExchangeEmailBasePath2.MustAppend("subsubfolder", false)
ExchangeEmailItemPath1 = ExchangeEmailBasePath.MustAppend(ItemName1, true)
ExchangeEmailItemPath2 = ExchangeEmailBasePath2.MustAppend(ItemName2, true)
ExchangeEmailItemPath3 = ExchangeEmailBasePath3.MustAppend(ItemName3, true)
ExchangeEmailItems = []details.Entry{
{
@ -151,7 +151,7 @@ var (
ShortRef: ExchangeEmailItemPath1.RR.ShortRef(),
ParentRef: ExchangeEmailItemPath1.RR.ToBuilder().Dir().ShortRef(),
ItemRef: ExchangeEmailItemPath1.ItemLocation(),
LocationRef: ExchangeEmailItemPath1.loc.String(),
LocationRef: ExchangeEmailItemPath1.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
@ -166,7 +166,7 @@ var (
ShortRef: ExchangeEmailItemPath2.RR.ShortRef(),
ParentRef: ExchangeEmailItemPath2.RR.ToBuilder().Dir().ShortRef(),
ItemRef: ExchangeEmailItemPath2.ItemLocation(),
LocationRef: ExchangeEmailItemPath2.loc.String(),
LocationRef: ExchangeEmailItemPath2.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
@ -181,7 +181,7 @@ var (
ShortRef: ExchangeEmailItemPath3.RR.ShortRef(),
ParentRef: ExchangeEmailItemPath3.RR.ToBuilder().Dir().ShortRef(),
ItemRef: ExchangeEmailItemPath3.ItemLocation(),
LocationRef: ExchangeEmailItemPath3.loc.String(),
LocationRef: ExchangeEmailItemPath3.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
@ -194,10 +194,10 @@ var (
}
ExchangeContactsRootPath = mustPathRep("tenant-id/exchange/user-id/contacts/contacts", false)
ExchangeContactsBasePath = ExchangeContactsRootPath.mustAppend("contacts", false)
ExchangeContactsBasePath2 = ExchangeContactsRootPath.mustAppend("morecontacts", false)
ExchangeContactsItemPath1 = ExchangeContactsBasePath.mustAppend(ItemName1, true)
ExchangeContactsItemPath2 = ExchangeContactsBasePath2.mustAppend(ItemName2, true)
ExchangeContactsBasePath = ExchangeContactsRootPath.MustAppend("contacts", false)
ExchangeContactsBasePath2 = ExchangeContactsRootPath.MustAppend("morecontacts", false)
ExchangeContactsItemPath1 = ExchangeContactsBasePath.MustAppend(ItemName1, true)
ExchangeContactsItemPath2 = ExchangeContactsBasePath2.MustAppend(ItemName2, true)
ExchangeContactsItems = []details.Entry{
{
@ -205,7 +205,7 @@ var (
ShortRef: ExchangeContactsItemPath1.RR.ShortRef(),
ParentRef: ExchangeContactsItemPath1.RR.ToBuilder().Dir().ShortRef(),
ItemRef: ExchangeContactsItemPath1.ItemLocation(),
LocationRef: ExchangeContactsItemPath1.loc.String(),
LocationRef: ExchangeContactsItemPath1.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeContact,
@ -218,7 +218,7 @@ var (
ShortRef: ExchangeContactsItemPath2.RR.ShortRef(),
ParentRef: ExchangeContactsItemPath2.RR.ToBuilder().Dir().ShortRef(),
ItemRef: ExchangeContactsItemPath2.ItemLocation(),
LocationRef: ExchangeContactsItemPath2.loc.String(),
LocationRef: ExchangeContactsItemPath2.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeContact,
@ -228,11 +228,10 @@ var (
},
}
ExchangeEventsRootPath = mustPathRep("tenant-id/exchange/user-id/events/holidays", false)
ExchangeEventsBasePath = ExchangeEventsRootPath.mustAppend("holidays", false)
ExchangeEventsBasePath2 = ExchangeEventsRootPath.mustAppend("moreholidays", false)
ExchangeEventsItemPath1 = ExchangeEventsBasePath.mustAppend(ItemName1, true)
ExchangeEventsItemPath2 = ExchangeEventsBasePath2.mustAppend(ItemName2, true)
ExchangeEventsBasePath = mustPathRep("tenant-id/exchange/user-id/events/holidays", false)
ExchangeEventsBasePath2 = mustPathRep("tenant-id/exchange/user-id/events/moreholidays", false)
ExchangeEventsItemPath1 = ExchangeEventsBasePath.MustAppend(ItemName1, true)
ExchangeEventsItemPath2 = ExchangeEventsBasePath2.MustAppend(ItemName2, true)
ExchangeEventsItems = []details.Entry{
{
@ -240,7 +239,7 @@ var (
ShortRef: ExchangeEventsItemPath1.RR.ShortRef(),
ParentRef: ExchangeEventsItemPath1.RR.ToBuilder().Dir().ShortRef(),
ItemRef: ExchangeEventsItemPath1.ItemLocation(),
LocationRef: ExchangeEventsItemPath1.loc.String(),
LocationRef: ExchangeEventsItemPath1.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeEvent,
@ -256,7 +255,7 @@ var (
ShortRef: ExchangeEventsItemPath2.RR.ShortRef(),
ParentRef: ExchangeEventsItemPath2.RR.ToBuilder().Dir().ShortRef(),
ItemRef: ExchangeEventsItemPath2.ItemLocation(),
LocationRef: ExchangeEventsItemPath2.loc.String(),
LocationRef: ExchangeEventsItemPath2.Loc.String(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeEvent,
@ -270,17 +269,17 @@ var (
}
OneDriveRootPath = mustPathRep("tenant-id/onedrive/user-id/files/drives/foo/root:", false)
OneDriveFolderPath = OneDriveRootPath.mustAppend("folder", false)
OneDriveBasePath1 = OneDriveFolderPath.mustAppend("a", false)
OneDriveBasePath2 = OneDriveFolderPath.mustAppend("b", false)
OneDriveFolderPath = OneDriveRootPath.MustAppend("folder", false)
OneDriveBasePath1 = OneDriveFolderPath.MustAppend("a", false)
OneDriveBasePath2 = OneDriveFolderPath.MustAppend("b", false)
OneDriveItemPath1 = OneDriveFolderPath.mustAppend(ItemName1, true)
OneDriveItemPath2 = OneDriveBasePath1.mustAppend(ItemName2, true)
OneDriveItemPath3 = OneDriveBasePath2.mustAppend(ItemName3, true)
OneDriveItemPath1 = OneDriveFolderPath.MustAppend(ItemName1, true)
OneDriveItemPath2 = OneDriveBasePath1.MustAppend(ItemName2, true)
OneDriveItemPath3 = OneDriveBasePath2.MustAppend(ItemName3, true)
OneDriveFolderFolder = OneDriveFolderPath.loc.PopFront().String()
OneDriveParentFolder1 = OneDriveBasePath1.loc.PopFront().String()
OneDriveParentFolder2 = OneDriveBasePath2.loc.PopFront().String()
OneDriveFolderFolder = OneDriveFolderPath.Loc.PopFront().String()
OneDriveParentFolder1 = OneDriveBasePath1.Loc.PopFront().String()
OneDriveParentFolder2 = OneDriveBasePath2.Loc.PopFront().String()
OneDriveItems = []details.Entry{
{
@ -288,7 +287,7 @@ var (
ShortRef: OneDriveItemPath1.RR.ShortRef(),
ParentRef: OneDriveItemPath1.RR.ToBuilder().Dir().ShortRef(),
ItemRef: OneDriveItemPath1.ItemLocation(),
LocationRef: OneDriveItemPath1.loc.String(),
LocationRef: OneDriveItemPath1.Loc.String(),
ItemInfo: details.ItemInfo{
OneDrive: &details.OneDriveInfo{
ItemType: details.OneDriveItem,
@ -306,7 +305,7 @@ var (
ShortRef: OneDriveItemPath2.RR.ShortRef(),
ParentRef: OneDriveItemPath2.RR.ToBuilder().Dir().ShortRef(),
ItemRef: OneDriveItemPath2.ItemLocation(),
LocationRef: OneDriveItemPath2.loc.String(),
LocationRef: OneDriveItemPath2.Loc.String(),
ItemInfo: details.ItemInfo{
OneDrive: &details.OneDriveInfo{
ItemType: details.OneDriveItem,
@ -324,7 +323,7 @@ var (
ShortRef: OneDriveItemPath3.RR.ShortRef(),
ParentRef: OneDriveItemPath3.RR.ToBuilder().Dir().ShortRef(),
ItemRef: OneDriveItemPath3.ItemLocation(),
LocationRef: OneDriveItemPath3.loc.String(),
LocationRef: OneDriveItemPath3.Loc.String(),
ItemInfo: details.ItemInfo{
OneDrive: &details.OneDriveInfo{
ItemType: details.OneDriveItem,
@ -340,17 +339,17 @@ var (
}
SharePointRootPath = mustPathRep("tenant-id/sharepoint/site-id/libraries/drives/foo/root:", false)
SharePointLibraryPath = SharePointRootPath.mustAppend("library", false)
SharePointBasePath1 = SharePointLibraryPath.mustAppend("a", false)
SharePointBasePath2 = SharePointLibraryPath.mustAppend("b", false)
SharePointLibraryPath = SharePointRootPath.MustAppend("library", false)
SharePointBasePath1 = SharePointLibraryPath.MustAppend("a", false)
SharePointBasePath2 = SharePointLibraryPath.MustAppend("b", false)
SharePointLibraryItemPath1 = SharePointLibraryPath.mustAppend(ItemName1, true)
SharePointLibraryItemPath2 = SharePointBasePath1.mustAppend(ItemName2, true)
SharePointLibraryItemPath3 = SharePointBasePath2.mustAppend(ItemName3, true)
SharePointLibraryItemPath1 = SharePointLibraryPath.MustAppend(ItemName1, true)
SharePointLibraryItemPath2 = SharePointBasePath1.MustAppend(ItemName2, true)
SharePointLibraryItemPath3 = SharePointBasePath2.MustAppend(ItemName3, true)
SharePointLibraryFolder = SharePointLibraryPath.loc.PopFront().String()
SharePointParentLibrary1 = SharePointBasePath1.loc.PopFront().String()
SharePointParentLibrary2 = SharePointBasePath2.loc.PopFront().String()
SharePointLibraryFolder = SharePointLibraryPath.Loc.PopFront().String()
SharePointParentLibrary1 = SharePointBasePath1.Loc.PopFront().String()
SharePointParentLibrary2 = SharePointBasePath2.Loc.PopFront().String()
SharePointLibraryItems = []details.Entry{
{
@ -358,7 +357,7 @@ var (
ShortRef: SharePointLibraryItemPath1.RR.ShortRef(),
ParentRef: SharePointLibraryItemPath1.RR.ToBuilder().Dir().ShortRef(),
ItemRef: SharePointLibraryItemPath1.ItemLocation(),
LocationRef: SharePointLibraryItemPath1.loc.String(),
LocationRef: SharePointLibraryItemPath1.Loc.String(),
ItemInfo: details.ItemInfo{
SharePoint: &details.SharePointInfo{
ItemType: details.SharePointLibrary,
@ -376,7 +375,7 @@ var (
ShortRef: SharePointLibraryItemPath2.RR.ShortRef(),
ParentRef: SharePointLibraryItemPath2.RR.ToBuilder().Dir().ShortRef(),
ItemRef: SharePointLibraryItemPath2.ItemLocation(),
LocationRef: SharePointLibraryItemPath2.loc.String(),
LocationRef: SharePointLibraryItemPath2.Loc.String(),
ItemInfo: details.ItemInfo{
SharePoint: &details.SharePointInfo{
ItemType: details.SharePointLibrary,
@ -394,7 +393,7 @@ var (
ShortRef: SharePointLibraryItemPath3.RR.ShortRef(),
ParentRef: SharePointLibraryItemPath3.RR.ToBuilder().Dir().ShortRef(),
ItemRef: SharePointLibraryItemPath3.ItemLocation(),
LocationRef: SharePointLibraryItemPath3.loc.String(),
LocationRef: SharePointLibraryItemPath3.Loc.String(),
ItemInfo: details.ItemInfo{
SharePoint: &details.SharePointInfo{
ItemType: details.SharePointLibrary,

View File

@ -38,3 +38,13 @@ func GetDriveFolderPath(p Path) (string, error) {
return Builder{}.Append(drivePath.Folders...).String(), nil
}
// BuildDriveLocation takes a driveID and a set of unescaped element names,
// including the root folder, and returns a *path.Builder containing the
// canonical path representation for the drive path.
func BuildDriveLocation(
driveID string,
unescapedElements ...string,
) *Builder {
return Builder{}.Append("drives", driveID).Append(unescapedElements...)
}

View File

@ -1,6 +1,7 @@
package path_test
import (
"strings"
"testing"
"github.com/alcionai/clues"
@ -63,3 +64,49 @@ func (suite *OneDrivePathSuite) Test_ToOneDrivePath() {
})
}
}
func (suite *OneDrivePathSuite) TestFormatDriveFolders() {
const (
driveID = "some-drive-id"
drivePrefix = "drives/" + driveID
)
table := []struct {
name string
input []string
expected string
}{
{
name: "normal",
input: []string{
"root:",
"foo",
"bar",
},
expected: strings.Join(
append([]string{drivePrefix}, "root:", "foo", "bar"),
"/"),
},
{
name: "has character that would be escaped",
input: []string{
"root:",
"foo/",
"bar",
},
// Element "foo/" should end up escaped in the string output.
expected: strings.Join(
append([]string{drivePrefix}, "root:", `foo\/`, "bar"),
"/"),
},
}
for _, test := range table {
suite.Run(test.name, func() {
assert.Equal(
suite.T(),
test.expected,
path.BuildDriveLocation(driveID, test.input...).String())
})
}
}

View File

@ -249,18 +249,6 @@ func (suite *SelectorReduceSuite) TestReduce() {
},
expected: []details.Entry{testdata.ExchangeEventsItems[0]},
},
{
name: "ExchangeEventsByFolderRoot",
selFunc: func() selectors.Reducer {
sel := selectors.NewExchangeRestore(selectors.Any())
sel.Include(sel.EventCalendars(
[]string{testdata.ExchangeEventsRootPath.FolderLocation()},
))
return sel
},
expected: testdata.ExchangeEventsItems,
},
}
for _, test := range table {