Trim trailing '/' from CLI input for restore/details (#1175)

## Description

Use paths package to trim unescaped trailing '/' characters from input for Exchange mail and OneDrive folder names. Add tests for Exchange showing that the trimming also works properly if the folder name ends with '/'.

## Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Test
- [ ] 💻 CI/Deployment
- [ ] 🐹 Trivial/Minor

## Issue(s)

* closes #1147 

## Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
ashmrtn 2022-10-17 10:54:36 -07:00 committed by GitHub
parent 7d72cd12a4
commit 72a3c7ab3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 79 additions and 9 deletions

View File

@ -142,6 +142,8 @@ func IncludeExchangeRestoreDataSelectors(
return return
} }
opts.EmailFolder = trimFolderSlash(opts.EmailFolder)
// or add selectors for each type of data // or add selectors for each type of data
AddExchangeInclude(sel, opts.Users, opts.ContactFolder, opts.Contact, sel.Contacts) AddExchangeInclude(sel, opts.Users, opts.ContactFolder, opts.Contact, sel.Contacts)
AddExchangeInclude(sel, opts.Users, opts.EmailFolder, opts.Email, sel.Mails) AddExchangeInclude(sel, opts.Users, opts.EmailFolder, opts.Email, sel.Mails)

View File

@ -7,6 +7,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/pkg/path"
) )
type PopulatedFlags map[string]struct{} type PopulatedFlags map[string]struct{}
@ -50,3 +51,16 @@ func IsValidBool(in string) bool {
_, err := strconv.ParseBool(in) _, err := strconv.ParseBool(in)
return err == nil return err == nil
} }
// trimFolderSlash takes a set of folder paths and returns a set of folder paths
// with any unescaped trailing `/` characters removed.
func trimFolderSlash(folders []string) []string {
res := make([]string, 0, len(folders))
for _, p := range folders {
// Use path package because it has logic to handle escaping already.
res = append(res, path.TrimTrailingSlash(p))
}
return res
}

View File

@ -87,6 +87,8 @@ func IncludeOneDriveRestoreDataSelectors(
return return
} }
opts.Paths = trimFolderSlash(opts.Paths)
if lp == 0 { if lp == 0 {
opts.Paths = selectors.Any() opts.Paths = selectors.Any()
} }

View File

