add clues, fault to selectors (#2335)

## Description

Adds clues and fault handling to selectors pkg.
Some bleed upward into the CLI occured from
where the cli directly calls selectors.Reduce.

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

- [x]  No 

## Type of change

- [x] 🧹 Tech Debt/Cleanup

## Issue(s)

* #1970

## Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-02-06 15:06:50 -07:00 committed by GitHub
parent faad5d35a4
commit 5537a11948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 134 additions and 91 deletions

View File

@ -16,6 +16,7 @@ import (
"github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors"
@ -470,9 +471,10 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
defer utils.CloseRepo(ctx, r)
ds, err := runDetailsExchangeCmd(ctx, r, backupID, opts)
if err != nil {
return Only(ctx, err)
ds, errs := runDetailsExchangeCmd(ctx, r, backupID, opts)
if errs.Err() != nil {
// TODO: log/display iterated errors
return Only(ctx, errs.Err())
}
if len(ds.Entries) == 0 {
@ -486,29 +488,33 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
}
// runDetailsExchangeCmd actually performs the lookup in backup details.
// the fault.Errors return is always non-nil. Callers should check if
// errs.Err() == nil.
func runDetailsExchangeCmd(
ctx context.Context,
r repository.BackupGetter,
backupID string,
opts utils.ExchangeOpts,
) (*details.Details, error) {
) (*details.Details, *fault.Errors) {
errs := fault.New(false)
if err := utils.ValidateExchangeRestoreFlags(backupID, opts); err != nil {
return nil, err
return nil, errs.Fail(err)
}
d, _, err := r.BackupDetails(ctx, backupID)
if err != nil {
if errors.Is(err, kopia.ErrNotFound) {
return nil, errors.Errorf("No backup exists with the id %s", backupID)
return nil, errs.Fail(errors.Errorf("No backup exists with the id %s", backupID))
}
return nil, errors.Wrap(err, "Failed to get backup details in the repository")
return nil, errs.Fail(errors.Wrap(err, "Failed to get backup details in the repository"))
}
sel := utils.IncludeExchangeRestoreDataSelectors(opts)
utils.FilterExchangeRestoreInfoSelectors(sel, opts)
return sel.Reduce(ctx, d), nil
return sel.Reduce(ctx, d, errs), errs
}
// ------------------------------------------------------------------------------------------------

View File

@ -223,33 +223,14 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectors() {
ctx,
test.BackupGetter,
"backup-ID",
test.Opts,
)
assert.NoError(t, err)
test.Opts)
assert.NoError(t, err.Err(), "failure")
assert.Empty(t, err.Errs(), "recovered errors")
assert.ElementsMatch(t, test.Expected, output.Entries)
})
}
}
func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectorsBadBackupID() {
t := suite.T()
ctx, flush := tester.NewContext()
backupGetter := &testdata.MockBackupGetter{}
defer flush()
output, err := runDetailsExchangeCmd(
ctx,
backupGetter,
"backup-ID",
utils.ExchangeOpts{},
)
assert.Error(t, err)
assert.Empty(t, output)
}
func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectorsBadFormats() {
ctx, flush := tester.NewContext()
defer flush()
@ -260,10 +241,9 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectorsBadFormats() {
ctx,
test.BackupGetter,
"backup-ID",
test.Opts,
)
assert.Error(t, err)
test.Opts)
assert.Error(t, err.Err(), "failure")
assert.Empty(t, err.Errs(), "recovered errors")
assert.Empty(t, output)
})
}

View File

