Match resource owners at top of reduce (#1891)

## Description

Checks for resource owner matches in the top
of the reduce func using the selector owners,
instead of waiting until the path match check.

## Does this PR need a docs update or release note?

- [x]  No 

## Type of change

- [x] 🌻 Feature

## Issue(s)

* #1617

## Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-01-04 17:59:16 -07:00 committed by GitHub
parent edc4426b9c
commit 1f26339813
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 197 additions and 139 deletions

View File

@ -5,13 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased] (alpha)
### Changed
- The selectors Reduce() process will only include details that match the DiscreteOwner, if one is specified.
- New selector constructors will automatically set the DiscreteOwner if given a single-item slice.
### Fixed ### Fixed
- Fixed issue where repository connect progress bar was clobbering backup/restore operation output. - Fixed issue where repository connect progress bar was clobbering backup/restore operation output.
- Fixed issue where a `backup create exchange` produced one backup record per data type.
## [v0.0.4] (alpha) ### Known Issues
- `backup list` will not display a resource owner for backups created prior to this release.
## [v0.0.4] (alpha) - 2022-12-23
### Added ### Added

View File

@ -505,8 +505,7 @@ func runDetailsExchangeCmd(
return nil, errors.Wrap(err, "Failed to get backup details in the repository") return nil, errors.Wrap(err, "Failed to get backup details in the repository")
} }
sel := selectors.NewExchangeRestore(nil) // TODO: generate selector in IncludeExchangeRestoreDataSelectors sel := utils.IncludeExchangeRestoreDataSelectors(opts)
utils.IncludeExchangeRestoreDataSelectors(sel, opts)
utils.FilterExchangeRestoreInfoSelectors(sel, opts) utils.FilterExchangeRestoreInfoSelectors(sel, opts)
// if no selector flags were specified, get all data in the service. // if no selector flags were specified, get all data in the service.

View File

@ -396,8 +396,7 @@ func runDetailsOneDriveCmd(
return nil, errors.Wrap(err, "Failed to get backup details in the repository") return nil, errors.Wrap(err, "Failed to get backup details in the repository")
} }
sel := selectors.NewOneDriveRestore(nil) // TODO: generate selector in IncludeExchangeRestoreDataSelectors sel := utils.IncludeOneDriveRestoreDataSelectors(opts)
utils.IncludeOneDriveRestoreDataSelectors(sel, opts)
utils.FilterOneDriveRestoreInfoSelectors(sel, opts) utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
// if no selector flags were specified, get all data in the service. // if no selector flags were specified, get all data in the service.

View File

@ -479,8 +479,7 @@ func runDetailsSharePointCmd(
return nil, errors.Wrap(err, "Failed to get backup details in the repository") return nil, errors.Wrap(err, "Failed to get backup details in the repository")
} }
sel := selectors.NewSharePointRestore(nil) // TODO: generate selector in IncludeSharePointRestoreDataSelectors sel := utils.IncludeSharePointRestoreDataSelectors(opts)
utils.IncludeSharePointRestoreDataSelectors(sel, opts)
utils.FilterSharePointRestoreInfoSelectors(sel, opts) utils.FilterSharePointRestoreInfoSelectors(sel, opts)
// if no selector flags were specified, get all data in the service. // if no selector flags were specified, get all data in the service.

View File

@ -216,8 +216,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
sel := selectors.NewExchangeRestore(nil) // TODO: generate selector in IncludeExchangeRestoreDataSelectors sel := utils.IncludeExchangeRestoreDataSelectors(opts)
utils.IncludeExchangeRestoreDataSelectors(sel, opts)
utils.FilterExchangeRestoreInfoSelectors(sel, opts) utils.FilterExchangeRestoreInfoSelectors(sel, opts)
// if no selector flags were specified, get all data in the service. // if no selector flags were specified, get all data in the service.

View File

