add prefix option to scopes (#1141)

## Description

adds extensible options to folder-level scopes that allows the caller to specify whether they want a
prefix-comparison matcher or a contains-comparison matcher.

Also corrects the behavior of the prefix filter so that it accurately follows the "target is prefix of input" specification, rather than the reverse.

## Type of change

- [x] 🌻 Feature

## Issue(s)

* #1133

## Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2022-10-13 16:58:18 -06:00 committed by GitHub
parent 5ee4ceb5a6
commit e5243404a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 191 additions and 191 deletions

View File

@ -24,8 +24,7 @@ const (
Fails Fails
// passthrough for the target // passthrough for the target
IdentityValue IdentityValue
// target is a prefix of the value it is compared // "foo" is a prefix of "foo/bar/baz"
// against
TargetPrefixes TargetPrefixes
) )
@ -143,6 +142,18 @@ func newFilter(c comparator, target string, negate bool) Filter {
// Comparisons // Comparisons
// ---------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------
// CompareAny checks whether any one of all the provided
// inputs passes the filter.
func (f Filter) CompareAny(inputs ...string) bool {
for _, in := range inputs {
if f.Compare(in) {
return true
}
}
return false
}
// Compare checks whether the input passes the filter. // Compare checks whether the input passes the filter.
func (f Filter) Compare(input string) bool { func (f Filter) Compare(input string) bool {
var cmp func(string, string) bool var cmp func(string, string) bool
@ -201,7 +212,7 @@ func in(target, input string) bool {
// true if target has input as a prefix. // true if target has input as a prefix.
func prefixed(target, input string) bool { func prefixed(target, input string) bool {
return strings.HasPrefix(target, input) return strings.HasPrefix(input, target)
} }
// ---------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------

View File

@ -37,6 +37,27 @@ func (suite *FiltersSuite) TestEquals() {
} }
} }
func (suite *FiltersSuite) TestEquals_any() {
f := filters.Equal("foo")
nf := filters.NotEqual("foo")
table := []struct {
name string
input []string
expectF assert.BoolAssertionFunc
expectNF assert.BoolAssertionFunc
}{
{"includes target", []string{"foo", "bar"}, assert.True, assert.True},
{"not includes target", []string{"baz", "qux"}, assert.False, assert.True},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
test.expectF(t, f.CompareAny(test.input...), "filter")
test.expectNF(t, nf.CompareAny(test.input...), "negated filter")
})
}
}
func (suite *FiltersSuite) TestGreater() { func (suite *FiltersSuite) TestGreater() {
f := filters.Greater("5") f := filters.Greater("5")
nf := filters.NotGreater("5") nf := filters.NotGreater("5")
@ -161,11 +182,13 @@ func (suite *FiltersSuite) TestIn_Joined() {
} }
func (suite *FiltersSuite) TestPrefixes() { func (suite *FiltersSuite) TestPrefixes() {
input := "folderA" target := "folderA"
f := filters.Prefix(target)
nf := filters.NotPrefix(target)
table := []struct { table := []struct {
name string name string
target string input string
expectF assert.BoolAssertionFunc expectF assert.BoolAssertionFunc
expectNF assert.BoolAssertionFunc expectNF assert.BoolAssertionFunc
}{ }{
@ -177,10 +200,8 @@ func (suite *FiltersSuite) TestPrefixes() {
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
f := filters.Prefix(test.target) test.expectF(t, f.Compare(test.input), "filter")
nf := filters.NotPrefix(test.target) test.expectNF(t, nf.Compare(test.input), "negated filter")
test.expectF(t, f.Compare(input), "filter")
test.expectNF(t, nf.Compare(input), "negated filter")
}) })
} }
} }

View File