@ -16,6 +16,7 @@ import (
"github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors"
@ -362,9 +363,10 @@ func detailsOneDriveCmd(cmd *cobra.Command, args []string) error {
Populated: utils.GetPopulatedFlags(cmd),
}
ds, err := runDetailsOneDriveCmd(ctx, r, backupID, opts)
if err != nil {
return Only(ctx, err)
ds, errs := runDetailsOneDriveCmd(ctx, r, backupID, opts)
if errs.Err() != nil {
// TODO: log/display iterated errors
return Only(ctx, errs.Err())
}
if len(ds.Entries) == 0 {
@ -378,29 +380,33 @@ func detailsOneDriveCmd(cmd *cobra.Command, args []string) error {
}
// runDetailsOneDriveCmd actually performs the lookup in backup details.
// the fault.Errors return is always non-nil. Callers should check if
// errs.Err() == nil.
func runDetailsOneDriveCmd(
ctx context.Context,
r repository.BackupGetter,
backupID string,
opts utils.OneDriveOpts,
) (*details.Details, error) {
) (*details.Details, *fault.Errors) {
errs := fault.New(false)
if err := utils.ValidateOneDriveRestoreFlags(backupID, opts); err != nil {
return nil, err
return nil, errs.Fail(err)
}
d, _, err := r.BackupDetails(ctx, backupID)
if err != nil {
if errors.Is(err, kopia.ErrNotFound) {
return nil, errors.Errorf("no backup exists with the id %s", backupID)
return nil, errs.Fail(errors.Errorf("no backup exists with the id %s", backupID))
}
return nil, errors.Wrap(err, "Failed to get backup details in the repository")
return nil, errs.Fail(errors.Wrap(err, "Failed to get backup details in the repository"))
}
sel := utils.IncludeOneDriveRestoreDataSelectors(opts)
utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
return sel.Reduce(ctx, d), nil
return sel.Reduce(ctx, d, errs), errs
}
// `corso backup delete onedrive [<flag>...]`

View File

@ -98,10 +98,9 @@ func (suite *OneDriveSuite) TestOneDriveBackupDetailsSelectors() {
ctx,
test.BackupGetter,
"backup-ID",
test.Opts,
)
assert.NoError(t, err)
test.Opts)
assert.NoError(t, err.Err())
assert.Empty(t, err.Errs())
assert.ElementsMatch(t, test.Expected, output.Entries)
})
}
@ -117,10 +116,9 @@ func (suite *OneDriveSuite) TestOneDriveBackupDetailsSelectorsBadFormats() {
ctx,
test.BackupGetter,
"backup-ID",
test.Opts,
)
assert.Error(t, err)
test.Opts)
assert.Error(t, err.Err())
assert.Empty(t, err.Errs())
assert.Empty(t, output)
})
}

View File

@ -18,6 +18,7 @@ import (
"github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors"
@ -481,9 +482,10 @@ func detailsSharePointCmd(cmd *cobra.Command, args []string) error {
Populated: utils.GetPopulatedFlags(cmd),
}
ds, err := runDetailsSharePointCmd(ctx, r, backupID, opts)
if err != nil {
return Only(ctx, err)
ds, errs := runDetailsSharePointCmd(ctx, r, backupID, opts)
if errs.Err() != nil {
// TODO: log/display iterated errors
return Only(ctx, errs.Err())
}
if len(ds.Entries) == 0 {
@ -497,27 +499,31 @@ func detailsSharePointCmd(cmd *cobra.Command, args []string) error {
}
// runDetailsSharePointCmd actually performs the lookup in backup details.
// the fault.Errors return is always non-nil. Callers should check if
// errs.Err() == nil.
func runDetailsSharePointCmd(
ctx context.Context,
r repository.BackupGetter,
backupID string,
opts utils.SharePointOpts,
) (*details.Details, error) {
) (*details.Details, *fault.Errors) {
errs := fault.New(false)
if err := utils.ValidateSharePointRestoreFlags(backupID, opts); err != nil {
return nil, err
return nil, errs.Fail(err)
}
d, _, err := r.BackupDetails(ctx, backupID)
if err != nil {
if errors.Is(err, kopia.ErrNotFound) {
return nil, errors.Errorf("no backup exists with the id %s", backupID)
return nil, errs.Fail(errors.Errorf("no backup exists with the id %s", backupID))
}
return nil, errors.Wrap(err, "Failed to get backup details in the repository")
return nil, errs.Fail(errors.Wrap(err, "Failed to get backup details in the repository"))
}
sel := utils.IncludeSharePointRestoreDataSelectors(opts)
utils.FilterSharePointRestoreInfoSelectors(sel, opts)
return sel.Reduce(ctx, d), nil
return sel.Reduce(ctx, d, errs), errs
}

View File

@ -213,10 +213,9 @@ func (suite *SharePointSuite) TestSharePointBackupDetailsSelectors() {
ctx,
test.BackupGetter,
"backup-ID",
test.Opts,
)
assert.NoError(t, err)
test.Opts)
assert.NoError(t, err.Err())
assert.Empty(t, err.Errs())
assert.ElementsMatch(t, test.Expected, output.Entries)
})
}
@ -232,10 +231,9 @@ func (suite *SharePointSuite) TestSharePointBackupDetailsSelectorsBadFormats() {
ctx,
test.BackupGetter,
"backup-ID",
test.Opts,
)
assert.Error(t, err)
test.Opts)
assert.Error(t, err.Err())
assert.Empty(t, err.Errs())
assert.Empty(t, output)
})
}