@ -153,8 +153,7 @@ func restoreOneDriveCmd(cmd *cobra.Command, args []string) error {
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
sel := selectors.NewOneDriveRestore(nil) // TODO: generate selector in IncludeOneDriveRestoreDataSelectors sel := utils.IncludeOneDriveRestoreDataSelectors(opts)
utils.IncludeOneDriveRestoreDataSelectors(sel, opts)
utils.FilterOneDriveRestoreInfoSelectors(sel, opts) utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
// if no selector flags were specified, get all data in the service. // if no selector flags were specified, get all data in the service.

View File

@ -154,8 +154,7 @@ func restoreSharePointCmd(cmd *cobra.Command, args []string) error {
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
sel := selectors.NewSharePointRestore(nil) // TODO: generate selector in IncludeSharePointRestoreDataSelectors sel := utils.IncludeSharePointRestoreDataSelectors(opts)
utils.IncludeSharePointRestoreDataSelectors(sel, opts)
utils.FilterSharePointRestoreInfoSelectors(sel, opts) utils.FilterSharePointRestoreInfoSelectors(sel, opts)
// if no selector flags were specified, get all data in the service. // if no selector flags were specified, get all data in the service.

View File

@ -128,30 +128,32 @@ func ValidateExchangeRestoreFlags(backupID string, opts ExchangeOpts) error {
// IncludeExchangeRestoreDataSelectors builds the common data-selector // IncludeExchangeRestoreDataSelectors builds the common data-selector
// inclusions for exchange commands. // inclusions for exchange commands.
func IncludeExchangeRestoreDataSelectors( func IncludeExchangeRestoreDataSelectors(opts ExchangeOpts) *selectors.ExchangeRestore {
sel *selectors.ExchangeRestore, users := opts.Users
opts ExchangeOpts, if len(users) == 0 {
) { users = selectors.Any()
}
sel := selectors.NewExchangeRestore(users)
lc, lcf := len(opts.Contact), len(opts.ContactFolder) lc, lcf := len(opts.Contact), len(opts.ContactFolder)
le, lef := len(opts.Email), len(opts.EmailFolder) le, lef := len(opts.Email), len(opts.EmailFolder)
lev, lec := len(opts.Event), len(opts.EventCalendar) lev, lec := len(opts.Event), len(opts.EventCalendar)
// either scope the request to a set of users // either scope the request to a set of users
if lc+lcf+le+lef+lev+lec == 0 { if lc+lcf+le+lef+lev+lec == 0 {
if len(opts.Users) == 0 { sel.Include(sel.Users(users))
opts.Users = selectors.Any()
}
sel.Include(sel.Users(opts.Users)) return sel
return
} }
opts.EmailFolder = trimFolderSlash(opts.EmailFolder) opts.EmailFolder = trimFolderSlash(opts.EmailFolder)
// or add selectors for each type of data // or add selectors for each type of data
AddExchangeInclude(sel, opts.Users, opts.ContactFolder, opts.Contact, sel.Contacts) AddExchangeInclude(sel, users, opts.ContactFolder, opts.Contact, sel.Contacts)
AddExchangeInclude(sel, opts.Users, opts.EmailFolder, opts.Email, sel.Mails) AddExchangeInclude(sel, users, opts.EmailFolder, opts.Email, sel.Mails)
AddExchangeInclude(sel, opts.Users, opts.EventCalendar, opts.Event, sel.Events) AddExchangeInclude(sel, users, opts.EventCalendar, opts.Event, sel.Events)
return sel
} }
// FilterExchangeRestoreInfoSelectors builds the common info-selector filters. // FilterExchangeRestoreInfoSelectors builds the common info-selector filters.

View File

@ -301,8 +301,7 @@ func (suite *ExchangeUtilsSuite) TestIncludeExchangeRestoreDataSelectors() {
} }
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) {
sel := selectors.NewExchangeRestore(nil) sel := utils.IncludeExchangeRestoreDataSelectors(test.opts)
utils.IncludeExchangeRestoreDataSelectors(sel, test.opts)
assert.Len(t, sel.Includes, test.expectIncludeLen) assert.Len(t, sel.Includes, test.expectIncludeLen)
}) })
} }

View File

