prevent cross-contamination on filter reduce (#898)
## Description Over-restrictive scope correlation caused the reduce processor to only include filters which matched the data type of the scope. Ironically, this allowed a superset of information to match, by evading the _all-match_ expectations of filter scopes. Also replaces the data category consts inside /details with the path category types, since those are acting as better canonical owners of data type identification throughout the app. ## Type of change - [x] 🐛 Bugfix ## Issue(s) * #890 ## Test Plan - [x] 💪 Manual - [x] ⚡ Unit test
This commit is contained in:
parent
b2d3330db7
commit
41bb3ee6f9
@ -39,7 +39,7 @@ func (dm DetailsModel) PrintEntries(ctx context.Context) {
|
||||
}
|
||||
|
||||
func printTable(ctx context.Context, dm DetailsModel) {
|
||||
perType := map[itemType][]print.Printable{}
|
||||
perType := map[ItemType][]print.Printable{}
|
||||
|
||||
for _, de := range dm.Entries {
|
||||
it := de.infoType()
|
||||
@ -226,21 +226,21 @@ func (de DetailsEntry) Values() []string {
|
||||
return vs
|
||||
}
|
||||
|
||||
type itemType int
|
||||
type ItemType int
|
||||
|
||||
const (
|
||||
UnknownType itemType = iota
|
||||
UnknownType ItemType = iota
|
||||
|
||||
// separate each service by a factor of 100 for padding
|
||||
ExchangeContact
|
||||
ExchangeEvent
|
||||
ExchangeMail
|
||||
|
||||
SharepointItem itemType = iota + 100
|
||||
SharepointItem ItemType = iota + 100
|
||||
|
||||
OneDriveItem itemType = iota + 200
|
||||
OneDriveItem ItemType = iota + 200
|
||||
|
||||
FolderItem itemType = iota + 300
|
||||
FolderItem ItemType = iota + 300
|
||||
)
|
||||
|
||||
// ItemInfo is a oneOf that contains service specific
|
||||
@ -258,7 +258,7 @@ type ItemInfo struct {
|
||||
// infoType provides internal categorization for collecting like-typed ItemInfos.
|
||||
// It should return the most granular value type (ex: "event" for an exchange
|
||||
// calendar event).
|
||||
func (i ItemInfo) infoType() itemType {
|
||||
func (i ItemInfo) infoType() ItemType {
|
||||
switch {
|
||||
case i.Folder != nil:
|
||||
return i.Folder.ItemType
|
||||
@ -277,7 +277,7 @@ func (i ItemInfo) infoType() itemType {
|
||||
}
|
||||
|
||||
type FolderInfo struct {
|
||||
ItemType itemType
|
||||
ItemType ItemType `json:"itemType,omitempty"`
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
|
||||
@ -291,7 +291,7 @@ func (i FolderInfo) Values() []string {
|
||||
|
||||
// ExchangeInfo describes an exchange item
|
||||
type ExchangeInfo struct {
|
||||
ItemType itemType
|
||||
ItemType ItemType `json:"itemType,omitempty"`
|
||||
Sender string `json:"sender,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Received time.Time `json:"received,omitempty"`
|
||||
@ -344,7 +344,7 @@ func (i ExchangeInfo) Values() []string {
|
||||
// TODO: Implement this. This is currently here
|
||||
// just to illustrate usage
|
||||
type SharepointInfo struct {
|
||||
ItemType itemType
|
||||
ItemType ItemType `json:"itemType,omitempty"`
|
||||
}
|
||||
|
||||
// Headers returns the human-readable names of properties in a SharepointInfo
|
||||
@ -361,7 +361,7 @@ func (i SharepointInfo) Values() []string {
|
||||
|
||||
// OneDriveInfo describes a oneDrive item
|
||||
type OneDriveInfo struct {
|
||||
ItemType itemType
|
||||
ItemType ItemType `json:"itemType,omitempty"`
|
||||
ParentPath string `json:"parentPath"`
|
||||
ItemName string `json:"itemName"`
|
||||
}
|
||||
|
||||
@ -333,8 +333,8 @@ func (sr *ExchangeRestore) EventRecurs(recurs string) []ExchangeScope {
|
||||
func (sr *ExchangeRestore) EventStartsAfter(timeStrings string) []ExchangeScope {
|
||||
return []ExchangeScope{
|
||||
makeFilterScope[ExchangeScope](
|
||||
ExchangeMail,
|
||||
ExchangeFilterMailReceivedAfter,
|
||||
ExchangeEvent,
|
||||
ExchangeFilterEventStartsAfter,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Less)),
|
||||
}
|
||||
@ -347,8 +347,8 @@ func (sr *ExchangeRestore) EventStartsAfter(timeStrings string) []ExchangeScope
|
||||
func (sr *ExchangeRestore) EventStartsBefore(timeStrings string) []ExchangeScope {
|
||||
return []ExchangeScope{
|
||||
makeFilterScope[ExchangeScope](
|
||||
ExchangeMail,
|
||||
ExchangeFilterMailReceivedBefore,
|
||||
ExchangeEvent,
|
||||
ExchangeFilterEventStartsBefore,
|
||||
[]string{timeStrings},
|
||||
wrapFilter(filters.Greater)),
|
||||
}
|
||||
@ -690,25 +690,21 @@ func (s exchange) Reduce(ctx context.Context, deets *details.Details) *details.D
|
||||
)
|
||||
}
|
||||
|
||||
// matchesEntry returns true if either the path or the info in the exchangeEntry matches the scope details.
|
||||
func (s ExchangeScope) matchesEntry(
|
||||
cat categorizer,
|
||||
pathValues map[categorizer]string,
|
||||
entry details.DetailsEntry,
|
||||
) bool {
|
||||
// matchesPathValues can be handled generically, thanks to SCIENCE.
|
||||
return matchesPathValues(s, cat.(exchangeCategory), pathValues, entry.ShortRef) || s.matchesInfo(entry.Exchange)
|
||||
}
|
||||
|
||||
// matchesInfo handles the standard behavior when comparing a scope and an ExchangeFilter
|
||||
// returns true if the scope and info match for the provided category.
|
||||
func (s ExchangeScope) matchesInfo(info *details.ExchangeInfo) bool {
|
||||
// we need values to match against
|
||||
func (s ExchangeScope) matchesInfo(dii details.ItemInfo) bool {
|
||||
info := dii.Exchange
|
||||
if info == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
filterCat := s.FilterCategory()
|
||||
|
||||
cfpc := categoryFromItemType(info.ItemType)
|
||||
if !typeAndCategoryMatches(filterCat, cfpc) {
|
||||
return false
|
||||
}
|
||||
|
||||
i := ""
|
||||
|
||||
switch filterCat {
|
||||
@ -732,3 +728,19 @@ func (s ExchangeScope) matchesInfo(info *details.ExchangeInfo) bool {
|
||||
|
||||
return s.Matches(filterCat, i)
|
||||
}
|
||||
|
||||
// categoryFromItemType interprets the category represented by the ExchangeInfo
|
||||
// struct. Since every ExchangeInfo can hold all exchange data info, the exact
|
||||
// type that the struct represents must be compared using its ItemType prop.
|
||||
func categoryFromItemType(pct details.ItemType) exchangeCategory {
|
||||
switch pct {
|
||||
case details.ExchangeContact:
|
||||
return ExchangeContact
|
||||
case details.ExchangeMail:
|
||||
return ExchangeMail
|
||||
case details.ExchangeEvent:
|
||||
return ExchangeEvent
|
||||
}
|
||||
|
||||
return ExchangeCategoryUnknown
|
||||
}
|
||||
|
||||
@ -710,7 +710,12 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesInfo() {
|
||||
epoch = time.Time{}
|
||||
now = time.Now()
|
||||
future = now.Add(1 * time.Minute)
|
||||
info = &details.ExchangeInfo{
|
||||
)
|
||||
|
||||
infoWith := func(itype details.ItemType) details.ItemInfo {
|
||||
return details.ItemInfo{
|
||||
Exchange: &details.ExchangeInfo{
|
||||
ItemType: itype,
|
||||
ContactName: name,
|
||||
EventRecurs: true,
|
||||
EventStart: now,
|
||||
@ -718,54 +723,86 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesInfo() {
|
||||
Sender: sender,
|
||||
Subject: subject,
|
||||
Received: now,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
table := []struct {
|
||||
name string
|
||||
itype details.ItemType
|
||||
scope []ExchangeScope
|
||||
expect assert.BoolAssertionFunc
|
||||
}{
|
||||
{"any mail with a sender", es.MailSender(AnyTgt), assert.True},
|
||||
{"no mail, regardless of sender", es.MailSender(NoneTgt), assert.False},
|
||||
{"mail from a different sender", es.MailSender("magoo@ma.goo"), assert.False},
|
||||
{"mail from the matching sender", es.MailSender(sender), assert.True},
|
||||
{"mail with any subject", es.MailSubject(AnyTgt), assert.True},
|
||||
{"mail with none subject", es.MailSubject(NoneTgt), assert.False},
|
||||
{"mail with a different subject", es.MailSubject("fancy"), assert.False},
|
||||
{"mail with the matching subject", es.MailSubject(subject), assert.True},
|
||||
{"mail with a substring subject match", es.MailSubject(subject[5:9]), assert.True},
|
||||
{"mail received after the epoch", es.MailReceivedAfter(common.FormatTime(epoch)), assert.True},
|
||||
{"mail received after now", es.MailReceivedAfter(common.FormatTime(now)), assert.False},
|
||||
{"mail received after sometime later", es.MailReceivedAfter(common.FormatTime(future)), assert.False},
|
||||
{"mail received before the epoch", es.MailReceivedBefore(common.FormatTime(epoch)), assert.False},
|
||||
{"mail received before now", es.MailReceivedBefore(common.FormatTime(now)), assert.False},
|
||||
{"mail received before sometime later", es.MailReceivedBefore(common.FormatTime(future)), assert.True},
|
||||
{"event with any organizer", es.EventOrganizer(AnyTgt), assert.True},
|
||||
{"event with none organizer", es.EventOrganizer(NoneTgt), assert.False},
|
||||
{"event with a different organizer", es.EventOrganizer("fancy"), assert.False},
|
||||
{"event with the matching organizer", es.EventOrganizer(organizer), assert.True},
|
||||
{"event that recurs", es.EventRecurs("true"), assert.True},
|
||||
{"event that does not recur", es.EventRecurs("false"), assert.False},
|
||||
{"event starting after the epoch", es.EventStartsAfter(common.FormatTime(epoch)), assert.True},
|
||||
{"event starting after now", es.EventStartsAfter(common.FormatTime(now)), assert.False},
|
||||
{"event starting after sometime later", es.EventStartsAfter(common.FormatTime(future)), assert.False},
|
||||
{"event starting before the epoch", es.EventStartsBefore(common.FormatTime(epoch)), assert.False},
|
||||
{"event starting before now", es.EventStartsBefore(common.FormatTime(now)), assert.False},
|
||||
{"event starting before sometime later", es.EventStartsBefore(common.FormatTime(future)), assert.True},
|
||||
{"event with any subject", es.EventSubject(AnyTgt), assert.True},
|
||||
{"event with none subject", es.EventSubject(NoneTgt), assert.False},
|
||||
{"event with a different subject", es.EventSubject("fancy"), assert.False},
|
||||
{"event with the matching subject", es.EventSubject(subject), assert.True},
|
||||
{"contact with a different name", es.ContactName("blarps"), assert.False},
|
||||
{"contact with the same name", es.ContactName(name), assert.True},
|
||||
{"contact with a subname search", es.ContactName(name[2:5]), assert.True},
|
||||
{"any mail with a sender", details.ExchangeMail, es.MailSender(AnyTgt), assert.True},
|
||||
{"no mail, regardless of sender", details.ExchangeMail, es.MailSender(NoneTgt), assert.False},
|
||||
{"mail from a different sender", details.ExchangeMail, es.MailSender("magoo@ma.goo"), assert.False},
|
||||
{"mail from the matching sender", details.ExchangeMail, es.MailSender(sender), assert.True},
|
||||
{"mail with any subject", details.ExchangeMail, es.MailSubject(AnyTgt), assert.True},
|
||||
{"mail with none subject", details.ExchangeMail, es.MailSubject(NoneTgt), assert.False},
|
||||
{"mail with a different subject", details.ExchangeMail, es.MailSubject("fancy"), assert.False},
|
||||
{"mail with the matching subject", details.ExchangeMail, es.MailSubject(subject), assert.True},
|
||||
{"mail with a substring subject match", details.ExchangeMail, es.MailSubject(subject[5:9]), assert.True},
|
||||
{"mail received after the epoch", details.ExchangeMail, es.MailReceivedAfter(common.FormatTime(epoch)), assert.True},
|
||||
{"mail received after now", details.ExchangeMail, es.MailReceivedAfter(common.FormatTime(now)), assert.False},
|
||||
{
|
||||
"mail received after sometime later",
|
||||
details.ExchangeMail,
|
||||
es.MailReceivedAfter(common.FormatTime(future)),
|
||||
assert.False,
|
||||
},
|
||||
{
|
||||
"mail received before the epoch",
|
||||
details.ExchangeMail,
|
||||
es.MailReceivedBefore(common.FormatTime(epoch)),
|
||||
assert.False,
|
||||
},
|
||||
{"mail received before now", details.ExchangeMail, es.MailReceivedBefore(common.FormatTime(now)), assert.False},
|
||||
{
|
||||
"mail received before sometime later",
|
||||
details.ExchangeMail,
|
||||
es.MailReceivedBefore(common.FormatTime(future)),
|
||||
assert.True,
|
||||
},
|
||||
{"event with any organizer", details.ExchangeEvent, es.EventOrganizer(AnyTgt), assert.True},
|
||||
{"event with none organizer", details.ExchangeEvent, es.EventOrganizer(NoneTgt), assert.False},
|
||||
{"event with a different organizer", details.ExchangeEvent, es.EventOrganizer("fancy"), assert.False},
|
||||
{"event with the matching organizer", details.ExchangeEvent, es.EventOrganizer(organizer), assert.True},
|
||||
{"event that recurs", details.ExchangeEvent, es.EventRecurs("true"), assert.True},
|
||||
{"event that does not recur", details.ExchangeEvent, es.EventRecurs("false"), assert.False},
|
||||
{"event starting after the epoch", details.ExchangeEvent, es.EventStartsAfter(common.FormatTime(epoch)), assert.True},
|
||||
{"event starting after now", details.ExchangeEvent, es.EventStartsAfter(common.FormatTime(now)), assert.False},
|
||||
{
|
||||
"event starting after sometime later",
|
||||
details.ExchangeEvent,
|
||||
es.EventStartsAfter(common.FormatTime(future)),
|
||||
assert.False,
|
||||
},
|
||||
{
|
||||
"event starting before the epoch",
|
||||
details.ExchangeEvent,
|
||||
es.EventStartsBefore(common.FormatTime(epoch)),
|
||||
assert.False,
|
||||
},
|
||||
{"event starting before now", details.ExchangeEvent, es.EventStartsBefore(common.FormatTime(now)), assert.False},
|
||||
{
|
||||
"event starting before sometime later",
|
||||
details.ExchangeEvent,
|
||||
es.EventStartsBefore(common.FormatTime(future)),
|
||||
assert.True,
|
||||
},
|
||||
{"event with any subject", details.ExchangeEvent, es.EventSubject(AnyTgt), assert.True},
|
||||
{"event with none subject", details.ExchangeEvent, es.EventSubject(NoneTgt), assert.False},
|
||||
{"event with a different subject", details.ExchangeEvent, es.EventSubject("fancy"), assert.False},
|
||||
{"event with the matching subject", details.ExchangeEvent, es.EventSubject(subject), assert.True},
|
||||
{"contact with a different name", details.ExchangeContact, es.ContactName("blarps"), assert.False},
|
||||
{"contact with the same name", details.ExchangeContact, es.ContactName(name), assert.True},
|
||||
{"contact with a subname search", details.ExchangeContact, es.ContactName(name[2:5]), assert.True},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
scopes := setScopesToDefault(test.scope)
|
||||
for _, scope := range scopes {
|
||||
test.expect(t, scope.matchesInfo(info))
|
||||
test.expect(t, scope.matchesInfo(infoWith(test.itype)))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -864,6 +901,12 @@ func (suite *ExchangeSelectorSuite) TestIdPath() {
|
||||
}
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
|
||||
var (
|
||||
contact = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "cfld", "cid")
|
||||
event = stubRepoRef(path.ExchangeService, path.EventsCategory, "uid", "ecld", "eid")
|
||||
mail = stubRepoRef(path.ExchangeService, path.EmailCategory, "uid", "mfld", "mid")
|
||||
)
|
||||
|
||||
makeDeets := func(refs ...string) *details.Details {
|
||||
deets := &details.Details{
|
||||
DetailsModel: details.DetailsModel{
|
||||
@ -872,20 +915,30 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
|
||||
}
|
||||
|
||||
for _, r := range refs {
|
||||
itype := details.UnknownType
|
||||
|
||||
switch r {
|
||||
case contact:
|
||||
itype = details.ExchangeContact
|
||||
case event:
|
||||
itype = details.ExchangeEvent
|
||||
case mail:
|
||||
itype = details.ExchangeMail
|
||||
}
|
||||
|
||||
deets.Entries = append(deets.Entries, details.DetailsEntry{
|
||||
RepoRef: r,
|
||||
ItemInfo: details.ItemInfo{
|
||||
Exchange: &details.ExchangeInfo{
|
||||
ItemType: itype,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return deets
|
||||
}
|
||||
|
||||
var (
|
||||
contact = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "cfld", "cid")
|
||||
event = stubRepoRef(path.ExchangeService, path.EventsCategory, "uid", "ecld", "eid")
|
||||
mail = stubRepoRef(path.ExchangeService, path.EmailCategory, "uid", "mfld", "mid")
|
||||
)
|
||||
|
||||
arr := func(s ...string) []string {
|
||||
return s
|
||||
}
|
||||
@ -1009,6 +1062,44 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
|
||||
},
|
||||
arr(contact, event),
|
||||
},
|
||||
{
|
||||
"filter on mail subject",
|
||||
func() *details.Details {
|
||||
ds := makeDeets(mail)
|
||||
for i := range ds.Entries {
|
||||
ds.Entries[i].Exchange.Subject = "has a subject"
|
||||
}
|
||||
return ds
|
||||
}(),
|
||||
func() *ExchangeRestore {
|
||||
er := NewExchangeRestore()
|
||||
er.Include(er.Users(Any()))
|
||||
er.Filter(er.MailSubject("subj"))
|
||||
return er
|
||||
},
|
||||
arr(mail),
|
||||
},
|
||||
{
|
||||
"filter on mail subject multiple input categories",
|
||||
func() *details.Details {
|
||||
mds := makeDeets(mail)
|
||||
for i := range mds.Entries {
|
||||
mds.Entries[i].Exchange.Subject = "has a subject"
|
||||
}
|
||||
|
||||
ds := makeDeets(contact, event)
|
||||
ds.Entries = append(ds.Entries, mds.Entries...)
|
||||
|
||||
return ds
|
||||
}(),
|
||||
func() *ExchangeRestore {
|
||||
er := NewExchangeRestore()
|
||||
er.Include(er.Users(Any()))
|
||||
er.Filter(er.MailSubject("subj"))
|
||||
return er
|
||||
},
|
||||
arr(mail),
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
@ -1067,7 +1158,7 @@ func (suite *ExchangeSelectorSuite) TestScopesByCategory() {
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
result := scopesByCategory[ExchangeScope](test.scopes, cats)
|
||||
result := scopesByCategory[ExchangeScope](test.scopes, cats, false)
|
||||
assert.Len(t, result[ExchangeContact], test.expect.contact)
|
||||
assert.Len(t, result[ExchangeEvent], test.expect.event)
|
||||
assert.Len(t, result[ExchangeMail], test.expect.mail)
|
||||
@ -1288,3 +1379,38 @@ func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathKeys() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestCategoryFromItemType() {
|
||||
table := []struct {
|
||||
name string
|
||||
input details.ItemType
|
||||
expect exchangeCategory
|
||||
}{
|
||||
{
|
||||
name: "contact",
|
||||
input: details.ExchangeContact,
|
||||
expect: ExchangeContact,
|
||||
},
|
||||
{
|
||||
name: "event",
|
||||
input: details.ExchangeEvent,
|
||||
expect: ExchangeEvent,
|
||||
},
|
||||
{
|
||||
name: "mail",
|
||||
input: details.ExchangeMail,
|
||||
expect: ExchangeMail,
|
||||
},
|
||||
{
|
||||
name: "unknown",
|
||||
input: details.UnknownType,
|
||||
expect: ExchangeCategoryUnknown,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
result := categoryFromItemType(test.input)
|
||||
assert.Equal(t, test.expect, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +52,10 @@ func (mc mockCategorizer) isLeaf() bool {
|
||||
}
|
||||
|
||||
func (mc mockCategorizer) pathValues(pth path.Path) map[categorizer]string {
|
||||
return map[categorizer]string{rootCatStub: "stub"}
|
||||
return map[categorizer]string{
|
||||
rootCatStub: "root",
|
||||
leafCatStub: "leaf",
|
||||
}
|
||||
}
|
||||
|
||||
func (mc mockCategorizer) pathKeys() []categorizer {
|
||||
@ -86,11 +89,7 @@ func (ms mockScope) categorizer() categorizer {
|
||||
return unknownCatStub
|
||||
}
|
||||
|
||||
func (ms mockScope) matchesEntry(
|
||||
cat categorizer,
|
||||
pathValues map[categorizer]string,
|
||||
entry details.DetailsEntry,
|
||||
) bool {
|
||||
func (ms mockScope) matchesInfo(dii details.ItemInfo) bool {
|
||||
return ms[shouldMatch].Target == "true"
|
||||
}
|
||||
|
||||
@ -107,14 +106,27 @@ func stubScope(match string) mockScope {
|
||||
sm = match
|
||||
}
|
||||
|
||||
filt := passAny
|
||||
if match == "none" {
|
||||
filt = failAny
|
||||
}
|
||||
|
||||
return mockScope{
|
||||
rootCatStub.String(): passAny,
|
||||
rootCatStub.String(): filt,
|
||||
leafCatStub.String(): filt,
|
||||
scopeKeyCategory: filters.Identity(rootCatStub.String()),
|
||||
scopeKeyDataType: filters.Identity(rootCatStub.String()),
|
||||
shouldMatch: filters.Identity(sm),
|
||||
}
|
||||
}
|
||||
|
||||
func stubInfoScope(match string) mockScope {
|
||||
sc := stubScope(match)
|
||||
sc[scopeKeyInfoFilter] = filters.Identity("true")
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// selectors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -299,23 +299,14 @@ func (s OneDriveScope) setDefaults() {
|
||||
// no-op while no child scope types below user are identified
|
||||
}
|
||||
|
||||
// matchesEntry returns true if either the path or the info in the oneDriveEntry matches the scope details.
|
||||
func (s OneDriveScope) matchesEntry(
|
||||
cat categorizer,
|
||||
pathValues map[categorizer]string,
|
||||
entry details.DetailsEntry,
|
||||
) bool {
|
||||
// matchesPathValues can be handled generically, thanks to SCIENCE.
|
||||
return matchesPathValues(s, cat.(oneDriveCategory), pathValues, entry.ShortRef) || s.matchesInfo(entry.OneDrive)
|
||||
}
|
||||
|
||||
// matchesInfo handles the standard behavior when comparing a scope and an oneDriveInfo
|
||||
// returns true if the scope and info match for the provided category.
|
||||
func (s OneDriveScope) matchesInfo(info *details.OneDriveInfo) bool {
|
||||
// we need values to match against
|
||||
func (s OneDriveScope) matchesInfo(dii details.ItemInfo) bool {
|
||||
info := dii.OneDrive
|
||||
if info == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// the scope must define targets to match on
|
||||
filterCat := s.FilterCategory()
|
||||
targets := s.Get(filterCat)
|
||||
|
||||
@ -93,19 +93,14 @@ type (
|
||||
// internal to scopes.go can utilize the scope's category without the service context.
|
||||
categorizer() categorizer
|
||||
|
||||
// matchesEntry is used to determine if the scope values match with either the pathValues,
|
||||
// or the DetailsEntry for the given category.
|
||||
// The path comparison (using cat and pathValues) can be handled generically within
|
||||
// scopes.go. However, the entry comparison requires service-specific context in order
|
||||
// for the scope to extract the correct serviceInfo in the entry.
|
||||
// matchesInfo is used to determine if the scope values match a specific DetailsEntry
|
||||
// ItemInfo filter. Unlike path filtering, the entry comparison requires service-specific
|
||||
// context in order for the scope to extract the correct serviceInfo in the entry.
|
||||
//
|
||||
// Params:
|
||||
// cat - the category type expressed in the Path. Not the category of the Scope. If the
|
||||
// scope does not align with this parameter, the result is automatically false.
|
||||
// pathValues - the result of categorizer.pathValues() for the Path being checked.
|
||||
// entry - the details entry containing extended service info for the item that a filter may
|
||||
// compare. Identification of the correct entry Info service is left up to the scope.
|
||||
matchesEntry(cat categorizer, pathValues map[categorizer]string, entry details.DetailsEntry) bool
|
||||
// info - the details entry itemInfo containing extended service info that a filter may
|
||||
// compare. Identification of the correct entry Info service is left up to the fulfiller.
|
||||
matchesInfo(info details.ItemInfo) bool
|
||||
|
||||
// setDefaults populates default values for certain scope categories.
|
||||
// Primarily to ensure that root- or mid-tier scopes (such as folders)
|
||||
@ -220,9 +215,9 @@ func reduce[T scopeT, C categoryT](
|
||||
}
|
||||
|
||||
// aggregate each scope type by category for easier isolation in future processing.
|
||||
excls := scopesByCategory[T](s.Excludes, dataCategories)
|
||||
filts := scopesByCategory[T](s.Filters, dataCategories)
|
||||
incls := scopesByCategory[T](s.Includes, dataCategories)
|
||||
excls := scopesByCategory[T](s.Excludes, dataCategories, false)
|
||||
filts := scopesByCategory[T](s.Filters, dataCategories, true)
|
||||
incls := scopesByCategory[T](s.Includes, dataCategories, false)
|
||||
|
||||
ents := []details.DetailsEntry{}
|
||||
|
||||
@ -262,9 +257,12 @@ func reduce[T scopeT, C categoryT](
|
||||
// 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.
|
||||
// For ALL-mach requirements, scopes used as filters should force inclusion using
|
||||
// includeAll=true, independent of the category.
|
||||
func scopesByCategory[T scopeT, C categoryT](
|
||||
scopes []scope,
|
||||
cats map[path.CategoryType]C,
|
||||
includeAll bool,
|
||||
) map[C][]T {
|
||||
m := map[C][]T{}
|
||||
for _, cat := range cats {
|
||||
@ -274,7 +272,8 @@ func scopesByCategory[T scopeT, C categoryT](
|
||||
for _, sc := range scopes {
|
||||
for _, cat := range cats {
|
||||
t := T(sc)
|
||||
if typeAndCategoryMatches(cat, t.categorizer()) {
|
||||
// include a scope if the data category matches, or the caller forces inclusion.
|
||||
if includeAll || typeAndCategoryMatches(cat, t.categorizer()) {
|
||||
m[cat] = append(m[cat], t)
|
||||
}
|
||||
}
|
||||
@ -285,8 +284,8 @@ func scopesByCategory[T scopeT, C categoryT](
|
||||
|
||||
// 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,
|
||||
func passes[T scopeT, C categoryT](
|
||||
cat C,
|
||||
pathValues map[categorizer]string,
|
||||
entry details.DetailsEntry,
|
||||
excs, filts, incs []T,
|
||||
@ -303,7 +302,7 @@ func passes[T scopeT](
|
||||
var included bool
|
||||
|
||||
for _, inc := range incs {
|
||||
if inc.matchesEntry(cat, pathValues, entry) {
|
||||
if matchesEntry(inc, cat, pathValues, entry) {
|
||||
included = true
|
||||
break
|
||||
}
|
||||
@ -316,14 +315,14 @@ func passes[T scopeT](
|
||||
|
||||
// all filters must pass
|
||||
for _, filt := range filts {
|
||||
if !filt.matchesEntry(cat, pathValues, entry) {
|
||||
if !matchesEntry(filt, cat, pathValues, entry) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// any matching exclusion means failure
|
||||
for _, exc := range excs {
|
||||
if exc.matchesEntry(cat, pathValues, entry) {
|
||||
if matchesEntry(exc, cat, pathValues, entry) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -331,6 +330,22 @@ func passes[T scopeT](
|
||||
return true
|
||||
}
|
||||
|
||||
// matchesEntry determines whether the category and scope require a path
|
||||
// comparison or an entry info comparison.
|
||||
func matchesEntry[T scopeT, C categoryT](
|
||||
sc T,
|
||||
cat C,
|
||||
pathValues map[categorizer]string,
|
||||
entry details.DetailsEntry,
|
||||
) bool {
|
||||
// filterCategory requires matching against service-specific info values
|
||||
if len(getFilterCategory(sc)) > 0 {
|
||||
return sc.matchesInfo(entry.ItemInfo)
|
||||
}
|
||||
|
||||
return matchesPathValues(sc, cat, pathValues, entry.ShortRef)
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -342,27 +357,25 @@ func matchesPathValues[T scopeT, C categoryT](
|
||||
pathValues map[categorizer]string,
|
||||
shortRef string,
|
||||
) bool {
|
||||
// if scope specifies a filter category,
|
||||
// path checking is automatically skipped.
|
||||
if len(getFilterCategory(sc)) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
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 path must contain a value to match against
|
||||
|
||||
// the pathValues must have an entry for the given categorizer
|
||||
pathVal, ok := pathValues[c]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// all parts of the scope must match
|
||||
cc := c.(C)
|
||||
if !isAnyTarget(sc, cc) {
|
||||
|
||||
@ -103,19 +103,29 @@ func (suite *SelectorScopesSuite) TestContains() {
|
||||
|
||||
func (suite *SelectorScopesSuite) TestGetCatValue() {
|
||||
t := suite.T()
|
||||
|
||||
stub := stubScope("")
|
||||
stub[rootCatStub.String()] = filterize(rootCatStub.String())
|
||||
|
||||
assert.Equal(t,
|
||||
[]string{rootCatStub.String()},
|
||||
getCatValue(stub, rootCatStub))
|
||||
assert.Equal(t, None(), getCatValue(stub, leafCatStub))
|
||||
assert.Equal(t,
|
||||
None(),
|
||||
getCatValue(stub, mockCategorizer("foo")))
|
||||
}
|
||||
|
||||
func (suite *SelectorScopesSuite) TestIsAnyTarget() {
|
||||
t := suite.T()
|
||||
stub := stubScope("")
|
||||
assert.True(t, isAnyTarget(stub, rootCatStub))
|
||||
assert.True(t, isAnyTarget(stub, leafCatStub))
|
||||
assert.False(t, isAnyTarget(stub, mockCategorizer("smarf")))
|
||||
|
||||
stub = stubScope("none")
|
||||
assert.False(t, isAnyTarget(stub, rootCatStub))
|
||||
assert.False(t, isAnyTarget(stub, leafCatStub))
|
||||
assert.False(t, isAnyTarget(stub, mockCategorizer("smarf")))
|
||||
}
|
||||
|
||||
var reduceTestTable = []struct {
|
||||
@ -161,7 +171,7 @@ var reduceTestTable = []struct {
|
||||
name: "include all filter none",
|
||||
sel: func() mockSel {
|
||||
sel := stubSelector()
|
||||
sel.Filters[0] = scope(stubScope("none"))
|
||||
sel.Filters[0] = scope(stubInfoScope("none"))
|
||||
sel.Excludes = nil
|
||||
return sel
|
||||
},
|
||||
@ -257,7 +267,8 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() {
|
||||
[]scope{scope(s1), scope(s2)},
|
||||
map[path.CategoryType]mockCategorizer{
|
||||
path.UnknownCategory: rootCatStub,
|
||||
})
|
||||
},
|
||||
false)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Len(t, result[rootCatStub], 1)
|
||||
assert.Empty(t, result[leafCatStub])
|
||||
@ -265,7 +276,8 @@ func (suite *SelectorScopesSuite) TestScopesByCategory() {
|
||||
|
||||
func (suite *SelectorScopesSuite) TestPasses() {
|
||||
cat := rootCatStub
|
||||
pathVals := map[categorizer]string{}
|
||||
pth := stubPath(suite.T(), "uid", []string{"fld"}, path.EventsCategory)
|
||||
pathVals := cat.pathValues(pth)
|
||||
entry := details.DetailsEntry{}
|
||||
|
||||
for _, test := range reduceTestTable {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user