View File

@ -4,7 +4,7 @@ go 1.19
require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0
github.com/alcionai/clues v0.0.0-20230131232239-cee86233b005
github.com/alcionai/clues v0.0.0-20230202001016-cbda58c9de9e
github.com/aws/aws-sdk-go v1.44.192
github.com/aws/aws-xray-sdk-go v1.8.0
github.com/google/uuid v1.3.0

View File

@ -54,6 +54,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/alcionai/clues v0.0.0-20230131232239-cee86233b005 h1:eTgICcmcydEWG8J+hgnidf0pzujV3Gd2XqmknykZkzA=
github.com/alcionai/clues v0.0.0-20230131232239-cee86233b005/go.mod h1:UlAs8jkWIpsOMakiC8NxPgQQVQRdvyf1hYMszlYYLb4=
github.com/alcionai/clues v0.0.0-20230202001016-cbda58c9de9e h1:KMRGDB9lh0wC/WYVmQ28MJ07qiHszCSH2PRwkw2YElM=
github.com/alcionai/clues v0.0.0-20230202001016-cbda58c9de9e/go.mod h1:UlAs8jkWIpsOMakiC8NxPgQQVQRdvyf1hYMszlYYLb4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=

View File

@ -344,7 +344,7 @@ func formatDetailsForRestoration(
deets *details.Details,
errs *fault.Errors,
) ([]path.Path, error) {
fds, err := sel.Reduce(ctx, deets)
fds, err := sel.Reduce(ctx, deets, errs)
if err != nil {
return nil, err
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/selectors"
)
@ -52,7 +53,7 @@ func Example_newSelector() {
bSel.ToOneDriveBackup()
}
// Output: OneDrive service is not Exchange: wrong selector service type
// Output: wrong selector service type: OneDrive is not Exchange
}
// ExampleIncludeFoldersAndItems demonstrates how to select for granular data.
@ -141,10 +142,11 @@ func Example_reduceDetails() {
ser := selectors.NewExchangeRestore(
[]string{"your-user-id", "foo-user-id", "bar-user-id"},
)
errAgg := fault.New(false)
// The Reduce() call is where our constructed selectors are applied to the data
// from a previous backup record.
filteredDetails := ser.Reduce(ctxBG, exampleDetails)
filteredDetails := ser.Reduce(ctxBG, exampleDetails, errAgg)
// 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))
@ -153,7 +155,7 @@ func Example_reduceDetails() {
ser.Filter(ser.MailSubject("the answer to life"))
// Now that we've selected our data, we should find a result.
filteredDetails = ser.Reduce(ctxBG, exampleDetails)
filteredDetails = ser.Reduce(ctxBG, exampleDetails, errAgg)
fmt.Println("After adding scopes:", len(filteredDetails.Entries))
// Output: Before adding scopes: 0

View File

@ -6,6 +6,7 @@ import (
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path"
)
@ -704,7 +705,11 @@ func (s ExchangeScope) setDefaults() {
// 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(ctx context.Context, deets *details.Details) *details.Details {
func (s exchange) Reduce(
ctx context.Context,
deets *details.Details,
errs fault.Adder,
) *details.Details {
return reduce[ExchangeScope](
ctx,
deets,
@ -714,7 +719,7 @@ func (s exchange) Reduce(ctx context.Context, deets *details.Details) *details.D
path.EventsCategory: ExchangeEvent,
path.EmailCategory: ExchangeMail,
},
)
errs)
}
// matchesInfo handles the standard behavior when comparing a scope and an ExchangeFilter

View File

@ -11,6 +11,7 @@ import (
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path"
)
@ -1029,10 +1030,13 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
ctx, flush := tester.NewContext()
defer flush()
errs := mock.NewAdder()
sel := test.makeSelector()
results := sel.Reduce(ctx, test.deets)
results := sel.Reduce(ctx, test.deets, errs)
paths := results.Paths()
assert.Equal(t, test.expect, paths)
assert.Empty(t, errs.Errs)
})
}
}

