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:
parent
c5b388a721
commit
2e4fc71310
@ -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()
|
||||
}
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
101
src/pkg/backup/details/testdata/testdata.go
vendored
101
src/pkg/backup/details/testdata/testdata.go
vendored
@ -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,
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user