@ -70,27 +70,22 @@ func AddOneDriveFilter(
// IncludeOneDriveRestoreDataSelectors builds the common data-selector // IncludeOneDriveRestoreDataSelectors builds the common data-selector
// inclusions for OneDrive commands. // inclusions for OneDrive commands.
func IncludeOneDriveRestoreDataSelectors( func IncludeOneDriveRestoreDataSelectors(opts OneDriveOpts) *selectors.OneDriveRestore {
sel *selectors.OneDriveRestore, users := opts.Users
opts OneDriveOpts, if len(users) == 0 {
) { users = selectors.Any()
}
sel := selectors.NewOneDriveRestore(users)
lp, ln := len(opts.Paths), len(opts.Names) lp, ln := len(opts.Paths), len(opts.Names)
// only use the inclusion if either a path or item name // only use the inclusion if either a path or item name
// is specified // is specified
if lp+ln == 0 {
return
}
if len(opts.Users) == 0 {
opts.Users = selectors.Any()
}
// either scope the request to a set of users
if lp+ln == 0 { if lp+ln == 0 {
sel.Include(sel.Users(opts.Users)) sel.Include(sel.Users(opts.Users))
return return sel
} }
opts.Paths = trimFolderSlash(opts.Paths) opts.Paths = trimFolderSlash(opts.Paths)
@ -102,12 +97,14 @@ func IncludeOneDriveRestoreDataSelectors(
containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.Paths) containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.Paths)
if len(containsFolders) > 0 { if len(containsFolders) > 0 {
sel.Include(sel.Items(opts.Users, containsFolders, opts.Names)) sel.Include(sel.Items(users, containsFolders, opts.Names))
} }
if len(prefixFolders) > 0 { if len(prefixFolders) > 0 {
sel.Include(sel.Items(opts.Users, prefixFolders, opts.Names, selectors.PrefixMatch())) sel.Include(sel.Items(users, prefixFolders, opts.Names, selectors.PrefixMatch()))
} }
return sel
} }
// FilterOneDriveRestoreInfoSelectors builds the common info-selector filters. // FilterOneDriveRestoreInfoSelectors builds the common info-selector filters.

View File

@ -7,7 +7,6 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/cli/utils" "github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/pkg/selectors"
) )
type OneDriveUtilsSuite struct { type OneDriveUtilsSuite struct {
@ -40,7 +39,7 @@ func (suite *OneDriveUtilsSuite) TestIncludeOneDriveRestoreDataSelectors() {
Paths: empty, Paths: empty,
Names: empty, Names: empty,
}, },
expectIncludeLen: 0, expectIncludeLen: 1,
}, },
{ {
name: "single inputs", name: "single inputs",
@ -90,9 +89,7 @@ func (suite *OneDriveUtilsSuite) TestIncludeOneDriveRestoreDataSelectors() {
} }
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) {
sel := selectors.NewOneDriveRestore(nil) sel := utils.IncludeOneDriveRestoreDataSelectors(test.opts)
// no return, mutates sel as a side effect
utils.IncludeOneDriveRestoreDataSelectors(sel, test.opts)
assert.Len(t, sel.Includes, test.expectIncludeLen) assert.Len(t, sel.Includes, test.expectIncludeLen)
}) })
} }

View File

