724 lines
15 KiB
Go
724 lines
15 KiB
Go
package path
|
|
|
|
import (
|
|
"fmt"
|
|
"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/tester"
|
|
)
|
|
|
|
type testData struct {
|
|
name string
|
|
input []string
|
|
expectedString string
|
|
}
|
|
|
|
// Test cases that are the same with and without escaping by the
|
|
// system-under-test.
|
|
var genericCases = []testData{
|
|
{
|
|
name: "SimplePath",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
`a`,
|
|
`path`,
|
|
},
|
|
expectedString: "this/is/a/path",
|
|
},
|
|
{
|
|
name: "EmptyElement",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
``,
|
|
`a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is/a/path`,
|
|
},
|
|
{
|
|
name: "EmptyInput",
|
|
expectedString: "",
|
|
},
|
|
}
|
|
|
|
// Inputs that should be escaped.
|
|
var basicUnescapedInputs = []testData{
|
|
{
|
|
name: "EscapeSeparator",
|
|
input: []string{
|
|
`this`,
|
|
`is/a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is\/a/path`,
|
|
},
|
|
{
|
|
name: "EscapeEscapeChar",
|
|
input: []string{
|
|
`this`,
|
|
`is\`,
|
|
`a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is\\/a/path`,
|
|
},
|
|
{
|
|
name: "EscapeEscapeAndSeparator",
|
|
input: []string{
|
|
`this`,
|
|
`is\/a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is\\\/a/path`,
|
|
},
|
|
{
|
|
name: "SeparatorAtEndOfElement",
|
|
input: []string{
|
|
`this`,
|
|
`is/`,
|
|
`a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is\//a/path`,
|
|
},
|
|
{
|
|
name: "SeparatorAtEndOfPath",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
`a`,
|
|
`path/`,
|
|
},
|
|
expectedString: `this/is/a/path\/`,
|
|
},
|
|
}
|
|
|
|
// Inputs that are already escaped.
|
|
var basicEscapedInputs = []testData{
|
|
{
|
|
name: "EscapedSeparator",
|
|
input: []string{
|
|
`this`,
|
|
`is\/a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is\/a/path`,
|
|
},
|
|
{
|
|
name: "EscapedEscapeChar",
|
|
input: []string{
|
|
`this`,
|
|
`is\\`,
|
|
`a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is\\/a/path`,
|
|
},
|
|
{
|
|
name: "EscapedEscapeAndSeparator",
|
|
input: []string{
|
|
`this`,
|
|
`is\\\/a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is\\\/a/path`,
|
|
},
|
|
{
|
|
name: "EscapedSeparatorAtEndOfElement",
|
|
input: []string{
|
|
`this`,
|
|
`is\/`,
|
|
`a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is\//a/path`,
|
|
},
|
|
{
|
|
name: "EscapedSeparatorAtEndOfPath",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
`a`,
|
|
`path\/`,
|
|
},
|
|
expectedString: `this/is/a/path\/`,
|
|
},
|
|
{
|
|
name: "ElementOfSeparator",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
`/`,
|
|
`a`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is/a/path`,
|
|
},
|
|
{
|
|
name: "TrailingElementSeparator",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
`a/`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is/a/path`,
|
|
},
|
|
{
|
|
name: "MultipleTrailingElementSeparator",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
`a///`,
|
|
`path`,
|
|
},
|
|
expectedString: `this/is/a/path`,
|
|
},
|
|
{
|
|
name: "TrailingSeparatorAtEnd",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
`a`,
|
|
`path/`,
|
|
},
|
|
expectedString: `this/is/a/path`,
|
|
},
|
|
{
|
|
name: "MultipleTrailingSeparatorAtEnd",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
`a`,
|
|
`path///`,
|
|
},
|
|
expectedString: `this/is/a/path`,
|
|
},
|
|
{
|
|
name: "TrailingSeparatorWithEmptyElementAtEnd",
|
|
input: []string{
|
|
`this`,
|
|
`is`,
|
|
`a`,
|
|
`path/`,
|
|
``,
|
|
},
|
|
expectedString: `this/is/a/path`,
|
|
},
|
|
}
|
|
|
|
type PathUnitSuite struct {
|
|
tester.Suite
|
|
}
|
|
|
|
func TestPathUnitSuite(t *testing.T) {
|
|
suite.Run(t, &PathUnitSuite{Suite: tester.NewUnitSuite(t)})
|
|
}
|
|
|
|
// set the clues hashing to mask for the span of this suite
|
|
func (suite *PathUnitSuite) SetupSuite() {
|
|
clues.SetHasher(clues.HashCfg{HashAlg: clues.Flatmask})
|
|
}
|
|
|
|
// revert clues hashing to plaintext for all other tests
|
|
func (suite *PathUnitSuite) TeardownSuite() {
|
|
clues.SetHasher(clues.NoHash())
|
|
}
|
|
|
|
func (suite *PathUnitSuite) TestFromDataLayerPathErrors() {
|
|
table := []struct {
|
|
name string
|
|
escapedPath string
|
|
}{
|
|
{
|
|
name: "TooFewElements",
|
|
escapedPath: `some/short/path`,
|
|
},
|
|
{
|
|
name: "TooFewElementsEmptyElement",
|
|
escapedPath: `tenant/exchange//email/folder`,
|
|
},
|
|
{
|
|
name: "BadEscapeSequence",
|
|
escapedPath: `tenant/exchange/user/email/folder\a`,
|
|
},
|
|
{
|
|
name: "TrailingEscapeCharacter",
|
|
escapedPath: `tenant/exchange/user/email/folder\`,
|
|
},
|
|
{
|
|
name: "UnknownService",
|
|
escapedPath: `tenant/badService/user/email/folder`,
|
|
},
|
|
{
|
|
name: "UnknownCategory",
|
|
escapedPath: `tenant/exchange/user/badCategory/folder`,
|
|
},
|
|
{
|
|
name: "NoFolderOrItem",
|
|
escapedPath: `tenant/exchange/user/email`,
|
|
},
|
|
{
|
|
name: "EmptyPath",
|
|
escapedPath: ``,
|
|
},
|
|
{
|
|
name: "JustPathSeparator",
|
|
escapedPath: `/`,
|
|
},
|
|
{
|
|
name: "JustMultiplePathSeparators",
|
|
escapedPath: `//`,
|
|
},
|
|
}
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
t := suite.T()
|
|
|
|
_, err := FromDataLayerPath(test.escapedPath, false)
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *PathUnitSuite) TestFromDataLayerPath() {
|
|
const (
|
|
testTenant = "tenant"
|
|
testResource = "resource"
|
|
testElement1 = "folder/"
|
|
testElementTrimmed = "folder"
|
|
testElement2 = "folder2"
|
|
testElement3 = "other"
|
|
)
|
|
|
|
isItem := []struct {
|
|
name string
|
|
isItem bool
|
|
}{
|
|
{
|
|
name: "Folder",
|
|
isItem: false,
|
|
},
|
|
{
|
|
name: "Item",
|
|
isItem: true,
|
|
},
|
|
}
|
|
table := []struct {
|
|
name string
|
|
// Should have placeholders of '%s' for service and category.
|
|
unescapedPath string
|
|
// Expected result for Folder() if path is marked as a folder.
|
|
expectedFolder string
|
|
// Expected result for Item() if path is marked as an item.
|
|
// Expected result for Split(Folder()) if path is marked as a folder.
|
|
expectedSplit []string
|
|
expectedItem string
|
|
// Expected result for Folder() if path is marked as an item.
|
|
expectedItemFolder string
|
|
// Expected result for Split(Folder()) if path is marked as an item.
|
|
expectedItemSplit []string
|
|
}{
|
|
{
|
|
name: "BasicPath",
|
|
unescapedPath: fmt.Sprintf(
|
|
"%s/%%s/%s/%%s/%s/%s/%s",
|
|
testTenant,
|
|
testResource,
|
|
testElement1,
|
|
testElement2,
|
|
testElement3),
|
|
expectedFolder: fmt.Sprintf(
|
|
"%s/%s/%s",
|
|
testElementTrimmed,
|
|
testElement2,
|
|
testElement3),
|
|
expectedSplit: []string{
|
|
testElementTrimmed,
|
|
testElement2,
|
|
testElement3,
|
|
},
|
|
expectedItem: testElement3,
|
|
expectedItemFolder: fmt.Sprintf(
|
|
"%s/%s",
|
|
testElementTrimmed,
|
|
testElement2),
|
|
expectedItemSplit: []string{
|
|
testElementTrimmed,
|
|
testElement2,
|
|
},
|
|
},
|
|
{
|
|
name: "PathWithEmptyElements",
|
|
unescapedPath: fmt.Sprintf(
|
|
"/%s//%%s//%s//%%s//%s///%s//%s//",
|
|
testTenant,
|
|
testResource,
|
|
testElementTrimmed,
|
|
testElement2,
|
|
testElement3),
|
|
expectedFolder: fmt.Sprintf(
|
|
"%s/%s/%s",
|
|
testElementTrimmed,
|
|
testElement2,
|
|
testElement3),
|
|
expectedSplit: []string{
|
|
testElementTrimmed,
|
|
testElement2,
|
|
testElement3,
|
|
},
|
|
expectedItem: testElement3,
|
|
expectedItemFolder: fmt.Sprintf(
|
|
"%s/%s",
|
|
testElementTrimmed,
|
|
testElement2),
|
|
expectedItemSplit: []string{
|
|
testElementTrimmed,
|
|
testElement2,
|
|
},
|
|
},
|
|
}
|
|
|
|
for service, cats := range serviceCategories {
|
|
for cat := range cats {
|
|
for _, item := range isItem {
|
|
suite.Run(fmt.Sprintf("%s-%s-%s", service, cat, item.name), func() {
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
t := suite.T()
|
|
testPath := fmt.Sprintf(test.unescapedPath, service, cat)
|
|
|
|
p, err := FromDataLayerPath(testPath, item.isItem)
|
|
require.NoError(t, err, clues.ToCore(err))
|
|
|
|
assert.Equal(t, service, p.Service(), "service")
|
|
assert.Equal(t, cat, p.Category(), "category")
|
|
assert.Equal(t, testTenant, p.Tenant(), "tenant")
|
|
assert.Equal(t, testResource, p.ProtectedResource(), "resource owner")
|
|
|
|
fld := p.Folder(false)
|
|
escfld := p.Folder(true)
|
|
|
|
if item.isItem {
|
|
assert.Equal(t, test.expectedItemFolder, fld, "item folder")
|
|
assert.Equal(t, test.expectedItemSplit, Split(fld), "item split")
|
|
assert.Equal(t, test.expectedItemFolder, escfld, "escaped item folder")
|
|
assert.Equal(t, test.expectedItemSplit, Split(escfld), "escaped item split")
|
|
assert.Equal(t, test.expectedItem, p.Item(), "item")
|
|
} else {
|
|
assert.Equal(t, test.expectedFolder, fld, "dir folder")
|
|
assert.Equal(t, test.expectedSplit, Split(fld), "dir split")
|
|
assert.Equal(t, test.expectedFolder, escfld, "escaped dir folder")
|
|
assert.Equal(t, test.expectedSplit, Split(escfld), "escaped dir split")
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (suite *PathUnitSuite) TestPrefixOrPathFromDataLayerPath() {
|
|
t := suite.T()
|
|
input := fmt.Sprintf(
|
|
"%s/%s/%s/%s",
|
|
"tenant",
|
|
ExchangeService.String(),
|
|
"user",
|
|
EmailCategory.String())
|
|
|
|
// Check that we can make a valid prefix path.
|
|
p, err := PrefixOrPathFromDataLayerPath(input, false)
|
|
assert.NoError(t, err, clues.ToCore(err))
|
|
assert.Equal(t, input, p.String())
|
|
|
|
// Check we can't make a regular path with the same input since it doesn't
|
|
// have enough segments.
|
|
_, err = FromDataLayerPath(input, false)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func (suite *PathUnitSuite) TestBuildOrPrefix() {
|
|
table := []struct {
|
|
name string
|
|
service ServiceType
|
|
category CategoryType
|
|
tenant string
|
|
owner string
|
|
folders []string
|
|
expect string
|
|
expectErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "ok, empty elements",
|
|
service: ExchangeService,
|
|
category: ContactsCategory,
|
|
tenant: "t",
|
|
owner: "ro",
|
|
folders: []string{},
|
|
expect: join([]string{"t", ExchangeService.String(), "ro", ContactsCategory.String()}),
|
|
expectErr: require.NoError,
|
|
},
|
|
{
|
|
name: "ok, non-empty elements",
|
|
service: ExchangeService,
|
|
category: ContactsCategory,
|
|
tenant: "t",
|
|
owner: "ro",
|
|
folders: []string{"foo"},
|
|
expect: join([]string{"t", ExchangeService.String(), "ro", ContactsCategory.String(), "foo"}),
|
|
expectErr: require.NoError,
|
|
},
|
|
{
|
|
name: "bad category",
|
|
service: ExchangeService,
|
|
category: FilesCategory,
|
|
tenant: "t",
|
|
owner: "ro",
|
|
expectErr: require.Error,
|
|
},
|
|
{
|
|
name: "bad tenant",
|
|
service: ExchangeService,
|
|
category: ContactsCategory,
|
|
tenant: "",
|
|
owner: "ro",
|
|
expectErr: require.Error,
|
|
},
|
|
{
|
|
name: "bad owner",
|
|
service: ExchangeService,
|
|
category: ContactsCategory,
|
|
tenant: "t",
|
|
owner: "",
|
|
expectErr: require.Error,
|
|
},
|
|
}
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
t := suite.T()
|
|
|
|
r, err := BuildOrPrefix(test.tenant, test.owner, test.service, test.category, false, test.folders...)
|
|
test.expectErr(t, err, clues.ToCore(err))
|
|
|
|
if r == nil {
|
|
return
|
|
}
|
|
|
|
assert.Equal(t, test.expect, r.String())
|
|
assert.NotPanics(t, func() {
|
|
r.Folders()
|
|
r.Item()
|
|
}, "runs Folders() and Item()")
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *PathUnitSuite) TestBuildPrefix() {
|
|
table := []struct {
|
|
name string
|
|
service ServiceType
|
|
category CategoryType
|
|
tenant string
|
|
owner string
|
|
expect string
|
|
expectErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "ok",
|
|
service: ExchangeService,
|
|
category: ContactsCategory,
|
|
tenant: "t",
|
|
owner: "ro",
|
|
expect: join([]string{"t", ExchangeService.String(), "ro", ContactsCategory.String()}),
|
|
expectErr: require.NoError,
|
|
},
|
|
{
|
|
name: "bad category",
|
|
service: ExchangeService,
|
|
category: FilesCategory,
|
|
tenant: "t",
|
|
owner: "ro",
|
|
expectErr: require.Error,
|
|
},
|
|
{
|
|
name: "bad tenant",
|
|
service: ExchangeService,
|
|
category: ContactsCategory,
|
|
tenant: "",
|
|
owner: "ro",
|
|
expectErr: require.Error,
|
|
},
|
|
{
|
|
name: "bad owner",
|
|
service: ExchangeService,
|
|
category: ContactsCategory,
|
|
tenant: "t",
|
|
owner: "",
|
|
expectErr: require.Error,
|
|
},
|
|
}
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
t := suite.T()
|
|
|
|
r, err := BuildPrefix(test.tenant, test.owner, test.service, test.category)
|
|
test.expectErr(t, err, clues.ToCore(err))
|
|
|
|
if r == nil {
|
|
return
|
|
}
|
|
|
|
assert.Equal(t, test.expect, r.String())
|
|
assert.NotPanics(t, func() {
|
|
r.Folders()
|
|
r.Item()
|
|
}, "runs Folders() and Item()")
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *PathUnitSuite) TestBuildRestorePaths() {
|
|
type args struct {
|
|
tenantID string
|
|
protectedResource string
|
|
service ServiceType
|
|
category CategoryType
|
|
fp []string
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
restorePath string
|
|
storagePath string
|
|
expectErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "single",
|
|
args: args{
|
|
tenantID: "tenant",
|
|
protectedResource: "protectedResource",
|
|
service: GroupsService,
|
|
category: LibrariesCategory,
|
|
fp: []string{"a"},
|
|
},
|
|
restorePath: "tenant/groupsMetadata/protectedResource/libraries",
|
|
storagePath: "tenant/groupsMetadata/protectedResource/libraries/a",
|
|
expectErr: require.NoError,
|
|
},
|
|
{
|
|
name: "multi",
|
|
args: args{
|
|
tenantID: "tenant",
|
|
protectedResource: "protectedResource",
|
|
service: GroupsService,
|
|
category: LibrariesCategory,
|
|
fp: []string{"a", "b"},
|
|
},
|
|
restorePath: "tenant/groupsMetadata/protectedResource/libraries/a",
|
|
storagePath: "tenant/groupsMetadata/protectedResource/libraries/a/b",
|
|
expectErr: require.NoError,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
suite.Run(test.name, func() {
|
|
t := suite.T()
|
|
|
|
r, err := BuildMetadata(
|
|
test.args.tenantID,
|
|
test.args.protectedResource,
|
|
test.args.service,
|
|
test.args.category,
|
|
true,
|
|
test.args.fp...)
|
|
test.expectErr(t, err, clues.ToCore(err))
|
|
|
|
rdir, err := r.Dir()
|
|
require.NoError(t, err, clues.ToCore(err))
|
|
|
|
assert.Equal(t, test.restorePath, rdir.String(), "restore path")
|
|
assert.Equal(t, test.storagePath, r.String(), "storage path")
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *PathUnitSuite) TestArePathsEquivalent() {
|
|
tests := []struct {
|
|
name string
|
|
path1 string
|
|
path2 string
|
|
equivalent bool
|
|
}{
|
|
// Test cases with equivalent paths
|
|
{
|
|
name: "same linux normal path",
|
|
path1: "/path/to/dir",
|
|
path2: "/path/to/dir",
|
|
equivalent: true,
|
|
},
|
|
{
|
|
name: "same windows normal path",
|
|
path1: "C:\\Users\\User\\Documents",
|
|
path2: "C:\\Users\\User\\Documents",
|
|
equivalent: true,
|
|
},
|
|
{
|
|
name: "same linux extra trailing slash path",
|
|
path1: "/path/with/trailing/slash/",
|
|
path2: "/path/with/trailing/slash",
|
|
equivalent: true,
|
|
},
|
|
{
|
|
name: "same linux relative path",
|
|
path1: "relative/path",
|
|
path2: "./relative/path",
|
|
equivalent: true,
|
|
},
|
|
|
|
// Test cases with non-equivalent paths
|
|
{
|
|
name: "different linux normal path",
|
|
path1: "/path/to/dir",
|
|
path2: "/path/to/different/dir",
|
|
equivalent: false,
|
|
},
|
|
{
|
|
name: "different windows normal path",
|
|
path1: "C:\\Users\\User\\Documents",
|
|
path2: "D:\\Users\\User\\Documents",
|
|
equivalent: false,
|
|
},
|
|
{
|
|
name: "different linux extra trailing slash path",
|
|
path1: "/path/with/trailing/slash/",
|
|
path2: "/different/path/with/trailing/slash",
|
|
equivalent: false,
|
|
},
|
|
{
|
|
name: "different linux relative path",
|
|
path1: "relative/path",
|
|
path2: "../relative/path",
|
|
equivalent: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t := suite.T()
|
|
suite.Run(test.name, func() {
|
|
result := ArePathsEquivalent(test.path1, test.path2)
|
|
if result != test.equivalent {
|
|
t.Errorf("Paths %s and %s are not equivalent as expected.", test.path1, test.path2)
|
|
}
|
|
})
|
|
}
|
|
}
|