View File

@ -5,6 +5,7 @@ import (
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path"
)
@ -483,7 +484,11 @@ func (s OneDriveScope) DiscreteCopy(user string) OneDriveScope {
// Reduce filters the entries in a details struct to only those that match the
// inclusions, filters, and exclusions in the selector.
func (s oneDrive) Reduce(ctx context.Context, deets *details.Details) *details.Details {
func (s oneDrive) Reduce(
ctx context.Context,
deets *details.Details,
errs fault.Adder,
) *details.Details {
return reduce[OneDriveScope](
ctx,
deets,
@ -491,7 +496,7 @@ func (s oneDrive) Reduce(ctx context.Context, deets *details.Details) *details.D
map[path.CategoryType]oneDriveCategory{
path.FilesCategory: OneDriveItem,
},
)
errs)
}
// matchesInfo handles the standard behavior when comparing a scope and an oneDriveInfo

View File

@ -11,6 +11,7 @@ import (
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/path"
)
@ -241,10 +242,13 @@ func (suite *OneDriveSelectorSuite) TestOneDriveRestore_Reduce() {
ctx, flush := tester.NewContext()
defer flush()
errs := mock.NewAdder()
sel := test.makeSelector()
results := sel.Reduce(ctx, test.deets)
results := sel.Reduce(ctx, test.deets, errs)
paths := results.Paths()
assert.Equal(t, test.expect, paths)
assert.Empty(t, errs.Errs)
})
}
}

View File

@ -5,10 +5,11 @@ import (
"golang.org/x/exp/maps"
"github.com/alcionai/clues"
D "github.com/alcionai/corso/src/internal/diagnostics"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path"
)
@ -286,6 +287,7 @@ func reduce[T scopeT, C categoryT](
deets *details.Details,
s Selector,
dataCategories map[path.CategoryType]C,
errs fault.Adder,
) *details.Details {
ctx, end := D.Span(ctx, "selectors:reduce")
defer end()
@ -311,7 +313,7 @@ func reduce[T scopeT, C categoryT](
for _, ent := range deets.Items() {
repoPath, err := path.FromDataLayerPath(ent.RepoRef, true)
if err != nil {
logger.Ctx(ctx).Debugw("transforming repoRef to path", "err", err)
errs.Add(clues.Wrap(err, "transforming repoRef to path").WithClues(ctx))
continue
}

View File

@ -9,6 +9,7 @@ import (
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path"
)
@ -273,13 +274,17 @@ func (suite *SelectorScopesSuite) TestReduce() {
ctx, flush := tester.NewContext()
defer flush()
errs := mock.NewAdder()
ds := deets()
result := reduce[mockScope](
ctx,
&ds,
test.sel().Selector,
dataCats)
dataCats,
errs)
require.NotNil(t, result)
require.Empty(t, errs.Errs, "iteration errors")
assert.Len(t, result.Entries, test.expectLen)
})
}

View File