@ -54,22 +54,22 @@ func AddSharePointFilter(
// IncludeSharePointRestoreDataSelectors builds the common data-selector // IncludeSharePointRestoreDataSelectors builds the common data-selector
// inclusions for SharePoint commands. // inclusions for SharePoint commands.
func IncludeSharePointRestoreDataSelectors( func IncludeSharePointRestoreDataSelectors(opts SharePointOpts) *selectors.SharePointRestore {
sel *selectors.SharePointRestore, sites := opts.Sites
opts SharePointOpts,
) {
lp, li := len(opts.LibraryPaths), len(opts.LibraryItems) lp, li := len(opts.LibraryPaths), len(opts.LibraryItems)
ls, lwu := len(opts.Sites), len(opts.WebURLs) ls, lwu := len(opts.Sites), len(opts.WebURLs)
slp, sli := len(opts.ListPaths), len(opts.ListItems) slp, sli := len(opts.ListPaths), len(opts.ListItems)
if ls == 0 { if ls == 0 {
opts.Sites = selectors.Any() sites = selectors.Any()
} }
if lp+li+lwu+slp+sli == 0 { sel := selectors.NewSharePointRestore(sites)
sel.Include(sel.Sites(opts.Sites))
return if lp+li+lwu+slp+sli == 0 {
sel.Include(sel.Sites(sites))
return sel
} }
if lp+li > 0 { if lp+li > 0 {
@ -81,11 +81,11 @@ func IncludeSharePointRestoreDataSelectors(
containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.LibraryPaths) containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.LibraryPaths)
if len(containsFolders) > 0 { if len(containsFolders) > 0 {
sel.Include(sel.LibraryItems(opts.Sites, containsFolders, opts.LibraryItems)) sel.Include(sel.LibraryItems(sites, containsFolders, opts.LibraryItems))
} }
if len(prefixFolders) > 0 { if len(prefixFolders) > 0 {
sel.Include(sel.LibraryItems(opts.Sites, prefixFolders, opts.LibraryItems, selectors.PrefixMatch())) sel.Include(sel.LibraryItems(sites, prefixFolders, opts.LibraryItems, selectors.PrefixMatch()))
} }
} }
@ -118,6 +118,8 @@ func IncludeSharePointRestoreDataSelectors(
sel.Include(sel.WebURL(suffixURLs, selectors.SuffixMatch())) sel.Include(sel.WebURL(suffixURLs, selectors.SuffixMatch()))
} }
} }
return sel
} }
// FilterSharePointRestoreInfoSelectors builds the common info-selector filters. // FilterSharePointRestoreInfoSelectors builds the common info-selector filters.

View File

@ -7,7 +7,6 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/cli/utils" "github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/pkg/selectors"
) )
type SharePointUtilsSuite struct { type SharePointUtilsSuite struct {
@ -164,11 +163,8 @@ func (suite *SharePointUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
} }
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) {
sel := selectors.NewSharePointRestore(nil) sel := utils.IncludeSharePointRestoreDataSelectors(test.opts)
// no return, mutates sel as a side effect assert.Len(t, sel.Includes, test.expectIncludeLen)
t.Logf("Options sent: %v\n", test.opts)
utils.IncludeSharePointRestoreDataSelectors(sel, test.opts)
assert.Len(t, sel.Includes, test.expectIncludeLen, sel)
}) })
} }
} }

View File

