refactor scope filtering (#555)
Scope filtering is currently hardcoded to the exchange use case. In order for future work to rely on boilerplate rather than re-writing the full filtering logic on each new type, as much of that code as is possible has been moved into a generic toolset.
This commit is contained in:
parent
7d057dd2ac
commit
12dbfce6d6
@ -480,7 +480,7 @@ func (ec exchangeCategory) includesType(cat categorizer) bool {
|
|||||||
// => {exchUser: userID, exchMailFolder: mailFolder, exchMail: mailID}
|
// => {exchUser: userID, exchMailFolder: mailFolder, exchMail: mailID}
|
||||||
func (ec exchangeCategory) pathValues(path []string) map[categorizer]string {
|
func (ec exchangeCategory) pathValues(path []string) map[categorizer]string {
|
||||||
m := map[categorizer]string{}
|
m := map[categorizer]string{}
|
||||||
if len(path) < 4 {
|
if len(path) < 2 {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
m[ExchangeUser] = path[1]
|
m[ExchangeUser] = path[1]
|
||||||
@ -528,7 +528,7 @@ func (ec exchangeCategory) pathKeys() []categorizer {
|
|||||||
type ExchangeScope scope
|
type ExchangeScope scope
|
||||||
|
|
||||||
// interface compliance checks
|
// interface compliance checks
|
||||||
// var _ scoper = &ExchangeScope{}
|
var _ scoper = &ExchangeScope{}
|
||||||
|
|
||||||
// Category describes the type of the data in scope.
|
// Category describes the type of the data in scope.
|
||||||
func (s ExchangeScope) Category() exchangeCategory {
|
func (s ExchangeScope) Category() exchangeCategory {
|
||||||
@ -541,8 +541,15 @@ func (s ExchangeScope) categorizer() categorizer {
|
|||||||
return s.Category()
|
return s.Category()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filer describes the specific filter, and its target values.
|
// Contains returns true if the category is included in the scope's
|
||||||
func (s ExchangeScope) Filter() exchangeCategory {
|
// data type, and the target string is included in the scope.
|
||||||
|
func (s ExchangeScope) Contains(cat exchangeCategory, target string) bool {
|
||||||
|
return contains(s, cat, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterCategory returns the category enum of the scope filter.
|
||||||
|
// If the scope is not a filter type, returns ExchangeUnknownCategory.
|
||||||
|
func (s ExchangeScope) FilterCategory() exchangeCategory {
|
||||||
return exchangeCatAtoI(s[scopeKeyInfoFilter])
|
return exchangeCatAtoI(s[scopeKeyInfoFilter])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,20 +559,13 @@ func (s ExchangeScope) Granularity() string {
|
|||||||
return s[scopeKeyGranularity]
|
return s[scopeKeyGranularity]
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncludeCategory checks whether the scope includes a
|
// IncludeCategory checks whether the scope includes a certain category of data.
|
||||||
// certain category of data.
|
|
||||||
// Ex: to check if the scope includes mail data:
|
// Ex: to check if the scope includes mail data:
|
||||||
// s.IncludesCategory(selector.ExchangeMail)
|
// s.IncludesCategory(selector.ExchangeMail)
|
||||||
func (s ExchangeScope) IncludesCategory(cat exchangeCategory) bool {
|
func (s ExchangeScope) IncludesCategory(cat exchangeCategory) bool {
|
||||||
return s.Category().isType(cat)
|
return s.Category().isType(cat)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains returns true if the category is included in the scope's
|
|
||||||
// data type, and the target string is included in the scope.
|
|
||||||
func (s ExchangeScope) Contains(cat exchangeCategory, target string) bool {
|
|
||||||
return contains(s, cat, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns true if the category is included in the scope's data type,
|
// returns true if the category is included in the scope's data type,
|
||||||
// and the value is set to Any().
|
// and the value is set to Any().
|
||||||
func (s ExchangeScope) IsAny(cat exchangeCategory) bool {
|
func (s ExchangeScope) IsAny(cat exchangeCategory) bool {
|
||||||
@ -606,31 +606,39 @@ func (s ExchangeScope) setDefaults() {
|
|||||||
// Backup Details Filtering
|
// Backup Details Filtering
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Reduce filters the entries in a details struct to only those that match the
|
||||||
|
// inclusions, filters, and exclusions in the selector.
|
||||||
|
func (s exchange) Reduce(deets *details.Details) *details.Details {
|
||||||
|
return reduce[ExchangeScope](
|
||||||
|
deets,
|
||||||
|
s.Selector,
|
||||||
|
map[pathType]exchangeCategory{
|
||||||
|
exchangeContactPath: ExchangeContact,
|
||||||
|
exchangeEventPath: ExchangeEvent,
|
||||||
|
exchangeMailPath: ExchangeMail,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// matchesEntry returns true if either the path or the info in the exchangeEntry matches the scope details.
|
// matchesEntry returns true if either the path or the info in the exchangeEntry matches the scope details.
|
||||||
func (s ExchangeScope) matchesEntry(
|
func (s ExchangeScope) matchesEntry(
|
||||||
cat categorizer,
|
cat categorizer,
|
||||||
pathValues map[categorizer]string,
|
pathValues map[categorizer]string,
|
||||||
entry details.DetailsEntry,
|
entry details.DetailsEntry,
|
||||||
) bool {
|
) bool {
|
||||||
return false
|
// matchesPathValues can be handled generically, thanks to SCIENCE.
|
||||||
// TODO: uncomment when reducer is added.
|
return matchesPathValues(s, cat, pathValues) || s.matchesInfo(entry.Exchange)
|
||||||
// return matchesPathValues(s, cat, pathValues) || s.matchesInfo(entry.Exchange)
|
|
||||||
}
|
|
||||||
|
|
||||||
// matches returns true if either the path or the info matches the scope details.
|
|
||||||
func (s ExchangeScope) matches(cat exchangeCategory, path []string, info *details.ExchangeInfo) bool {
|
|
||||||
return s.matchesPath(cat, path) || s.matchesInfo(cat, info)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchesInfo handles the standard behavior when comparing a scope and an exchangeInfo
|
// matchesInfo handles the standard behavior when comparing a scope and an exchangeInfo
|
||||||
// returns true if the scope and info match for the provided category.
|
// returns true if the scope and info match for the provided category.
|
||||||
func (s ExchangeScope) matchesInfo(cat exchangeCategory, info *details.ExchangeInfo) bool {
|
func (s ExchangeScope) matchesInfo(info *details.ExchangeInfo) bool {
|
||||||
// we need values to match against
|
// we need values to match against
|
||||||
if info == nil {
|
if info == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// the scope must define targets to match on
|
// the scope must define targets to match on
|
||||||
filterCat := s.Filter()
|
filterCat := s.FilterCategory()
|
||||||
targets := s.Get(filterCat)
|
targets := s.Get(filterCat)
|
||||||
if len(targets) == 0 {
|
if len(targets) == 0 {
|
||||||
return false
|
return false
|
||||||
@ -664,152 +672,3 @@ func (s ExchangeScope) matchesInfo(cat exchangeCategory, info *details.ExchangeI
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchesPath handles the standard behavior when comparing a scope and a path
|
|
||||||
// returns true if the scope and path match for the provided category.
|
|
||||||
func (s ExchangeScope) matchesPath(cat exchangeCategory, path []string) bool {
|
|
||||||
pathIDs := cat.pathValues(path)
|
|
||||||
for _, c := range categoryPathSet[cat] {
|
|
||||||
target := s.Get(c.(exchangeCategory))
|
|
||||||
// the scope must define the targets to match on
|
|
||||||
if len(target) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// None() fails all matches
|
|
||||||
if target[0] == NoneTgt {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// the path must contain a value to match against
|
|
||||||
id, ok := pathIDs[c]
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// all parts of the scope must match
|
|
||||||
isAny := target[0] == AnyTgt
|
|
||||||
if !isAny {
|
|
||||||
if !common.ContainsString(target, id) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Restore Point Filtering
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Reduce reduces the entries in a backupDetails struct to only
|
|
||||||
// those that match the inclusions, filters, and exclusions in the selector.
|
|
||||||
func (sr *ExchangeRestore) Reduce(deets *details.Details) *details.Details {
|
|
||||||
if deets == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entExcs := exchangeScopesByCategory(sr.Excludes)
|
|
||||||
entFilt := exchangeScopesByCategory(sr.Filters)
|
|
||||||
entIncs := exchangeScopesByCategory(sr.Includes)
|
|
||||||
|
|
||||||
ents := []details.DetailsEntry{}
|
|
||||||
|
|
||||||
for _, ent := range deets.Entries {
|
|
||||||
// todo: use Path pkg for this
|
|
||||||
path := strings.Split(ent.RepoRef, "/")
|
|
||||||
// not all paths will be len=3. Most should be longer.
|
|
||||||
// This just protects us from panicing four lines later.
|
|
||||||
if len(path) < 3 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var cat exchangeCategory
|
|
||||||
switch path[2] {
|
|
||||||
case "contact":
|
|
||||||
cat = ExchangeContact
|
|
||||||
case "event":
|
|
||||||
cat = ExchangeEvent
|
|
||||||
case "mail":
|
|
||||||
cat = ExchangeMail
|
|
||||||
}
|
|
||||||
matched := matchExchangeEntry(
|
|
||||||
cat,
|
|
||||||
path,
|
|
||||||
ent.Exchange,
|
|
||||||
entExcs[cat.String()],
|
|
||||||
entFilt[cat.String()],
|
|
||||||
entIncs[cat.String()])
|
|
||||||
if matched {
|
|
||||||
ents = append(ents, ent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deets.Entries = ents
|
|
||||||
return deets
|
|
||||||
}
|
|
||||||
|
|
||||||
// groups each scope by its category of data (contact, event, or mail).
|
|
||||||
// user-level scopes will duplicate to all three categories.
|
|
||||||
func exchangeScopesByCategory(scopes []scope) map[string][]ExchangeScope {
|
|
||||||
m := map[string][]ExchangeScope{
|
|
||||||
ExchangeContact.String(): {},
|
|
||||||
ExchangeEvent.String(): {},
|
|
||||||
ExchangeMail.String(): {},
|
|
||||||
}
|
|
||||||
for _, msc := range scopes {
|
|
||||||
sc := ExchangeScope(msc)
|
|
||||||
if sc.IncludesCategory(ExchangeContact) {
|
|
||||||
m[ExchangeContact.String()] = append(m[ExchangeContact.String()], sc)
|
|
||||||
}
|
|
||||||
if sc.IncludesCategory(ExchangeEvent) {
|
|
||||||
m[ExchangeEvent.String()] = append(m[ExchangeEvent.String()], sc)
|
|
||||||
}
|
|
||||||
if sc.IncludesCategory(ExchangeMail) {
|
|
||||||
m[ExchangeMail.String()] = append(m[ExchangeMail.String()], sc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare each path to the included and excluded exchange scopes. Returns true
|
|
||||||
// if the path is included, passes filters, and not excluded.
|
|
||||||
func matchExchangeEntry(
|
|
||||||
cat exchangeCategory,
|
|
||||||
path []string,
|
|
||||||
info *details.ExchangeInfo,
|
|
||||||
excs, filts, incs []ExchangeScope,
|
|
||||||
) bool {
|
|
||||||
// a passing match requires either a filter or an inclusion
|
|
||||||
if len(incs)+len(filts) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip this check if 0 inclusions were populated
|
|
||||||
// since filters act as the inclusion check in that case
|
|
||||||
if len(incs) > 0 {
|
|
||||||
// at least one inclusion must apply.
|
|
||||||
var included bool
|
|
||||||
for _, inc := range incs {
|
|
||||||
if inc.matches(cat, path, info) {
|
|
||||||
included = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !included {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// all filters must pass
|
|
||||||
for _, filt := range filts {
|
|
||||||
if !filt.matches(cat, path, info) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// any matching exclusion means failure
|
|
||||||
for _, exc := range excs {
|
|
||||||
if exc.matches(cat, path, info) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
@ -67,7 +67,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Contacts() {
|
|||||||
|
|
||||||
sel.Exclude(sel.Contacts([]string{user}, []string{folder}, []string{c1, c2}))
|
sel.Exclude(sel.Contacts([]string{user}, []string{folder}, []string{c1, c2}))
|
||||||
scopes := sel.Excludes
|
scopes := sel.Excludes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -88,7 +88,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Contacts() {
|
|||||||
|
|
||||||
sel.Include(sel.Contacts([]string{user}, []string{folder}, []string{c1, c2}))
|
sel.Include(sel.Contacts([]string{user}, []string{folder}, []string{c1, c2}))
|
||||||
scopes := sel.Includes
|
scopes := sel.Includes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -110,7 +110,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_ContactFolders()
|
|||||||
|
|
||||||
sel.Exclude(sel.ContactFolders([]string{user}, []string{f1, f2}))
|
sel.Exclude(sel.ContactFolders([]string{user}, []string{f1, f2}))
|
||||||
scopes := sel.Excludes
|
scopes := sel.Excludes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -130,7 +130,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_ContactFolders()
|
|||||||
|
|
||||||
sel.Include(sel.ContactFolders([]string{user}, []string{f1, f2}))
|
sel.Include(sel.ContactFolders([]string{user}, []string{f1, f2}))
|
||||||
scopes := sel.Includes
|
scopes := sel.Includes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -152,7 +152,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Events() {
|
|||||||
|
|
||||||
sel.Exclude(sel.Events([]string{user}, []string{e1, e2}))
|
sel.Exclude(sel.Events([]string{user}, []string{e1, e2}))
|
||||||
scopes := sel.Excludes
|
scopes := sel.Excludes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -171,7 +171,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Events() {
|
|||||||
|
|
||||||
sel.Include(sel.Events([]string{user}, []string{e1, e2}))
|
sel.Include(sel.Events([]string{user}, []string{e1, e2}))
|
||||||
scopes := sel.Includes
|
scopes := sel.Includes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -193,7 +193,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Mails() {
|
|||||||
|
|
||||||
sel.Exclude(sel.Mails([]string{user}, []string{folder}, []string{m1, m2}))
|
sel.Exclude(sel.Mails([]string{user}, []string{folder}, []string{m1, m2}))
|
||||||
scopes := sel.Excludes
|
scopes := sel.Excludes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -214,7 +214,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Mails() {
|
|||||||
|
|
||||||
sel.Include(sel.Mails([]string{user}, []string{folder}, []string{m1, m2}))
|
sel.Include(sel.Mails([]string{user}, []string{folder}, []string{m1, m2}))
|
||||||
scopes := sel.Includes
|
scopes := sel.Includes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -236,7 +236,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_MailFolders() {
|
|||||||
|
|
||||||
sel.Exclude(sel.MailFolders([]string{user}, []string{f1, f2}))
|
sel.Exclude(sel.MailFolders([]string{user}, []string{f1, f2}))
|
||||||
scopes := sel.Excludes
|
scopes := sel.Excludes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -256,7 +256,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_MailFolders() {
|
|||||||
|
|
||||||
sel.Include(sel.MailFolders([]string{user}, []string{f1, f2}))
|
sel.Include(sel.MailFolders([]string{user}, []string{f1, f2}))
|
||||||
scopes := sel.Includes
|
scopes := sel.Includes
|
||||||
require.Equal(t, 1, len(scopes))
|
require.Len(t, scopes, 1)
|
||||||
|
|
||||||
scope := scopes[0]
|
scope := scopes[0]
|
||||||
assert.Equal(t, scope[ExchangeUser.String()], user)
|
assert.Equal(t, scope[ExchangeUser.String()], user)
|
||||||
@ -277,7 +277,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Exclude_Users() {
|
|||||||
|
|
||||||
sel.Exclude(sel.Users([]string{u1, u2}))
|
sel.Exclude(sel.Users([]string{u1, u2}))
|
||||||
scopes := sel.Excludes
|
scopes := sel.Excludes
|
||||||
require.Equal(t, 6, len(scopes))
|
require.Len(t, scopes, 6)
|
||||||
|
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
assert.Contains(t, join(u1, u2), scope[ExchangeUser.String()])
|
assert.Contains(t, join(u1, u2), scope[ExchangeUser.String()])
|
||||||
@ -306,7 +306,7 @@ func (suite *ExchangeSourceSuite) TestExchangeSelector_Include_Users() {
|
|||||||
|
|
||||||
sel.Include(sel.Users([]string{u1, u2}))
|
sel.Include(sel.Users([]string{u1, u2}))
|
||||||
scopes := sel.Includes
|
scopes := sel.Includes
|
||||||
require.Equal(t, 6, len(scopes))
|
require.Len(t, scopes, 6)
|
||||||
|
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
assert.Contains(t, join(u1, u2), scope[ExchangeUser.String()])
|
assert.Contains(t, join(u1, u2), scope[ExchangeUser.String()])
|
||||||
@ -531,7 +531,7 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_MatchesInfo() {
|
|||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
scopes := setScopesToDefault(test.scope)
|
scopes := setScopesToDefault(test.scope)
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
test.expect(t, scope.matchesInfo(scope.Category(), info))
|
test.expect(t, scope.matchesInfo(info))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -574,7 +574,8 @@ func (suite *ExchangeSourceSuite) TestExchangeScope_MatchesPath() {
|
|||||||
scopes := setScopesToDefault(test.scope)
|
scopes := setScopesToDefault(test.scope)
|
||||||
var aMatch bool
|
var aMatch bool
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
if scope.matchesPath(ExchangeMail, path) {
|
pv := ExchangeMail.pathValues(path)
|
||||||
|
if matchesPathValues(scope, ExchangeMail, pv) {
|
||||||
aMatch = true
|
aMatch = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -781,7 +782,7 @@ func (suite *ExchangeSourceSuite) TestExchangeRestore_Reduce() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() {
|
func (suite *ExchangeSourceSuite) TestScopesByCategory() {
|
||||||
var (
|
var (
|
||||||
es = NewExchangeRestore()
|
es = NewExchangeRestore()
|
||||||
users = es.Users(Any())
|
users = es.Users(Any())
|
||||||
@ -804,6 +805,11 @@ func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() {
|
|||||||
}
|
}
|
||||||
return mss
|
return mss
|
||||||
}
|
}
|
||||||
|
cats := map[pathType]exchangeCategory{
|
||||||
|
exchangeContactPath: ExchangeContact,
|
||||||
|
exchangeEventPath: ExchangeEvent,
|
||||||
|
exchangeMailPath: ExchangeMail,
|
||||||
|
}
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
scopes input
|
scopes input
|
||||||
@ -817,16 +823,16 @@ func (suite *ExchangeSourceSuite) TestExchangeScopesByCategory() {
|
|||||||
}
|
}
|
||||||
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) {
|
||||||
result := exchangeScopesByCategory(test.scopes)
|
result := scopesByCategory[ExchangeScope](test.scopes, cats)
|
||||||
assert.Equal(t, test.expect.contact, len(result[ExchangeContact.String()]))
|
assert.Len(t, result[ExchangeContact], test.expect.contact)
|
||||||
assert.Equal(t, test.expect.event, len(result[ExchangeEvent.String()]))
|
assert.Len(t, result[ExchangeEvent], test.expect.event)
|
||||||
assert.Equal(t, test.expect.mail, len(result[ExchangeMail.String()]))
|
assert.Len(t, result[ExchangeMail], test.expect.mail)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() {
|
func (suite *ExchangeSourceSuite) TestPasses() {
|
||||||
var exchangeInfo *details.ExchangeInfo
|
deets := details.DetailsEntry{}
|
||||||
const (
|
const (
|
||||||
mid = "mailID"
|
mid = "mailID"
|
||||||
cat = ExchangeMail
|
cat = ExchangeMail
|
||||||
@ -867,7 +873,14 @@ func (suite *ExchangeSourceSuite) TestMatchExchangeEntry() {
|
|||||||
}
|
}
|
||||||
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) {
|
||||||
test.expect(t, matchExchangeEntry(cat, path, exchangeInfo, test.excludes, test.filters, test.includes))
|
result := passes(
|
||||||
|
cat,
|
||||||
|
cat.pathValues(path),
|
||||||
|
deets,
|
||||||
|
test.excludes,
|
||||||
|
test.filters,
|
||||||
|
test.includes)
|
||||||
|
test.expect(t, result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1049,7 +1062,7 @@ func (suite *ExchangeSourceSuite) TestExchangeCategory_PathValues() {
|
|||||||
expect map[categorizer]string
|
expect map[categorizer]string
|
||||||
}{
|
}{
|
||||||
{ExchangeCategoryUnknown, nil, map[categorizer]string{}},
|
{ExchangeCategoryUnknown, nil, map[categorizer]string{}},
|
||||||
{ExchangeCategoryUnknown, []string{"a", "b", "c"}, map[categorizer]string{}},
|
{ExchangeCategoryUnknown, []string{"a"}, map[categorizer]string{}},
|
||||||
{ExchangeContact, contactPath, contactMap},
|
{ExchangeContact, contactPath, contactMap},
|
||||||
{ExchangeEvent, eventPath, eventMap},
|
{ExchangeEvent, eventPath, eventMap},
|
||||||
{ExchangeMail, mailPath, mailMap},
|
{ExchangeMail, mailPath, mailMap},
|
||||||
|
|||||||
@ -45,13 +45,12 @@ func (sc mockCategorizer) pathKeys() []categorizer {
|
|||||||
return []categorizer{rootCatStub, leafCatStub}
|
return []categorizer{rootCatStub, leafCatStub}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Uncomment when reducer func is added
|
func stubPathValues() map[categorizer]string {
|
||||||
// func stubPathValues() map[categorizer]string {
|
return map[categorizer]string{
|
||||||
// return map[categorizer]string{
|
rootCatStub: rootCatStub.String(),
|
||||||
// rootCatStub: rootCatStub.String(),
|
leafCatStub: leafCatStub.String(),
|
||||||
// leafCatStub: leafCatStub.String(),
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// scopers
|
// scopers
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package selectors
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/internal/common"
|
||||||
"github.com/alcionai/corso/pkg/backup/details"
|
"github.com/alcionai/corso/pkg/backup/details"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,12 +41,11 @@ type (
|
|||||||
// so that the two can be compared.
|
// so that the two can be compared.
|
||||||
pathKeys() []categorizer
|
pathKeys() []categorizer
|
||||||
}
|
}
|
||||||
// TODO: Uncomment when reducer func is added
|
|
||||||
// categoryT is the generic type interface of a categorizer
|
// categoryT is the generic type interface of a categorizer
|
||||||
// categoryT interface {
|
categoryT interface {
|
||||||
// ~int
|
~int
|
||||||
// categorizer
|
categorizer
|
||||||
// }
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -157,3 +157,188 @@ func isAnyTarget[T scopeT](s T, cat categorizer) bool {
|
|||||||
}
|
}
|
||||||
return s[cat.String()] == AnyTgt
|
return s[cat.String()] == AnyTgt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reduce filters the entries in the details to only those that match the
|
||||||
|
// inclusions, filters, and exclusions in the selector.
|
||||||
|
//
|
||||||
|
func reduce[T scopeT, C categoryT](
|
||||||
|
deets *details.Details,
|
||||||
|
s Selector,
|
||||||
|
dataCategories map[pathType]C,
|
||||||
|
) *details.Details {
|
||||||
|
if deets == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// aggregate each scope type by category for easier isolation in future processing.
|
||||||
|
excludes := scopesByCategory[T](s.Excludes, dataCategories)
|
||||||
|
filters := scopesByCategory[T](s.Filters, dataCategories)
|
||||||
|
includes := scopesByCategory[T](s.Includes, dataCategories)
|
||||||
|
|
||||||
|
ents := []details.DetailsEntry{}
|
||||||
|
|
||||||
|
// for each entry, compare that entry against the scopes of the same data type
|
||||||
|
for _, ent := range deets.Entries {
|
||||||
|
// todo: use Path pkg for this
|
||||||
|
path := strings.Split(ent.RepoRef, "/")
|
||||||
|
dc, ok := dataCategories[pathTypeIn(path)]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
passed := passes(
|
||||||
|
dc,
|
||||||
|
dc.pathValues(path),
|
||||||
|
ent,
|
||||||
|
excludes[dc],
|
||||||
|
filters[dc],
|
||||||
|
includes[dc],
|
||||||
|
)
|
||||||
|
if passed {
|
||||||
|
ents = append(ents, ent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reduced := &details.Details{DetailsModel: deets.DetailsModel}
|
||||||
|
reduced.Entries = ents
|
||||||
|
return reduced
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is a hack. We don't want these values declared here- it will get
|
||||||
|
// unwieldy to have all of them for all services. They should be declared in
|
||||||
|
// paths, since that's where service- and data-type-specific assertions are owned.
|
||||||
|
type pathType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
unknownPathType pathType = iota
|
||||||
|
exchangeEventPath
|
||||||
|
exchangeContactPath
|
||||||
|
exchangeMailPath
|
||||||
|
)
|
||||||
|
|
||||||
|
// return the service data type of the path.
|
||||||
|
// TODO: this is a hack. We don't want this identification to occur in this
|
||||||
|
// package. It should get handled in paths, since that's where service- and
|
||||||
|
// data-type-specific assertions are owned.
|
||||||
|
// Ideally, we'd use something like path.DataType() instead of this func.
|
||||||
|
func pathTypeIn(path []string) pathType {
|
||||||
|
// not all paths will be len=3. Most should be longer.
|
||||||
|
// This just protects us from panicing below.
|
||||||
|
if len(path) < 3 {
|
||||||
|
return unknownPathType
|
||||||
|
}
|
||||||
|
switch path[2] {
|
||||||
|
case "mail":
|
||||||
|
return exchangeMailPath
|
||||||
|
case "contact":
|
||||||
|
return exchangeContactPath
|
||||||
|
case "event":
|
||||||
|
return exchangeEventPath
|
||||||
|
}
|
||||||
|
return unknownPathType
|
||||||
|
}
|
||||||
|
|
||||||
|
// groups each scope by its category of data (specified by the service-selector).
|
||||||
|
// ex: a slice containing the scopes [mail1, mail2, event1]
|
||||||
|
// would produce a map like { mail: [1, 2], event: [1] }
|
||||||
|
// so long as "mail" and "event" are contained in cats.
|
||||||
|
func scopesByCategory[T scopeT, C categoryT](
|
||||||
|
scopes []scope,
|
||||||
|
cats map[pathType]C,
|
||||||
|
) map[C][]T {
|
||||||
|
m := map[C][]T{}
|
||||||
|
for _, cat := range cats {
|
||||||
|
m[cat] = []T{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sc := range scopes {
|
||||||
|
for _, cat := range cats {
|
||||||
|
t := T(sc)
|
||||||
|
if t.categorizer().includesType(cat) {
|
||||||
|
m[cat] = append(m[cat], t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// passes compares each path to the included and excluded exchange scopes. Returns true
|
||||||
|
// if the path is included, passes filters, and not excluded.
|
||||||
|
func passes[T scopeT](
|
||||||
|
cat categorizer,
|
||||||
|
pathValues map[categorizer]string,
|
||||||
|
entry details.DetailsEntry,
|
||||||
|
excs, filts, incs []T,
|
||||||
|
) bool {
|
||||||
|
// a passing match requires either a filter or an inclusion
|
||||||
|
if len(incs)+len(filts) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip this check if 0 inclusions were populated
|
||||||
|
// since filters act as the inclusion check in that case
|
||||||
|
if len(incs) > 0 {
|
||||||
|
// at least one inclusion must apply.
|
||||||
|
var included bool
|
||||||
|
for _, inc := range incs {
|
||||||
|
if inc.matchesEntry(cat, pathValues, entry) {
|
||||||
|
included = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !included {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all filters must pass
|
||||||
|
for _, filt := range filts {
|
||||||
|
if !filt.matchesEntry(cat, pathValues, entry) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// any matching exclusion means failure
|
||||||
|
for _, exc := range excs {
|
||||||
|
if exc.matchesEntry(cat, pathValues, entry) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchesPathValues will check whether the pathValues have matching entries
|
||||||
|
// in the scope. The keys of the values to match against are identified by
|
||||||
|
// the categorizer.
|
||||||
|
// Standard expectations apply: None() or missing values always fail, Any()
|
||||||
|
// always succeeds.
|
||||||
|
func matchesPathValues[T scopeT](
|
||||||
|
sc T,
|
||||||
|
cat categorizer,
|
||||||
|
pathValues map[categorizer]string,
|
||||||
|
) bool {
|
||||||
|
for _, c := range cat.pathKeys() {
|
||||||
|
target := getCatValue(sc, c)
|
||||||
|
// the scope must define the targets to match on
|
||||||
|
if len(target) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// None() fails all matches
|
||||||
|
if target[0] == NoneTgt {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// the path must contain a value to match against
|
||||||
|
pv, ok := pathValues[c]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// all parts of the scope must match
|
||||||
|
if !isAnyTarget(sc, c) {
|
||||||
|
if !common.ContainsString(target, pv) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@ -4,7 +4,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/pkg/backup/details"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -115,3 +118,196 @@ func (suite *SelectorScopesSuite) TestIsAnyTarget() {
|
|||||||
assert.True(t, isAnyTarget(stub, rootCatStub))
|
assert.True(t, isAnyTarget(stub, rootCatStub))
|
||||||
assert.False(t, isAnyTarget(stub, leafCatStub))
|
assert.False(t, isAnyTarget(stub, leafCatStub))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reduceTestTable = []struct {
|
||||||
|
name string
|
||||||
|
sel func() Selector
|
||||||
|
expectLen int
|
||||||
|
expectPasses assert.BoolAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "include all",
|
||||||
|
sel: func() Selector {
|
||||||
|
sel := stubSelector()
|
||||||
|
sel.Filters = nil
|
||||||
|
sel.Excludes = nil
|
||||||
|
return sel
|
||||||
|
},
|
||||||
|
expectLen: 1,
|
||||||
|
expectPasses: assert.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "include none",
|
||||||
|
sel: func() Selector {
|
||||||
|
sel := stubSelector()
|
||||||
|
sel.Includes[0] = scope(stubScope("none"))
|
||||||
|
sel.Filters = nil
|
||||||
|
sel.Excludes = nil
|
||||||
|
return sel
|
||||||
|
},
|
||||||
|
expectLen: 0,
|
||||||
|
expectPasses: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter and include all",
|
||||||
|
sel: func() Selector {
|
||||||
|
sel := stubSelector()
|
||||||
|
sel.Excludes = nil
|
||||||
|
return sel
|
||||||
|
},
|
||||||
|
expectLen: 1,
|
||||||
|
expectPasses: assert.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "include all filter none",
|
||||||
|
sel: func() Selector {
|
||||||
|
sel := stubSelector()
|
||||||
|
sel.Filters[0] = scope(stubScope("none"))
|
||||||
|
sel.Excludes = nil
|
||||||
|
return sel
|
||||||
|
},
|
||||||
|
expectLen: 0,
|
||||||
|
expectPasses: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "include all exclude all",
|
||||||
|
sel: func() Selector {
|
||||||
|
sel := stubSelector()
|
||||||
|
sel.Filters = nil
|
||||||
|
return sel
|
||||||
|
},
|
||||||
|
expectLen: 0,
|
||||||
|
expectPasses: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "include all exclude none",
|
||||||
|
sel: func() Selector {
|
||||||
|
sel := stubSelector()
|
||||||
|
sel.Filters = nil
|
||||||
|
sel.Excludes[0] = scope(stubScope("none"))
|
||||||
|
return sel
|
||||||
|
},
|
||||||
|
expectLen: 1,
|
||||||
|
expectPasses: assert.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter all exclude all",
|
||||||
|
sel: func() Selector {
|
||||||
|
sel := stubSelector()
|
||||||
|
sel.Includes = nil
|
||||||
|
return sel
|
||||||
|
},
|
||||||
|
expectLen: 0,
|
||||||
|
expectPasses: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter all exclude none",
|
||||||
|
sel: func() Selector {
|
||||||
|
sel := stubSelector()
|
||||||
|
sel.Includes = nil
|
||||||
|
sel.Excludes[0] = scope(stubScope("none"))
|
||||||
|
return sel
|
||||||
|
},
|
||||||
|
expectLen: 1,
|
||||||
|
expectPasses: assert.True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SelectorScopesSuite) TestReduce() {
|
||||||
|
deets := func() details.Details {
|
||||||
|
return details.Details{
|
||||||
|
DetailsModel: details.DetailsModel{
|
||||||
|
Entries: []details.DetailsEntry{
|
||||||
|
{RepoRef: rootCatStub.String() + "/stub/" + leafCatStub.String()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataCats := map[pathType]mockCategorizer{
|
||||||
|
unknownPathType: rootCatStub,
|
||||||
|
}
|
||||||
|
for _, test := range reduceTestTable {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
ds := deets()
|
||||||
|
result := reduce[mockScope](&ds, test.sel(), dataCats)
|
||||||
|
require.NotNil(t, result)
|
||||||
|
assert.Len(t, result.Entries, test.expectLen)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SelectorScopesSuite) TestPathTypeIn() {
|
||||||
|
t := suite.T()
|
||||||
|
assert.Equal(t, unknownPathType, pathTypeIn([]string{}), "empty")
|
||||||
|
assert.Equal(t, exchangeMailPath, pathTypeIn([]string{"", "", "mail"}), "mail")
|
||||||
|
assert.Equal(t, exchangeContactPath, pathTypeIn([]string{"", "", "contact"}), "contact")
|
||||||
|
assert.Equal(t, exchangeEventPath, pathTypeIn([]string{"", "", "event"}), "event")
|
||||||
|
assert.Equal(t, unknownPathType, pathTypeIn([]string{"", "", "fnords"}), "bogus")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SelectorScopesSuite) TestScopesByCategory() {
|
||||||
|
t := suite.T()
|
||||||
|
s1 := stubScope("")
|
||||||
|
s2 := stubScope("")
|
||||||
|
s2[scopeKeyCategory] = unknownCatStub.String()
|
||||||
|
result := scopesByCategory[mockScope](
|
||||||
|
[]scope{scope(s1), scope(s2)},
|
||||||
|
map[pathType]mockCategorizer{
|
||||||
|
unknownPathType: rootCatStub,
|
||||||
|
})
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Len(t, result[rootCatStub], 1)
|
||||||
|
assert.Empty(t, result[leafCatStub])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SelectorScopesSuite) TestPasses() {
|
||||||
|
cat := rootCatStub
|
||||||
|
pathVals := map[categorizer]string{}
|
||||||
|
entry := details.DetailsEntry{}
|
||||||
|
for _, test := range reduceTestTable {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
sel := test.sel()
|
||||||
|
excl := toMockScope(sel.Excludes)
|
||||||
|
filt := toMockScope(sel.Filters)
|
||||||
|
incl := toMockScope(sel.Includes)
|
||||||
|
result := passes(
|
||||||
|
cat,
|
||||||
|
pathVals,
|
||||||
|
entry,
|
||||||
|
excl, filt, incl)
|
||||||
|
test.expectPasses(t, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toMockScope(sc []scope) []mockScope {
|
||||||
|
if len(sc) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ms := []mockScope{}
|
||||||
|
for _, s := range sc {
|
||||||
|
ms = append(ms, mockScope(s))
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SelectorScopesSuite) TestMatchesPathValues() {
|
||||||
|
t := suite.T()
|
||||||
|
cat := rootCatStub
|
||||||
|
sc := stubScope("")
|
||||||
|
sc[rootCatStub.String()] = rootCatStub.String()
|
||||||
|
sc[leafCatStub.String()] = leafCatStub.String()
|
||||||
|
pvs := stubPathValues()
|
||||||
|
assert.True(t, matchesPathValues(sc, cat, pvs), "matching values")
|
||||||
|
// "any" seems like it should pass, but this is the path value,
|
||||||
|
// not the scope value, so unless the scope is also "any", it fails.
|
||||||
|
pvs[rootCatStub] = AnyTgt
|
||||||
|
pvs[leafCatStub] = AnyTgt
|
||||||
|
assert.False(t, matchesPathValues(sc, cat, pvs), "any")
|
||||||
|
pvs[rootCatStub] = NoneTgt
|
||||||
|
pvs[leafCatStub] = NoneTgt
|
||||||
|
assert.False(t, matchesPathValues(sc, cat, pvs), "none")
|
||||||
|
pvs[rootCatStub] = "foo"
|
||||||
|
pvs[leafCatStub] = "bar"
|
||||||
|
assert.False(t, matchesPathValues(sc, cat, pvs), "mismatched values")
|
||||||
|
}
|
||||||
|
|||||||
@ -53,7 +53,7 @@ func (suite *SelectorSuite) TestPrintable_IncludedResources() {
|
|||||||
p := sel.Printable()
|
p := sel.Printable()
|
||||||
res := p.Resources()
|
res := p.Resources()
|
||||||
|
|
||||||
assert.Equal(t, stubResource, res, "resource should state only the user")
|
assert.Equal(t, stubResource, res, "resource should state only the stub")
|
||||||
|
|
||||||
sel.Includes = []scope{
|
sel.Includes = []scope{
|
||||||
scope(stubScope("")),
|
scope(stubScope("")),
|
||||||
@ -68,7 +68,7 @@ func (suite *SelectorSuite) TestPrintable_IncludedResources() {
|
|||||||
p.Includes = nil
|
p.Includes = nil
|
||||||
res = p.Resources()
|
res = p.Resources()
|
||||||
|
|
||||||
assert.Equal(t, stubResource, res, "resource on filters should state only the user")
|
assert.Equal(t, stubResource, res, "resource on filters should state only the stub")
|
||||||
|
|
||||||
p.Filters = nil
|
p.Filters = nil
|
||||||
res = p.Resources()
|
res = p.Resources()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user