@ -5,9 +5,11 @@ import (
"encoding/json"
"strings"
"github.com/alcionai/clues"
"github.com/pkg/errors"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path"
)
@ -30,8 +32,9 @@ var serviceToPathType = map[service]path.ServiceType{
}
var (
ErrorBadSelectorCast = errors.New("wrong selector service type")
ErrorNoMatchingItems = errors.New("no items match the specified selectors")
ErrorBadSelectorCast = errors.New("wrong selector service type")
ErrorNoMatchingItems = errors.New("no items match the specified selectors")
ErrorUnrecognizedService = errors.New("unrecognized service")
)
const (
@ -67,7 +70,7 @@ var (
const All = "All"
type Reducer interface {
Reduce(context.Context, *details.Details) *details.Details
Reduce(context.Context, *details.Details, fault.Adder) *details.Details
}
// selectorResourceOwners aggregates all discrete path category types described
@ -234,13 +237,17 @@ func (s Selector) PathService() path.ServiceType {
// from the generic selector by interpreting the selector service type rather
// than have the caller make that interpretation. Returns an error if the
// service is unsupported.
func (s Selector) Reduce(ctx context.Context, deets *details.Details) (*details.Details, error) {
func (s Selector) Reduce(
ctx context.Context,
deets *details.Details,
errs fault.Adder,
) (*details.Details, error) {
r, err := selectorAsIface[Reducer](s)
if err != nil {
return nil, err
}
return r.Reduce(ctx, deets), nil
return r.Reduce(ctx, deets, errs), nil
}
// returns the sets of path categories identified in each scope set.
@ -272,7 +279,7 @@ func selectorAsIface[T any](s Selector) (T, error) {
a, err = func() (any, error) { return s.ToSharePointRestore() }()
t = a.(T)
default:
err = errors.New("service not supported: " + s.Service.String())
err = clues.Stack(ErrorUnrecognizedService, errors.New(s.Service.String()))
}
return t, err
@ -374,7 +381,7 @@ func pathComparator() option {
}
func badCastErr(cast, is service) error {
return errors.Wrapf(ErrorBadSelectorCast, "%s service is not %s", cast, is)
return clues.Stack(ErrorBadSelectorCast, errors.Errorf("%s is not %s", cast, is))
}
func join(s ...string) string {

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/selectors"
"github.com/alcionai/corso/src/pkg/selectors/testdata"
)
@ -264,8 +265,11 @@ func (suite *SelectorReduceSuite) TestReduce() {
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
output := test.selFunc().Reduce(ctx, allDetails)
errs := mock.NewAdder()
output := test.selFunc().Reduce(ctx, allDetails, errs)
assert.ElementsMatch(t, test.expected, output.Entries)
assert.Empty(t, errs.Errs)
})
}
}

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
)
@ -555,7 +556,11 @@ func (s SharePointScope) DiscreteCopy(site string) SharePointScope {
// Reduce filters the entries in a details struct to only those that match the
// inclusions, filters, and exclusions in the selector.
func (s sharePoint) Reduce(ctx context.Context, deets *details.Details) *details.Details {
func (s sharePoint) Reduce(
ctx context.Context,
deets *details.Details,
errs fault.Adder,
) *details.Details {
return reduce[SharePointScope](
ctx,
deets,
@ -565,7 +570,7 @@ func (s sharePoint) Reduce(ctx context.Context, deets *details.Details) *details
path.ListsCategory: SharePointListItem,
path.PagesCategory: SharePointPage,
},
)
errs)
}
// matchesInfo handles the standard behavior when comparing a scope and an sharePointInfo

View File

@ -9,6 +9,7 @@ import (
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/path"
)
@ -305,10 +306,13 @@ func (suite *SharePointSelectorSuite) TestSharePointRestore_Reduce() {
ctx, flush := tester.NewContext()
defer flush()
errs := mock.NewAdder()
sel := test.makeSelector()
results := sel.Reduce(ctx, test.deets)
results := sel.Reduce(ctx, test.deets, errs)
paths := results.Paths()
assert.Equal(t, test.expect, paths)
assert.Empty(t, errs.Errs)
})
}
}