diff --git a/src/pkg/selectors/exchange.go b/src/pkg/selectors/exchange.go index 67bf2badf..75accde1e 100644 --- a/src/pkg/selectors/exchange.go +++ b/src/pkg/selectors/exchange.go @@ -551,6 +551,11 @@ func (ec exchangeCategory) unknownCat() categorizer { return ExchangeCategoryUnknown } +// isLeaf is true if the category is a mail, event, or contact category. +func (ec exchangeCategory) isLeaf() bool { + return ec == ec.leafCat() +} + // pathValues transforms a path to a map of identified properties. // // Example: @@ -692,7 +697,7 @@ func (s ExchangeScope) matchesEntry( entry details.DetailsEntry, ) bool { // matchesPathValues can be handled generically, thanks to SCIENCE. - return matchesPathValues(s, cat.(exchangeCategory), pathValues) || s.matchesInfo(entry.Exchange) + return matchesPathValues(s, cat.(exchangeCategory), pathValues, entry.ShortRef) || s.matchesInfo(entry.Exchange) } // matchesInfo handles the standard behavior when comparing a scope and an ExchangeFilter diff --git a/src/pkg/selectors/exchange_test.go b/src/pkg/selectors/exchange_test.go index 43c4f5c9f..42bc852f2 100644 --- a/src/pkg/selectors/exchange_test.go +++ b/src/pkg/selectors/exchange_test.go @@ -779,30 +779,34 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesPath() { ) var ( - pth = stubPath(suite.T(), usr, []string{fld, mail}, path.EmailCategory) - es = NewExchangeRestore() + pth = stubPath(suite.T(), usr, []string{fld, mail}, path.EmailCategory) + short = "thisisahashofsomekind" + es = NewExchangeRestore() ) table := []struct { - name string - scope []ExchangeScope - expect assert.BoolAssertionFunc + name string + scope []ExchangeScope + shortRef string + expect assert.BoolAssertionFunc }{ - {"all user's items", es.Users(Any()), assert.True}, - {"no user's items", es.Users(None()), assert.False}, - {"matching user", es.Users([]string{usr}), assert.True}, - {"non-matching user", es.Users([]string{"smarf"}), assert.False}, - {"one of multiple users", es.Users([]string{"smarf", usr}), assert.True}, - {"all folders", es.MailFolders(Any(), Any()), assert.True}, - {"no folders", es.MailFolders(Any(), None()), assert.False}, - {"matching folder", es.MailFolders(Any(), []string{fld}), assert.True}, - {"non-matching folder", es.MailFolders(Any(), []string{"smarf"}), assert.False}, - {"one of multiple folders", es.MailFolders(Any(), []string{"smarf", fld}), assert.True}, - {"all mail", es.Mails(Any(), Any(), Any()), assert.True}, - {"no mail", es.Mails(Any(), Any(), None()), assert.False}, - {"matching mail", es.Mails(Any(), Any(), []string{mail}), assert.True}, - {"non-matching mail", es.Mails(Any(), Any(), []string{"smarf"}), assert.False}, - {"one of multiple mails", es.Mails(Any(), Any(), []string{"smarf", mail}), assert.True}, + {"all user's items", es.Users(Any()), "", assert.True}, + {"no user's items", es.Users(None()), "", assert.False}, + {"matching user", es.Users([]string{usr}), "", assert.True}, + {"non-matching user", es.Users([]string{"smarf"}), "", assert.False}, + {"one of multiple users", es.Users([]string{"smarf", usr}), "", assert.True}, + {"all folders", es.MailFolders(Any(), Any()), "", assert.True}, + {"no folders", es.MailFolders(Any(), None()), "", assert.False}, + {"matching folder", es.MailFolders(Any(), []string{fld}), "", assert.True}, + {"non-matching folder", es.MailFolders(Any(), []string{"smarf"}), "", assert.False}, + {"one of multiple folders", es.MailFolders(Any(), []string{"smarf", fld}), "", assert.True}, + {"all mail", es.Mails(Any(), Any(), Any()), "", assert.True}, + {"no mail", es.Mails(Any(), Any(), None()), "", assert.False}, + {"matching mail", es.Mails(Any(), Any(), []string{mail}), "", assert.True}, + {"non-matching mail", es.Mails(Any(), Any(), []string{"smarf"}), "", assert.False}, + {"one of multiple mails", es.Mails(Any(), Any(), []string{"smarf", mail}), "", assert.True}, + {"mail short ref", es.Mails(Any(), Any(), []string{short}), short, assert.True}, + {"non-leaf short ref", es.Mails([]string{short}, []string{short}, []string{"foo"}), short, assert.False}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { @@ -810,7 +814,7 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesPath() { var aMatch bool for _, scope := range scopes { pv := ExchangeMail.pathValues(pth) - if matchesPathValues(scope, ExchangeMail, pv) { + if matchesPathValues(scope, ExchangeMail, pv, short) { aMatch = true break } @@ -1072,7 +1076,8 @@ func (suite *ExchangeSelectorSuite) TestScopesByCategory() { } func (suite *ExchangeSelectorSuite) TestPasses() { - deets := details.DetailsEntry{} + short := "thisisahashofsomekind" + entry := details.DetailsEntry{ShortRef: short} const ( mid = "mailID" @@ -1118,7 +1123,7 @@ func (suite *ExchangeSelectorSuite) TestPasses() { result := passes( cat, cat.pathValues(pth), - deets, + entry, test.excludes, test.filters, test.includes) diff --git a/src/pkg/selectors/helpers_test.go b/src/pkg/selectors/helpers_test.go index ceb3923ee..1fa216c66 100644 --- a/src/pkg/selectors/helpers_test.go +++ b/src/pkg/selectors/helpers_test.go @@ -47,6 +47,10 @@ func (mc mockCategorizer) unknownCat() categorizer { return unknownCatStub } +func (mc mockCategorizer) isLeaf() bool { + return mc == leafCatStub +} + func (mc mockCategorizer) pathValues(pth path.Path) map[categorizer]string { return map[categorizer]string{rootCatStub: "stub"} } diff --git a/src/pkg/selectors/onedrive.go b/src/pkg/selectors/onedrive.go index 6f2942f71..84f417f6e 100644 --- a/src/pkg/selectors/onedrive.go +++ b/src/pkg/selectors/onedrive.go @@ -183,6 +183,11 @@ func (c oneDriveCategory) unknownCat() categorizer { return OneDriveCategoryUnknown } +// isLeaf is true if the category is a mail, event, or contact category. +func (c oneDriveCategory) isLeaf() bool { + return c == c.leafCat() +} + // pathValues transforms a path to a map of identified properties. // // Example: @@ -271,7 +276,7 @@ func (s OneDriveScope) matchesEntry( entry details.DetailsEntry, ) bool { // matchesPathValues can be handled generically, thanks to SCIENCE. - return matchesPathValues(s, cat.(oneDriveCategory), pathValues) || s.matchesInfo(entry.OneDrive) + return matchesPathValues(s, cat.(oneDriveCategory), pathValues, entry.ShortRef) || s.matchesInfo(entry.OneDrive) } // matchesInfo handles the standard behavior when comparing a scope and an oneDriveInfo diff --git a/src/pkg/selectors/scopes.go b/src/pkg/selectors/scopes.go index a2c9f1060..ed16628f2 100644 --- a/src/pkg/selectors/scopes.go +++ b/src/pkg/selectors/scopes.go @@ -32,6 +32,10 @@ type ( // unknownType returns the unknown category value unknownCat() categorizer + // isLeaf returns true if the category is one of the leaf categories. + // eg: in a resourceOwner/folder/item structure, the item is the leaf. + isLeaf() bool + // pathValues should produce a map of category:string pairs populated by extracting // values out of the path.Path struct. // @@ -205,7 +209,6 @@ func isAnyTarget[T scopeT, C categoryT](s T, cat C) bool { // 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]( ctx context.Context, deets *details.Details, @@ -337,6 +340,7 @@ func matchesPathValues[T scopeT, C categoryT]( sc T, cat C, pathValues map[categorizer]string, + shortRef string, ) bool { // if scope specifies a filter category, // path checking is automatically skipped. @@ -362,7 +366,12 @@ func matchesPathValues[T scopeT, C categoryT]( // all parts of the scope must match cc := c.(C) if !isAnyTarget(sc, cc) { - if filters.NotContains(join(scopeVals...)).Compare(pathVal) { + notMatch := filters.NotContains(join(scopeVals...)) + if c.isLeaf() && len(shortRef) > 0 { + if notMatch.Compare(pathVal) && notMatch.Compare(shortRef) { + return false + } + } else if notMatch.Compare(pathVal) { return false } } diff --git a/src/pkg/selectors/scopes_test.go b/src/pkg/selectors/scopes_test.go index f4aca08ec..944515751 100644 --- a/src/pkg/selectors/scopes_test.go +++ b/src/pkg/selectors/scopes_test.go @@ -301,12 +301,15 @@ func toMockScope(sc []scope) []mockScope { func (suite *SelectorScopesSuite) TestMatchesPathValues() { cat := rootCatStub pvs := stubPathValues() + short := "brunheelda" table := []struct { - name string - rootVal string - leafVal string - expect assert.BoolAssertionFunc + name string + cat mockCategorizer + rootVal string + leafVal string + shortRef string + expect assert.BoolAssertionFunc }{ { name: "matching values", @@ -332,6 +335,20 @@ func (suite *SelectorScopesSuite) TestMatchesPathValues() { leafVal: "smarf", expect: assert.False, }, + { + name: "leaf matches shortRef", + rootVal: rootCatStub.String(), + leafVal: short, + shortRef: short, + expect: assert.True, + }, + { + name: "root matches shortRef", + rootVal: short, + leafVal: leafCatStub.String(), + shortRef: short, + expect: assert.False, + }, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { @@ -339,7 +356,7 @@ func (suite *SelectorScopesSuite) TestMatchesPathValues() { sc[rootCatStub.String()] = filterize(test.rootVal) sc[leafCatStub.String()] = filterize(test.leafVal) - test.expect(t, matchesPathValues(sc, cat, pvs)) + test.expect(t, matchesPathValues(sc, cat, pvs, test.shortRef)) }) } }