add pathEquals to filter set (#2395)
## Description Adds a pathEquals filter, and a corresponding option for setting pathEquals in the scope options. This will enable tests to restrict calendar lookups to only the primary calendar folder, instead of including all children underneath. ## Does this PR need a docs update or release note? - [x] ⛔ No ## Type of change - [x] 🤖 Test ## Issue(s) * #2388 ## Test Plan - [x] ⚡ Unit test
This commit is contained in:
parent
a7fd90b2f8
commit
47f5ca1d95
@ -28,9 +28,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add versions to backups so that we can understand/handle older backup formats
|
||||
|
||||
### Fixed
|
||||
- Backing up a calendar that has the same name as the default calendar
|
||||
|
||||
- Added additional backoff-retry to all OneDrive queries.
|
||||
- Users with `null` userType values are no longer excluded from user queries.
|
||||
- Fix bug when backing up a calendar that has the same name as the default calendar
|
||||
|
||||
### Known Issues
|
||||
|
||||
|
||||
@ -36,6 +36,8 @@ const (
|
||||
TargetPathContains
|
||||
// "baz" equals any complete element suffix of "foo/bar/baz"
|
||||
TargetPathSuffix
|
||||
// "foo/bar/baz" equals the complete path "foo/bar/baz"
|
||||
TargetPathEquals
|
||||
)
|
||||
|
||||
func norm(s string) string {
|
||||
@ -295,6 +297,44 @@ func NotPathSuffix(targets []string) Filter {
|
||||
return newSliceFilter(TargetPathSuffix, targets, tgts, true)
|
||||
}
|
||||
|
||||
// PathEquals creates a filter where Compare(v) is true if
|
||||
// target.Equals(v) &&
|
||||
// split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
||||
// ex: target "foo" returns true for inputs "/foo/", "/foo", and "foo/"
|
||||
// but false for "/foo/bar", "bar/foo/", and "/foobar/"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func PathEquals(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathEquals, targets, tgts, false)
|
||||
}
|
||||
|
||||
// NotPathEquals creates a filter where Compare(v) is true if
|
||||
// !target.Equals(v) ||
|
||||
// !split(target)[i].Equals(split(v)[i]) for _all_ i in 0..len(target)-1
|
||||
// ex: target "foo" returns true "/foo/bar", "bar/foo/", and "/foobar/"
|
||||
// but false for for inputs "/foo/", "/foo", and "foo/"
|
||||
//
|
||||
// Unlike single-target filters, this filter accepts a
|
||||
// slice of targets, will compare an input against each target
|
||||
// independently, and returns true if one or more of the
|
||||
// comparisons succeed.
|
||||
func NotPathEquals(targets []string) Filter {
|
||||
tgts := make([]string, len(targets))
|
||||
for i := range targets {
|
||||
tgts[i] = normPathElem(targets[i])
|
||||
}
|
||||
|
||||
return newSliceFilter(TargetPathEquals, targets, tgts, true)
|
||||
}
|
||||
|
||||
// newFilter is the standard filter constructor.
|
||||
func newFilter(c comparator, target string, negate bool) Filter {
|
||||
return Filter{
|
||||
@ -367,6 +407,9 @@ func (f Filter) Compare(input string) bool {
|
||||
case TargetPathSuffix:
|
||||
cmp = pathSuffix
|
||||
hasSlice = true
|
||||
case TargetPathEquals:
|
||||
cmp = pathEquals
|
||||
hasSlice = true
|
||||
case Passes:
|
||||
return true
|
||||
case Fails:
|
||||
@ -471,6 +514,20 @@ func pathSuffix(target, input string) bool {
|
||||
return strings.HasSuffix(normPathElem(input), target)
|
||||
}
|
||||
|
||||
// true if target is an _exact_ match on the input, excluding
|
||||
// path delmiters. Element complete means we do not succeed
|
||||
// on partial element matches (ex: "/bar" does not match
|
||||
// "/foobar").
|
||||
//
|
||||
// As a precondition, assumes the target value has been
|
||||
// passed through normPathElem().
|
||||
//
|
||||
// The input is assumed to be the complete path that may
|
||||
// match the target.
|
||||
func pathEquals(target, input string) bool {
|
||||
return normPathElem(input) == target
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
@ -487,6 +544,7 @@ var prefixString = map[comparator]string{
|
||||
TargetPathPrefix: "pathPfx:",
|
||||
TargetPathContains: "pathCont:",
|
||||
TargetPathSuffix: "pathSfx:",
|
||||
TargetPathEquals: "pathEq:",
|
||||
}
|
||||
|
||||
func (f Filter) String() string {
|
||||
|
||||
@ -461,3 +461,70 @@ func (suite *FiltersSuite) TestPathSuffix_NormalizedTargets() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestPathEquals() {
|
||||
table := []struct {
|
||||
name string
|
||||
targets []string
|
||||
input string
|
||||
expectF assert.BoolAssertionFunc
|
||||
expectNF assert.BoolAssertionFunc
|
||||
}{
|
||||
{"Exact - same case", []string{"fA"}, "/fA", assert.True, assert.False},
|
||||
{"Exact - different case", []string{"fa"}, "/fA", assert.True, assert.False},
|
||||
{"Exact - multiple folders", []string{"fA/fB"}, "/fA/fB", assert.True, assert.False},
|
||||
{"Exact - target variations - none", []string{"fA"}, "/fA", assert.True, assert.False},
|
||||
{"Exact - target variations - prefix", []string{"/fA"}, "/fA", assert.True, assert.False},
|
||||
{"Exact - target variations - suffix", []string{"fA/"}, "/fA", assert.True, assert.False},
|
||||
{"Exact - target variations - both", []string{"/fA/"}, "/fA", assert.True, assert.False},
|
||||
{"Exact - input variations - none", []string{"fA"}, "fA", assert.True, assert.False},
|
||||
{"Exact - input variations - prefix", []string{"fA"}, "/fA", assert.True, assert.False},
|
||||
{"Exact - input variations - suffix", []string{"fA"}, "fA/", assert.True, assert.False},
|
||||
{"Exact - input variations - both", []string{"fA"}, "/fA/", assert.True, assert.False},
|
||||
{"Partial match", []string{"f"}, "/fA/", assert.False, assert.True},
|
||||
{"Suffix - same case", []string{"fB"}, "/fA/fB", assert.False, assert.True},
|
||||
{"Suffix - different case", []string{"fb"}, "/fA/fB", assert.False, assert.True},
|
||||
{"Prefix - same case", []string{"fA"}, "/fA/fB", assert.False, assert.True},
|
||||
{"Prefix - different case", []string{"fa"}, "/fA/fB", assert.False, assert.True},
|
||||
{"Contains - same case", []string{"fB"}, "/fA/fB/fC", assert.False, assert.True},
|
||||
{"Contains - different case", []string{"fb"}, "/fA/fB/fC", assert.False, assert.True},
|
||||
{"Slice - one matches", []string{"foo", "/fA/fb", "fb"}, "/fA/fb", assert.True, assert.True},
|
||||
{"Slice - none match", []string{"foo", "fa/f", "f"}, "/fA/fb", assert.False, assert.True},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
f := filters.PathEquals(test.targets)
|
||||
nf := filters.NotPathEquals(test.targets)
|
||||
|
||||
test.expectF(t, f.Compare(test.input), "filter")
|
||||
test.expectNF(t, nf.Compare(test.input), "negated filter")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersSuite) TestPathEquals_NormalizedTargets() {
|
||||
table := []struct {
|
||||
name string
|
||||
targets []string
|
||||
expect []string
|
||||
}{
|
||||
{"Single - no slash", []string{"fA"}, []string{"/fA/"}},
|
||||
{"Single - pre slash", []string{"/fA"}, []string{"/fA/"}},
|
||||
{"Single - suff slash", []string{"fA/"}, []string{"/fA/"}},
|
||||
{"Single - both slashes", []string{"/fA/"}, []string{"/fA/"}},
|
||||
{"Multipath - no slash", []string{"fA/fB"}, []string{"/fA/fB/"}},
|
||||
{"Multipath - pre slash", []string{"/fA/fB"}, []string{"/fA/fB/"}},
|
||||
{"Multipath - suff slash", []string{"fA/fB/"}, []string{"/fA/fB/"}},
|
||||
{"Multipath - both slashes", []string{"/fA/fB/"}, []string{"/fA/fB/"}},
|
||||
{"Multi input - no slash", []string{"fA", "fB"}, []string{"/fA/", "/fB/"}},
|
||||
{"Multi input - pre slash", []string{"/fA", "/fB"}, []string{"/fA/", "/fB/"}},
|
||||
{"Multi input - suff slash", []string{"fA/", "fB/"}, []string{"/fA/", "/fB/"}},
|
||||
{"Multi input - both slashes", []string{"/fA/", "/fB/"}, []string{"/fA/", "/fB/"}},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
f := filters.PathEquals(test.targets)
|
||||
assert.Equal(t, test.expect, f.NormalizedTargets)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -343,6 +343,7 @@ type scopeConfig struct {
|
||||
usePathFilter bool
|
||||
usePrefixFilter bool
|
||||
useSuffixFilter bool
|
||||
useEqualsFilter bool
|
||||
}
|
||||
|
||||
type option func(*scopeConfig)
|
||||
@ -371,6 +372,15 @@ func SuffixMatch() option {
|
||||
}
|
||||
}
|
||||
|
||||
// ExactMatch ensures the selector uses an Equals comparator, instead
|
||||
// of contains. Will not override a default Any() or None()
|
||||
// comparator.
|
||||
func ExactMatch() option {
|
||||
return func(sc *scopeConfig) {
|
||||
sc.useEqualsFilter = true
|
||||
}
|
||||
}
|
||||
|
||||
// pathComparator is an internal-facing option. It is assumed that scope
|
||||
// constructors will provide the pathComparator option whenever a folder-
|
||||
// level scope (ie, a scope that compares path hierarchies) is created.
|
||||
@ -433,6 +443,10 @@ func filterize(sc scopeConfig, s ...string) filters.Filter {
|
||||
}
|
||||
|
||||
if sc.usePathFilter {
|
||||
if sc.useEqualsFilter {
|
||||
return filters.PathEquals(s)
|
||||
}
|
||||
|
||||
if sc.usePrefixFilter {
|
||||
return filters.PathPrefix(s)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user