@ -189,12 +189,12 @@ func (s *exchange) Contacts(users, folders, contacts []string) []ExchangeScope {
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *exchange) ContactFolders(users, folders []string) []ExchangeScope { func (s *exchange) ContactFolders(users, folders []string, opts ...option) []ExchangeScope {
scopes := []ExchangeScope{} scopes := []ExchangeScope{}
scopes = append( scopes = append(
scopes, scopes,
makeScope[ExchangeScope](ExchangeContactFolder, users, folders), makeScope[ExchangeScope](ExchangeContactFolder, users, folders, opts...),
) )
return scopes return scopes
@ -221,12 +221,12 @@ func (s *exchange) Events(users, calendars, events []string) []ExchangeScope {
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *exchange) EventCalendars(users, events []string) []ExchangeScope { func (s *exchange) EventCalendars(users, events []string, opts ...option) []ExchangeScope {
scopes := []ExchangeScope{} scopes := []ExchangeScope{}
scopes = append( scopes = append(
scopes, scopes,
makeScope[ExchangeScope](ExchangeEventCalendar, users, events), makeScope[ExchangeScope](ExchangeEventCalendar, users, events, opts...),
) )
return scopes return scopes
@ -252,12 +252,12 @@ func (s *exchange) Mails(users, folders, mails []string) []ExchangeScope {
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *exchange) MailFolders(users, folders []string) []ExchangeScope { func (s *exchange) MailFolders(users, folders []string, opts ...option) []ExchangeScope {
scopes := []ExchangeScope{} scopes := []ExchangeScope{}
scopes = append( scopes = append(
scopes, scopes,
makeScope[ExchangeScope](ExchangeMailFolder, users, folders), makeScope[ExchangeScope](ExchangeMailFolder, users, folders, opts...),
) )
return scopes return scopes
@ -429,44 +429,6 @@ func (sr *ExchangeRestore) MailSubject(subject string) []ExchangeScope {
} }
} }
// ---------------------------------------------------------------------------
// Destination
// ---------------------------------------------------------------------------
type ExchangeDestination Destination
func NewExchangeDestination() ExchangeDestination {
return ExchangeDestination{}
}
// GetOrDefault gets the destination of the provided category. If no
// destination is set, returns the current value.
func (d ExchangeDestination) GetOrDefault(cat exchangeCategory, current string) string {
dest, ok := d[cat.String()]
if !ok {
return current
}
return dest.Target
}
// Sets the destination value of the provided category. Returns an error
// if a destination is already declared for that category.
func (d ExchangeDestination) Set(cat exchangeCategory, dest string) error {
if len(dest) == 0 {
return nil
}
cs := cat.String()
if curr, ok := d[cs]; ok {
return existingDestinationErr(cs, curr.Target)
}
d[cs] = filterize(dest)
return nil
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Categories // Categories
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -479,55 +479,6 @@ func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Users() {
} }
} }
func (suite *ExchangeSelectorSuite) TestNewExchangeDestination() {
t := suite.T()
dest := NewExchangeDestination()
assert.Len(t, dest, 0)
}
func (suite *ExchangeSelectorSuite) TestExchangeDestination_Set() {
dest := NewExchangeDestination()
table := []exchangeCategory{
ExchangeCategoryUnknown,
ExchangeContact,
ExchangeContactFolder,
ExchangeEvent,
ExchangeMail,
ExchangeMailFolder,
ExchangeUser,
}
for _, test := range table {
suite.T().Run(test.String(), func(t *testing.T) {
assert.NoError(t, dest.Set(test, "foo"))
assert.Error(t, dest.Set(test, "foo"))
})
}
assert.NoError(suite.T(), dest.Set(ExchangeUser, ""))
}
func (suite *ExchangeSelectorSuite) TestExchangeDestination_GetOrDefault() {
dest := NewExchangeDestination()
table := []exchangeCategory{
ExchangeCategoryUnknown,
ExchangeContact,
ExchangeContactFolder,
ExchangeEvent,
ExchangeMail,
ExchangeMailFolder,
ExchangeUser,
}
for _, test := range table {
suite.T().Run(test.String(), func(t *testing.T) {
assert.Equal(t, "bar", dest.GetOrDefault(test, "bar"))
assert.NoError(t, dest.Set(test, "foo"))
assert.Equal(t, "foo", dest.GetOrDefault(test, "bar"))
})
}
}
func (suite *ExchangeSelectorSuite) TestExchangeBackup_Scopes() { func (suite *ExchangeSelectorSuite) TestExchangeBackup_Scopes() {
eb := NewExchangeBackup() eb := NewExchangeBackup()
eb.Include(eb.Users(Any())) eb.Include(eb.Users(Any()))

View File

@ -177,12 +177,12 @@ func (s *oneDrive) Users(users []string) []OneDriveScope {
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any] // If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None] // If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None] // If any slice is empty, it defaults to [selectors.None]
func (s *oneDrive) Folders(users, folders []string) []OneDriveScope { func (s *oneDrive) Folders(users, folders []string, opts ...option) []OneDriveScope {
scopes := []OneDriveScope{} scopes := []OneDriveScope{}
scopes = append( scopes = append(
scopes, scopes,
makeScope[OneDriveScope](OneDriveFolder, users, folders), makeScope[OneDriveScope](OneDriveFolder, users, folders, opts...),
) )
return scopes return scopes

View File

@ -119,12 +119,19 @@ type (
func makeScope[T scopeT]( func makeScope[T scopeT](
cat categorizer, cat categorizer,
resources, vs []string, resources, vs []string,
opts ...option,
) T { ) T {
sc := scopeConfig{}
for _, opt := range opts {
opt(&sc)
}
s := T{ s := T{
scopeKeyCategory: filters.Identity(cat.String()), scopeKeyCategory: filters.Identity(cat.String()),
scopeKeyDataType: filters.Identity(cat.leafCat().String()), scopeKeyDataType: filters.Identity(cat.leafCat().String()),
cat.String(): filterize(vs...), cat.String(): filterize(sc, vs...),
cat.rootCat().String(): filterize(resources...), cat.rootCat().String(): filterize(scopeConfig{}, resources...),
} }
return s return s
@ -189,10 +196,20 @@ func getCatValue[T scopeT](s T, cat categorizer) []string {
// set sets a value by category to the scope. Only intended for internal // set sets a value by category to the scope. Only intended for internal
// use, not for exporting to callers. // use, not for exporting to callers.
func set[T scopeT](s T, cat categorizer, v []string) T { func set[T scopeT](s T, cat categorizer, v []string) T {
s[cat.String()] = filterize(v...) s[cat.String()] = filterize(scopeConfig{}, v...)
return s return s
} }
// returns true if the category is included in the scope's category type,
// and the value is set to None().
func isNoneTarget[T scopeT, C categoryT](s T, cat C) bool {
if !typeAndCategoryMatches(cat, s.categorizer()) {
return false
}
return s[cat.String()].Target == NoneTgt
}
// returns true if the category is included in the scope's category type, // returns true if the category is included in the scope's category type,
// and the value is set to Any(). // and the value is set to Any().
func isAnyTarget[T scopeT, C categoryT](s T, cat C) bool { func isAnyTarget[T scopeT, C categoryT](s T, cat C) bool {
@ -361,67 +378,54 @@ func matchesPathValues[T scopeT, C categoryT](
shortRef string, shortRef string,
) bool { ) bool {
for _, c := range cat.pathKeys() { for _, c := range cat.pathKeys() {
scopeVals := getCatValue(sc, c)
// the scope must define the targets to match on
if len(scopeVals) == 0 {
return false
}
// None() fails all matches
if scopeVals[0] == NoneTgt {
return false
}
// the pathValues must have an entry for the given categorizer // the pathValues must have an entry for the given categorizer
pathVal, ok := pathValues[c] pathVal, ok := pathValues[c]
if !ok { if !ok {
return false return false
} }
// all parts of the scope must match
cc := c.(C) cc := c.(C)
if !isAnyTarget(sc, cc) {
var (
match = false
// Used to check if the path contains the value specified in scopeVals
pathHas = filters.Contains(pathVal)
// Used to check if the path has the value specified in scopeVal as a prefix
pathPrefix = filters.Prefix(pathVal)
// Used to check if the shortRef equals the value specified in scopeVals
shortRefEq = filters.Equal(shortRef)
)
for _, scopeVal := range scopeVals { if isNoneTarget(sc, cc) {
switch { return false
case c.isLeaf() && len(shortRef) > 0: }
// Leaf category - we do a "contains" match for path or equality match on
// the shortRef if isAnyTarget(sc, cc) {
if pathHas.Compare(scopeVal) || shortRefEq.Compare(scopeVal) { // continue, not return: all path keys must match the entry to succeed
match = true continue
} }
case !c.isLeaf() && c != c.rootCat():
// Folder category - we check if the scope is a prefix var (
// TODO: If the scopeVal is not a "path" - then we'll want to check match bool
// if any of the path elements match the scopeVal exactly isLeaf = c.isLeaf()
if pathPrefix.Compare(scopeVal) { isRoot = c == c.rootCat()
match = true )
}
default: switch {
if pathHas.Compare(scopeVal) { // Leaf category - the scope can match either the path value (the item ID itself),
match = true // or the shortRef hash representing the item.
} case isLeaf && len(shortRef) > 0:
} match = matches(sc, cc, pathVal) || matches(sc, cc, shortRef)
// short circuit if we found a match
if match { // Folder category - checks if any target folder is a prefix of the path folders.
// Assumes (correctly) that we need to split the targets and re-compose them into
// individual prefix matchers.
// TODO: assumes all folders require prefix matchers. Users can now specify whether
// the folder filter is a prefix match or not. We should respect that configuration.
case !isLeaf && !isRoot:
for _, tgt := range getCatValue(sc, c) {
if filters.Prefix(tgt).Compare(pathVal) {
match = true
break break
} }
} }
if !match { default:
// Didn't match any scope match = matches(sc, cc, pathVal)
return false }
}
if !match {
return false
} }
} }

View File

@ -65,7 +65,7 @@ func (suite *SelectorScopesSuite) TestContains() {
name: "blank target", name: "blank target",
scope: func() mockScope { scope: func() mockScope {
stub := stubScope("") stub := stubScope("")
stub[rootCatStub.String()] = filterize("fnords") stub[rootCatStub.String()] = filterize(scopeConfig{}, "fnords")
return stub return stub
}, },
check: "", check: "",
@ -75,7 +75,7 @@ func (suite *SelectorScopesSuite) TestContains() {
name: "matching target", name: "matching target",
scope: func() mockScope { scope: func() mockScope {
stub := stubScope("") stub := stubScope("")
stub[rootCatStub.String()] = filterize(rootCatStub.String()) stub[rootCatStub.String()] = filterize(scopeConfig{}, rootCatStub.String())
return stub return stub
}, },
check: rootCatStub.String(), check: rootCatStub.String(),
@ -85,7 +85,7 @@ func (suite *SelectorScopesSuite) TestContains() {
name: "non-matching target", name: "non-matching target",
scope: func() mockScope { scope: func() mockScope {
stub := stubScope("") stub := stubScope("")
stub[rootCatStub.String()] = filterize(rootCatStub.String()) stub[rootCatStub.String()] = filterize(scopeConfig{}, rootCatStub.String())
return stub return stub
}, },
check: "smarf", check: "smarf",
@ -105,7 +105,7 @@ func (suite *SelectorScopesSuite) TestGetCatValue() {
t := suite.T() t := suite.T()
stub := stubScope("") stub := stubScope("")
stub[rootCatStub.String()] = filterize(rootCatStub.String()) stub[rootCatStub.String()] = filterize(scopeConfig{}, rootCatStub.String())
assert.Equal(t, assert.Equal(t,
[]string{rootCatStub.String()}, []string{rootCatStub.String()},
@ -265,7 +265,7 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() {
t := suite.T() t := suite.T()
s1 := stubScope("") s1 := stubScope("")
s2 := stubScope("") s2 := stubScope("")
s2[scopeKeyCategory] = filterize(unknownCatStub.String()) s2[scopeKeyCategory] = filterize(scopeConfig{}, unknownCatStub.String())
result := scopesByCategory[mockScope]( result := scopesByCategory[mockScope](
[]scope{scope(s1), scope(s2)}, []scope{scope(s1), scope(s2)},
map[path.CategoryType]mockCategorizer{ map[path.CategoryType]mockCategorizer{
@ -368,8 +368,8 @@ func (suite *SelectorScopesSuite) TestMatchesPathValues() {
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
sc := stubScope("") sc := stubScope("")
sc[rootCatStub.String()] = filterize(test.rootVal) sc[rootCatStub.String()] = filterize(scopeConfig{}, test.rootVal)
sc[leafCatStub.String()] = filterize(test.leafVal) sc[leafCatStub.String()] = filterize(scopeConfig{}, test.leafVal)
test.expect(t, matchesPathValues(sc, cat, pvs, test.shortRef)) test.expect(t, matchesPathValues(sc, cat, pvs, test.shortRef))
}) })
@ -486,3 +486,30 @@ func (suite *SelectorScopesSuite) TestWrapFilter() {
}) })
} }
} }
func (suite *SelectorScopesSuite) TestScopeConfig() {
input := "input"
table := []struct {
name string
config scopeConfig
expect int
}{
{
name: "no configs set",
config: scopeConfig{},
expect: int(filters.EqualTo),
},
{
name: "force prefix",
config: scopeConfig{usePrefixFilter: true},
expect: int(filters.TargetPrefixes),
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
result := filterize(test.config, input)
assert.Equal(t, test.expect, int(result.Comparator))
})
}
}

View File

@ -309,12 +309,23 @@ func addToSet(set []string, v []string) []string {
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Destination // helpers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
type Destination scope type scopeConfig struct {
usePrefixFilter bool
}
var ErrorDestinationAlreadySet = errors.New("destination is already declared") type option func(*scopeConfig)
// PrefixMatch ensures the selector uses a Prefix comparator, instead
// of contains or equals. Will not override a default Any() or None()
// comparator.
func PrefixMatch() option {
return func(sc *scopeConfig) {
sc.usePrefixFilter = true
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// helpers // helpers
@ -324,10 +335,6 @@ func badCastErr(cast, is service) error {
return errors.Wrapf(ErrorBadSelectorCast, "%s service is not %s", cast, is) return errors.Wrapf(ErrorBadSelectorCast, "%s service is not %s", cast, is)
} }
func existingDestinationErr(category, is string) error {
return errors.Wrapf(ErrorDestinationAlreadySet, "%s destination already set to %s", category, is)
}
func join(s ...string) string { func join(s ...string) string {
return strings.Join(s, delimiter) return strings.Join(s, delimiter)
} }
@ -362,20 +369,25 @@ func clean(s []string) []string {
// filterize turns the slice into a filter. // filterize turns the slice into a filter.
// if the input is Any(), returns a passAny filter. // if the input is Any(), returns a passAny filter.
// if the input is None(), returns a failAny filter. // if the input is None(), returns a failAny filter.
// if the scopeConfig specifies a filter, use that filter.
// if the input is len(1), returns an Equals filter. // if the input is len(1), returns an Equals filter.
// otherwise returns a Contains filter. // otherwise returns a Contains filter.
func filterize(s ...string) filters.Filter { func filterize(sc scopeConfig, s ...string) filters.Filter {
s = clean(s) s = clean(s)
if len(s) == 0 || s[0] == NoneTgt {
return failAny
}
if s[0] == AnyTgt {
return passAny
}
if sc.usePrefixFilter {
return filters.Prefix(join(s...))
}
if len(s) == 1 { if len(s) == 1 {
if s[0] == AnyTgt {
return passAny
}
if s[0] == NoneTgt {
return failAny
}
return filters.Equal(s[0]) return filters.Equal(s[0])
} }

View File

@ -192,6 +192,23 @@ func (suite *SelectorReduceSuite) TestReduce() {
}, },
expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]}, expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]},
}, },
// TODO (keepers): all folders are treated as prefix-matches at this time.
// so this test actually does nothing different. In the future, we'll
// need to amend the non-prefix folder tests to expect non-prefix matches.
{
name: "ExchangeMailByFolderPrefix",
selFunc: func() selectors.Reducer {
sel := selectors.NewExchangeRestore()
sel.Include(sel.MailFolders(
selectors.Any(),
[]string{testdata.ExchangeEmailBasePath.Folder()},
selectors.PrefixMatch(), // force prefix matching
))
return sel
},
expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]},
},
{ {
name: "ExchangeMailByFolderRoot", name: "ExchangeMailByFolderRoot",
selFunc: func() selectors.Reducer { selFunc: func() selectors.Reducer {

View File

@ -29,11 +29,6 @@ func (suite *SelectorSuite) TestBadCastErr() {
assert.Error(suite.T(), err) assert.Error(suite.T(), err)
} }
func (suite *SelectorSuite) TestExistingDestinationErr() {
err := existingDestinationErr("foo", "bar")
assert.Error(suite.T(), err)
}
func (suite *SelectorSuite) TestPrintable() { func (suite *SelectorSuite) TestPrintable() {
t := suite.T() t := suite.T()
@ -57,7 +52,7 @@ func (suite *SelectorSuite) TestPrintable_IncludedResources() {
stubWithResource := func(resource string) scope { stubWithResource := func(resource string) scope {
ss := stubScope("") ss := stubScope("")
ss[rootCatStub.String()] = filterize(resource) ss[rootCatStub.String()] = filterize(scopeConfig{}, resource)
return scope(ss) return scope(ss)
} }
@ -102,8 +97,8 @@ func (suite *SelectorSuite) TestToResourceTypeMap() {
input: []scope{ input: []scope{
scope(stubScope("")), scope(stubScope("")),
{ {
rootCatStub.String(): filterize("smarf"), rootCatStub.String(): filterize(scopeConfig{}, "smarf"),
scopeKeyDataType: filterize(unknownCatStub.String()), scopeKeyDataType: filterize(scopeConfig{}, unknownCatStub.String()),
}, },
}, },
expect: map[string][]string{ expect: map[string][]string{
@ -116,8 +111,8 @@ func (suite *SelectorSuite) TestToResourceTypeMap() {
input: []scope{ input: []scope{
scope(stubScope("")), scope(stubScope("")),
{ {
rootCatStub.String(): filterize(AnyTgt), rootCatStub.String(): filterize(scopeConfig{}, AnyTgt),
scopeKeyDataType: filterize("other"), scopeKeyDataType: filterize(scopeConfig{}, "other"),
}, },
}, },
expect: map[string][]string{ expect: map[string][]string{
@ -138,9 +133,9 @@ func (suite *SelectorSuite) TestContains() {
key := rootCatStub key := rootCatStub
target := "fnords" target := "fnords"
does := stubScope("") does := stubScope("")
does[key.String()] = filterize(target) does[key.String()] = filterize(scopeConfig{}, target)
doesNot := stubScope("") doesNot := stubScope("")
doesNot[key.String()] = filterize("smarf") doesNot[key.String()] = filterize(scopeConfig{}, "smarf")
assert.True(t, matches(does, key, target), "does contain") assert.True(t, matches(does, key, target), "does contain")
assert.False(t, matches(doesNot, key, target), "does not contain") assert.False(t, matches(doesNot, key, target), "does not contain")