@ -268,6 +268,7 @@ var (
{ {
Name: "BadFileCreatedAfter", Name: "BadFileCreatedAfter",
Opts: utils.OneDriveOpts{ Opts: utils.OneDriveOpts{
Users: selectors.Any(),
FileCreatedAfter: "foo", FileCreatedAfter: "foo",
Populated: utils.PopulatedFlags{ Populated: utils.PopulatedFlags{
utils.FileCreatedAfterFN: struct{}{}, utils.FileCreatedAfterFN: struct{}{},

View File

@ -782,11 +782,13 @@ func makeExchangeBackupSel(
for _, d := range dests { for _, d := range dests {
for c := range d.cats { for c := range d.cats {
resourceOwners[d.resourceOwner] = struct{}{}
// nil owners here, but we'll need to stitch this together
// below after the loops are complete.
sel := selectors.NewExchangeBackup(nil) sel := selectors.NewExchangeBackup(nil)
builder := sel.MailFolders builder := sel.MailFolders
resourceOwners[d.resourceOwner] = struct{}{}
switch c { switch c {
case path.ContactsCategory: case path.ContactsCategory:
builder = sel.ContactFolders builder = sel.ContactFolders
@ -814,12 +816,15 @@ func makeOneDriveBackupSel(
dests []destAndCats, dests []destAndCats,
) selectors.Selector { ) selectors.Selector {
toInclude := [][]selectors.OneDriveScope{} toInclude := [][]selectors.OneDriveScope{}
resourceOwners := []string{} resourceOwners := map[string]struct{}{}
for _, d := range dests { for _, d := range dests {
resourceOwners[d.resourceOwner] = struct{}{}
// nil owners here, we'll need to stitch this together
// below after the loops are complete.
sel := selectors.NewOneDriveBackup(nil) sel := selectors.NewOneDriveBackup(nil)
resourceOwners = append(resourceOwners, d.resourceOwner)
toInclude = append(toInclude, sel.Folders( toInclude = append(toInclude, sel.Folders(
[]string{d.resourceOwner}, []string{d.resourceOwner},
[]string{d.dest}, []string{d.dest},
@ -827,7 +832,7 @@ func makeOneDriveBackupSel(
)) ))
} }
sel := selectors.NewOneDriveBackup(resourceOwners) sel := selectors.NewOneDriveBackup(maps.Keys(resourceOwners))
sel.Include(toInclude...) sel.Include(toInclude...)
return sel.Selector return sel.Selector

View File

@ -358,13 +358,16 @@ func (suite *BackupOpSuite) TestBackupOperation_PersistResults() {
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.expectStatus.String(), func(t *testing.T) { suite.T().Run(test.expectStatus.String(), func(t *testing.T) {
sel := selectors.Selector{}
sel.DiscreteOwner = "bombadil"
op, err := NewBackupOperation( op, err := NewBackupOperation(
ctx, ctx,
control.Options{}, control.Options{},
kw, kw,
sw, sw,
acct, acct,
selectors.Selector{DiscreteOwner: "test"}, sel,
evmock.NewBus()) evmock.NewBus())
require.NoError(t, err) require.NoError(t, err)
test.expectErr(t, op.persistResults(now, &test.stats)) test.expectErr(t, op.persistResults(now, &test.stats))

View File

@ -178,6 +178,7 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() {
users := []string{m365UserID} users := []string{m365UserID}
bsel := selectors.NewExchangeBackup(users) bsel := selectors.NewExchangeBackup(users)
bsel.DiscreteOwner = m365UserID
bsel.Include( bsel.Include(
bsel.MailFolders(users, []string{exchange.DefaultMailFolder}, selectors.PrefixMatch()), bsel.MailFolders(users, []string{exchange.DefaultMailFolder}, selectors.PrefixMatch()),
bsel.ContactFolders(users, []string{exchange.DefaultContactFolder}, selectors.PrefixMatch()), bsel.ContactFolders(users, []string{exchange.DefaultContactFolder}, selectors.PrefixMatch()),

View File

@ -128,7 +128,7 @@ var (
DetailsModel: details.DetailsModel{ DetailsModel: details.DetailsModel{
Entries: []details.DetailsEntry{ Entries: []details.DetailsEntry{
{ {
RepoRef: "tID/exchange/uID/email/example/itemID", RepoRef: "tID/exchange/your-user-id/email/example/itemID",
ShortRef: "xyz", ShortRef: "xyz",
ItemInfo: details.ItemInfo{ ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{ Exchange: &details.ExchangeInfo{
@ -155,7 +155,7 @@ func Example_reduceDetails() {
// We haven't added any scopes to our selector yet, so none of the data is retained. // We haven't added any scopes to our selector yet, so none of the data is retained.
fmt.Println("Before adding scopes:", len(filteredDetails.Entries)) fmt.Println("Before adding scopes:", len(filteredDetails.Entries))
ser.Include(ser.Mails([]string{"uID"}, []string{"example"}, []string{"xyz"})) ser.Include(ser.Mails([]string{"your-user-id"}, []string{"example"}, []string{"xyz"}))
ser.Filter(ser.MailSubject("the answer to life")) ser.Filter(ser.MailSubject("the answer to life"))
// Now that we've selected our data, we should find a result. // Now that we've selected our data, we should find a result.

View File

@ -781,7 +781,7 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesPath() {
var ( var (
pth = stubPath(suite.T(), usr, []string{fld1, fld2, mail}, path.EmailCategory) pth = stubPath(suite.T(), usr, []string{fld1, fld2, mail}, path.EmailCategory)
short = "thisisahashofsomekind" short = "thisisahashofsomekind"
es = NewExchangeRestore(Any()) // TODO: move into test so that test user set is embedded in the selector es = NewExchangeRestore(Any())
) )
table := []struct { table := []struct {
@ -791,9 +791,9 @@ func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesPath() {
expect assert.BoolAssertionFunc expect assert.BoolAssertionFunc
}{ }{
{"all user's items", es.Users(Any()), "", assert.True}, {"all user's items", es.Users(Any()), "", assert.True},
{"no user's items", es.Users(None()), "", assert.False}, {"no user's items", es.Users(None()), "", assert.True},
{"matching user", es.Users([]string{usr}), "", assert.True}, {"matching user", es.Users([]string{usr}), "", assert.True},
{"non-matching user", es.Users([]string{"smarf"}), "", assert.False}, {"non-matching user", es.Users([]string{"smarf"}), "", assert.True},
{"one of multiple users", es.Users([]string{"smarf", usr}), "", assert.True}, {"one of multiple users", es.Users([]string{"smarf", usr}), "", assert.True},
{"all folders", es.MailFolders(Any(), Any()), "", assert.True}, {"all folders", es.MailFolders(Any(), Any()), "", assert.True},
{"no folders", es.MailFolders(Any(), None()), "", assert.False}, {"no folders", es.MailFolders(Any(), None()), "", assert.False},
@ -1194,14 +1194,14 @@ func (suite *ExchangeSelectorSuite) TestPasses() {
}{ }{
{"empty", nil, nil, nil, assert.False}, {"empty", nil, nil, nil, assert.False},
{"in Any", nil, nil, anyUser, assert.True}, {"in Any", nil, nil, anyUser, assert.True},
{"in None", nil, nil, noUser, assert.False}, {"in None", nil, nil, noUser, assert.True},
{"in Mail", nil, nil, mail, assert.True}, {"in Mail", nil, nil, mail, assert.True},
{"in Other", nil, nil, otherMail, assert.False}, {"in Other", nil, nil, otherMail, assert.False},
{"in no Mail", nil, nil, noMail, assert.False}, {"in no Mail", nil, nil, noMail, assert.False},
{"ex Any", anyUser, nil, anyUser, assert.False}, {"ex Any", anyUser, nil, anyUser, assert.False},
{"ex Any filter", anyUser, anyUser, nil, assert.False}, {"ex Any filter", anyUser, anyUser, nil, assert.False},
{"ex None", noUser, nil, anyUser, assert.True}, {"ex None", noUser, nil, anyUser, assert.False},
{"ex None filter mail", noUser, mail, nil, assert.True}, {"ex None filter mail", noUser, mail, nil, assert.False},
{"ex None filter any user", noUser, anyUser, nil, assert.False}, {"ex None filter any user", noUser, anyUser, nil, assert.False},
{"ex Mail", mail, nil, anyUser, assert.False}, {"ex Mail", mail, nil, anyUser, assert.False},
{"ex Other", otherMail, nil, anyUser, assert.True}, {"ex Other", otherMail, nil, anyUser, assert.True},

View File

@ -148,9 +148,10 @@ type mockSel struct {
Selector Selector
} }
func stubSelector() mockSel { func stubSelector(resourceOwners []string) mockSel {
return mockSel{ return mockSel{
Selector: Selector{ Selector: Selector{
ResourceOwners: filterize(scopeConfig{}, resourceOwners...),
Service: ServiceExchange, Service: ServiceExchange,
Excludes: []scope{scope(stubScope(""))}, Excludes: []scope{scope(stubScope(""))},
Filters: []scope{scope(stubScope(""))}, Filters: []scope{scope(stubScope(""))},

View File

@ -295,6 +295,12 @@ func reduce[T scopeT, C categoryT](
return nil return nil
} }
// if a DiscreteOwner is specified, only match details for that owner.
matchesResourceOwner := s.ResourceOwners
if len(s.DiscreteOwner) > 0 {
matchesResourceOwner = filterize(scopeConfig{}, s.DiscreteOwner)
}
// aggregate each scope type by category for easier isolation in future processing. // aggregate each scope type by category for easier isolation in future processing.
excls := scopesByCategory[T](s.Excludes, dataCategories, false) excls := scopesByCategory[T](s.Excludes, dataCategories, false)
filts := scopesByCategory[T](s.Filters, dataCategories, true) filts := scopesByCategory[T](s.Filters, dataCategories, true)
@ -310,6 +316,11 @@ func reduce[T scopeT, C categoryT](
continue continue
} }
// first check, every entry needs to match the selector's resource owners.
if !matchesResourceOwner.Compare(repoPath.ResourceOwner()) {
continue
}
dc, ok := dataCategories[repoPath.Category()] dc, ok := dataCategories[repoPath.Category()]
if !ok { if !ok {
continue continue
@ -439,6 +450,11 @@ func matchesPathValues[T scopeT, C categoryT](
shortRef string, shortRef string,
) bool { ) bool {
for _, c := range cat.pathKeys() { for _, c := range cat.pathKeys() {
// resourceOwners are now checked at the beginning of the reduction.
if c == c.rootCat() {
continue
}
// 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 {

View File

@ -132,12 +132,13 @@ var reduceTestTable = []struct {
name string name string
sel func() mockSel sel func() mockSel
expectLen int expectLen int
expectPassesReduce assert.BoolAssertionFunc
expectPasses assert.BoolAssertionFunc expectPasses assert.BoolAssertionFunc
}{ }{
{ {
name: "include all", name: "include all resource owners",
sel: func() mockSel { sel: func() mockSel {
sel := stubSelector() sel := stubSelector(Any())
sel.Filters = nil sel.Filters = nil
sel.Excludes = nil sel.Excludes = nil
return sel return sel
@ -146,9 +147,32 @@ var reduceTestTable = []struct {
expectPasses: assert.True, expectPasses: assert.True,
}, },
{ {
name: "include none", name: "include all scopes",
sel: func() mockSel { sel: func() mockSel {
sel := stubSelector() sel := stubSelector(Any())
sel.Filters = nil
sel.Excludes = nil
return sel
},
expectLen: 1,
expectPasses: assert.True,
},
{
name: "include none resource owners",
sel: func() mockSel {
sel := stubSelector(None())
sel.Includes[0] = scope(stubScope(AnyTgt))
sel.Filters = nil
sel.Excludes = nil
return sel
},
expectLen: 0,
expectPasses: assert.True, // passes() does not check owners
},
{
name: "include none scopes",
sel: func() mockSel {
sel := stubSelector(Any())
sel.Includes[0] = scope(stubScope("none")) sel.Includes[0] = scope(stubScope("none"))
sel.Filters = nil sel.Filters = nil
sel.Excludes = nil sel.Excludes = nil
@ -160,7 +184,7 @@ var reduceTestTable = []struct {
{ {
name: "filter and include all", name: "filter and include all",
sel: func() mockSel { sel: func() mockSel {
sel := stubSelector() sel := stubSelector(Any())
sel.Excludes = nil sel.Excludes = nil
return sel return sel
}, },
@ -170,7 +194,7 @@ var reduceTestTable = []struct {
{ {
name: "include all filter none", name: "include all filter none",
sel: func() mockSel { sel: func() mockSel {
sel := stubSelector() sel := stubSelector(Any())
sel.Filters[0] = scope(stubInfoScope("none")) sel.Filters[0] = scope(stubInfoScope("none"))
sel.Excludes = nil sel.Excludes = nil
return sel return sel
@ -181,7 +205,7 @@ var reduceTestTable = []struct {
{ {
name: "include all exclude all", name: "include all exclude all",
sel: func() mockSel { sel: func() mockSel {
sel := stubSelector() sel := stubSelector(Any())
sel.Filters = nil sel.Filters = nil
return sel return sel
}, },
@ -191,7 +215,7 @@ var reduceTestTable = []struct {
{ {
name: "include all exclude none", name: "include all exclude none",
sel: func() mockSel { sel: func() mockSel {
sel := stubSelector() sel := stubSelector(Any())
sel.Filters = nil sel.Filters = nil
sel.Excludes[0] = scope(stubScope("none")) sel.Excludes[0] = scope(stubScope("none"))
return sel return sel
@ -202,7 +226,7 @@ var reduceTestTable = []struct {
{ {
name: "filter all exclude all", name: "filter all exclude all",
sel: func() mockSel { sel: func() mockSel {
sel := stubSelector() sel := stubSelector(Any())
sel.Includes = nil sel.Includes = nil
return sel return sel
}, },
@ -212,7 +236,7 @@ var reduceTestTable = []struct {
{ {
name: "filter all exclude none", name: "filter all exclude none",
sel: func() mockSel { sel: func() mockSel {
sel := stubSelector() sel := stubSelector(Any())
sel.Includes = nil sel.Includes = nil
sel.Excludes[0] = scope(stubScope("none")) sel.Excludes[0] = scope(stubScope("none"))
return sel return sel
@ -357,13 +381,6 @@ func (suite *SelectorScopesSuite) TestMatchesPathValues() {
shortRef: short, shortRef: short,
expect: assert.True, expect: assert.True,
}, },
{
name: "root matches shortRef",
rootVal: short,
leafVal: leafCatStub.String(),
shortRef: short,
expect: assert.False,
},
} }
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) {

View File

@ -35,7 +35,7 @@ func (suite *SelectorSuite) TestBadCastErr() {
func (suite *SelectorSuite) TestPrintable() { func (suite *SelectorSuite) TestPrintable() {
t := suite.T() t := suite.T()
sel := stubSelector() sel := stubSelector(Any())
p := sel.Printable() p := sel.Printable()
assert.Equal(t, sel.Service.String(), p.Service) assert.Equal(t, sel.Service.String(), p.Service)
@ -45,9 +45,32 @@ func (suite *SelectorSuite) TestPrintable() {
} }
func (suite *SelectorSuite) TestPrintable_IncludedResources() { func (suite *SelectorSuite) TestPrintable_IncludedResources() {
t := suite.T() table := []struct {
name string
sel := stubSelector() resourceOwners []string
expect func(string) bool
reason string
}{
{
name: "distinct",
resourceOwners: []string{"foo", "smarf", "fnords"},
expect: func(s string) bool {
return strings.HasSuffix(s, "(2 more)")
},
reason: "should end with (2 more)",
},
{
name: "distinct",
resourceOwners: nil,
expect: func(s string) bool {
return strings.HasSuffix(s, "None")
},
reason: "no resource owners should produce None",
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
sel := stubSelector(test.resourceOwners)
p := sel.Printable() p := sel.Printable()
res := p.Resources() res := p.Resources()
@ -60,26 +83,20 @@ func (suite *SelectorSuite) TestPrintable_IncludedResources() {
return scope(ss) return scope(ss)
} }
sel.Includes = []scope{ sel.Includes = []scope{}
stubWithResource("foo"), sel.Filters = []scope{}
stubWithResource("smarf"),
stubWithResource("fnords"), for _, ro := range test.resourceOwners {
sel.Includes = append(sel.Includes, stubWithResource(ro))
sel.Filters = append(sel.Filters, stubWithResource(ro))
} }
p = sel.Printable() p = sel.Printable()
res = p.Resources() res = p.Resources()
assert.True(t, strings.HasSuffix(res, "(2 more)"), "resource '"+res+"' should have (2 more) suffix") assert.True(t, test.expect(res), test.reason)
})
p.Includes = nil }
res = p.Resources()
assert.Equal(t, "All", res, "filters is also an all-pass")
p.Filters = nil
res = p.Resources()
assert.Equal(t, "None", res, "resource with no Includes or Filters should state None")
} }
func (suite *SelectorSuite) TestToResourceTypeMap() { func (suite *SelectorSuite) TestToResourceTypeMap() {