@ -139,8 +139,38 @@ var (
}, },
}, },
{ {
Name: "EmailsBySubject", Name: "EmailsFolderPrefixMatchTrailingSlash",
Expected: testdata.ExchangeEmailItems, Expected: testdata.ExchangeEmailItems,
Opts: utils.ExchangeOpts{
EmailFolder: []string{testdata.ExchangeEmailInboxPath.Folder() + "/"},
},
},
{
Name: "EmailsFolderWithSlashPrefixMatch",
Expected: []details.DetailsEntry{
testdata.ExchangeEmailItems[1],
testdata.ExchangeEmailItems[2],
},
Opts: utils.ExchangeOpts{
EmailFolder: []string{testdata.ExchangeEmailBasePath2.Folder()},
},
},
{
Name: "EmailsFolderWithSlashPrefixMatchTrailingSlash",
Expected: []details.DetailsEntry{
testdata.ExchangeEmailItems[1],
testdata.ExchangeEmailItems[2],
},
Opts: utils.ExchangeOpts{
EmailFolder: []string{testdata.ExchangeEmailBasePath2.Folder() + "/"},
},
},
{
Name: "EmailsBySubject",
Expected: []details.DetailsEntry{
testdata.ExchangeEmailItems[0],
testdata.ExchangeEmailItems[1],
},
Opts: utils.ExchangeOpts{ Opts: utils.ExchangeOpts{
EmailSender: "a-person", EmailSender: "a-person",
}, },
@ -181,7 +211,10 @@ var (
}, },
{ {
Name: "MultipleMailShortRef", Name: "MultipleMailShortRef",
Expected: testdata.ExchangeEmailItems, Expected: []details.DetailsEntry{
testdata.ExchangeEmailItems[0],
testdata.ExchangeEmailItems[1],
},
Opts: utils.ExchangeOpts{ Opts: utils.ExchangeOpts{
Email: []string{ Email: []string{
testdata.ExchangeEmailItemPath1.ShortRef(), testdata.ExchangeEmailItemPath1.ShortRef(),

View File

@ -145,7 +145,7 @@ func (pb *Builder) appendElements(escaped bool, elements []string) error {
tmp := e tmp := e
if escaped { if escaped {
tmp = trimTrailingSlash(tmp) tmp = TrimTrailingSlash(tmp)
// If tmp was just the path separator then it will be empty now. // If tmp was just the path separator then it will be empty now.
if len(tmp) == 0 { if len(tmp) == 0 {
continue continue
@ -310,7 +310,7 @@ func (pb Builder) ToDataLayerOneDrivePath(
// resource-specific type. If p does not match any resource-specific paths or // resource-specific type. If p does not match any resource-specific paths or
// is malformed returns an error. // is malformed returns an error.
func FromDataLayerPath(p string, isItem bool) (Path, error) { func FromDataLayerPath(p string, isItem bool) (Path, error) {
p = trimTrailingSlash(p) p = TrimTrailingSlash(p)
// If p was just the path separator then it will be empty now. // If p was just the path separator then it will be empty now.
if len(p) == 0 { if len(p) == 0 {
return nil, errors.Errorf("logically empty path given: %s", p) return nil, errors.Errorf("logically empty path given: %s", p)
@ -437,11 +437,11 @@ func validateEscapedElement(element string) error {
return nil return nil
} }
// trimTrailingSlash takes an escaped path element and returns an escaped path // TrimTrailingSlash takes an escaped path element and returns an escaped path
// element with the trailing path separator character(s) removed if they were not // element with the trailing path separator character(s) removed if they were not
// escaped. If there were no trailing path separator character(s) or the separator(s) // escaped. If there were no trailing path separator character(s) or the separator(s)
// were escaped the input is returned unchanged. // were escaped the input is returned unchanged.
func trimTrailingSlash(element string) string { func TrimTrailingSlash(element string) string {
for len(element) > 0 && element[len(element)-1] == pathSeparator { for len(element) > 0 && element[len(element)-1] == pathSeparator {
lastIdx := len(element) - 1 lastIdx := len(element) - 1
numSlashes := 0 numSlashes := 0

View File

@ -92,7 +92,10 @@ func (suite *SelectorReduceSuite) TestReduce() {
return sel return sel
}, },
expected: testdata.ExchangeEmailItems, expected: []details.DetailsEntry{
testdata.ExchangeEmailItems[0],
testdata.ExchangeEmailItems[1],
},
}, },
{ {
name: "ExchangeMailReceivedTime", name: "ExchangeMailReceivedTime",

View File

@ -36,6 +36,7 @@ func mustAppendPath(p path.Path, newElement string, isItem bool) path.Path {
const ( const (
ItemName1 = "item1" ItemName1 = "item1"
ItemName2 = "item2" ItemName2 = "item2"
ItemName3 = "item3"
) )
var ( var (
@ -44,9 +45,11 @@ var (
ExchangeEmailInboxPath = mustParsePath("tenant-id/exchange/user-id/email/Inbox", false) ExchangeEmailInboxPath = mustParsePath("tenant-id/exchange/user-id/email/Inbox", false)
ExchangeEmailBasePath = mustAppendPath(ExchangeEmailInboxPath, "subfolder", false) ExchangeEmailBasePath = mustAppendPath(ExchangeEmailInboxPath, "subfolder", false)
ExchangeEmailBasePath2 = mustAppendPath(ExchangeEmailInboxPath, "othersubfolder", false) ExchangeEmailBasePath2 = mustAppendPath(ExchangeEmailInboxPath, "othersubfolder/", false)
ExchangeEmailBasePath3 = mustAppendPath(ExchangeEmailBasePath2, "subsubfolder", false)
ExchangeEmailItemPath1 = mustAppendPath(ExchangeEmailBasePath, ItemName1, true) ExchangeEmailItemPath1 = mustAppendPath(ExchangeEmailBasePath, ItemName1, true)
ExchangeEmailItemPath2 = mustAppendPath(ExchangeEmailBasePath2, ItemName2, true) ExchangeEmailItemPath2 = mustAppendPath(ExchangeEmailBasePath2, ItemName2, true)
ExchangeEmailItemPath3 = mustAppendPath(ExchangeEmailBasePath3, ItemName3, true)
ExchangeEmailItems = []details.DetailsEntry{ ExchangeEmailItems = []details.DetailsEntry{
{ {
@ -75,6 +78,19 @@ var (
}, },
}, },
}, },
{
RepoRef: ExchangeEmailItemPath3.String(),
ShortRef: ExchangeEmailItemPath3.ShortRef(),
ParentRef: ExchangeEmailItemPath3.ToBuilder().Dir().ShortRef(),
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
Sender: "another-person",
Subject: "baz",
Received: Time2,
},
},
},
} }
ExchangeContactsRootPath = mustParsePath("tenant-id/exchange/user-id/contacts/contacts", false) ExchangeContactsRootPath = mustParsePath("tenant-id/exchange/user-id/contacts/contacts", false)