From 824f02469c262ddbdd2d9df0851e13c948ba26da Mon Sep 17 00:00:00 2001 From: ashmrtn Date: Fri, 23 Sep 2022 09:51:08 -0700 Subject: [PATCH] Backup details list exchange tests (#945) ## Description Add tests for the `backup list exchange` subcommand. Tests mostly center around indirectly testing how selectors are created and used in this subcommand by checking the output of running the `Reduce` call on a known set of `details.DetailsEntry`s. Also check various error cases Tests for invalid formats of flag values are disabled as that code does not currently exist. These tests can be enabled when #943 is resolved ## Type of change - [ ] :sunflower: Feature - [ ] :bug: Bugfix - [ ] :world_map: Documentation - [x] :robot: Test - [ ] :computer: CI/Deployment - [ ] :hamster: Trivial/Minor ## Issue(s) * #913 ## Test Plan - [ ] :muscle: Manual - [x] :zap: Unit test - [ ] :green_heart: E2E --- src/cli/backup/exchange.go | 46 +++++--- src/cli/backup/exchange_test.go | 55 ++++++++++ src/cli/utils/testdata/opts.go | 181 ++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+), 14 deletions(-) create mode 100644 src/cli/utils/testdata/opts.go diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index b95d3e6d7..25fc4cbb8 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -1,6 +1,8 @@ package backup import ( + "context" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -11,6 +13,7 @@ import ( "github.com/alcionai/corso/src/cli/utils" "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/control" "github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/selectors" @@ -353,11 +356,6 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { defer utils.CloseRepo(ctx, r) - d, _, err := r.BackupDetails(ctx, backupID) - if err != nil { - return Only(ctx, errors.Wrap(err, "Failed to get backup details in the repository")) - } - opts := utils.ExchangeOpts{ Contacts: contact, ContactFolders: contactFolder, @@ -378,6 +376,34 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { EventSubject: eventSubject, } + ds, err := runDetailsExchangeCmd(ctx, r, backupID, opts) + if err != nil { + return Only(ctx, err) + } + + if len(ds.Entries) == 0 { + Info(ctx, selectors.ErrorNoMatchingItems) + return nil + } + + ds.PrintEntries(ctx) + + return nil +} + +// runDetailsExchangeCmd actually performs the lookup in backup details. Assumes +// len(backupID) > 0. +func runDetailsExchangeCmd( + ctx context.Context, + r repository.BackupGetter, + backupID string, + opts utils.ExchangeOpts, +) (*details.Details, error) { + d, _, err := r.BackupDetails(ctx, backupID) + if err != nil { + return nil, errors.Wrap(err, "Failed to get backup details in the repository") + } + sel := selectors.NewExchangeRestore() utils.IncludeExchangeRestoreDataSelectors(sel, opts) utils.FilterExchangeRestoreInfoSelectors(sel, opts) @@ -387,15 +413,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { sel.Include(sel.Users(selectors.Any())) } - ds := sel.Reduce(ctx, d) - if len(ds.Entries) == 0 { - Info(ctx, selectors.ErrorNoMatchingItems) - return nil - } - - ds.PrintEntries(ctx) - - return nil + return sel.Reduce(ctx, d), nil } // ------------------------------------------------------------------------------------------------ diff --git a/src/cli/backup/exchange_test.go b/src/cli/backup/exchange_test.go index 225e179da..9ac1bad5f 100644 --- a/src/cli/backup/exchange_test.go +++ b/src/cli/backup/exchange_test.go @@ -1,6 +1,7 @@ package backup import ( + "context" "testing" "github.com/spf13/cobra" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/alcionai/corso/src/cli/utils" + "github.com/alcionai/corso/src/cli/utils/testdata" "github.com/alcionai/corso/src/internal/tester" ) @@ -214,3 +216,56 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { }) } } + +func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectors() { + ctx := context.Background() + + for _, test := range testdata.ExchangeOptionDetailLookups { + suite.T().Run(test.Name, func(t *testing.T) { + output, err := runDetailsExchangeCmd( + ctx, + test.BackupGetter, + "backup-ID", + test.Opts, + ) + assert.NoError(t, err) + + assert.ElementsMatch(t, test.Expected, output.Entries) + }) + } +} + +func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectorsBadBackupID() { + t := suite.T() + ctx := context.Background() + backupGetter := &testdata.MockBackupGetter{} + + output, err := runDetailsExchangeCmd( + ctx, + backupGetter, + "backup-ID", + utils.ExchangeOpts{}, + ) + assert.Error(t, err) + + assert.Empty(t, output) +} + +// TODO(ashmrtn): Uncomment these when the CLI validates flag input values. +//func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectorsBadFormats() { +// ctx := context.Background() +// +// for _, test := range testdata.BadExchangeOptionsFormats { +// suite.T().Run(test.Name, func(t *testing.T) { +// output, err := runDetailsExchangeCmd( +// ctx, +// test.BackupGetter, +// "backup-ID", +// test.Opts, +// ) +// assert.Error(t, err) +// +// assert.Empty(t, output) +// }) +// } +//} diff --git a/src/cli/utils/testdata/opts.go b/src/cli/utils/testdata/opts.go new file mode 100644 index 000000000..6e66e0809 --- /dev/null +++ b/src/cli/utils/testdata/opts.go @@ -0,0 +1,181 @@ +package testdata + +import ( + "context" + "errors" + "time" + + "github.com/alcionai/corso/src/cli/utils" + "github.com/alcionai/corso/src/internal/common" + "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/selectors" + "github.com/alcionai/corso/src/pkg/selectors/testdata" +) + +type ExchangeOptionsTest struct { + Name string + Opts utils.ExchangeOpts + BackupGetter *MockBackupGetter + Expected []details.DetailsEntry +} + +var ( + + // BadExchangeOptionsFormats contains ExchangeOpts with flags that should + // cause errors about the format of the input flag. Mocks are configured to + // allow the system to run if it doesn't throw an error on formatting. + BadExchangeOptionsFormats = []ExchangeOptionsTest{ + { + Name: "BadEmailReceiveAfter", + Opts: utils.ExchangeOpts{ + EmailReceivedAfter: "foo", + }, + }, + { + Name: "BadEmailReceiveBefore", + Opts: utils.ExchangeOpts{ + EmailReceivedBefore: "foo", + }, + }, + { + Name: "BadEventRecurs", + Opts: utils.ExchangeOpts{ + EventRecurs: "foo", + }, + }, + { + Name: "BadEventStartsAfter", + Opts: utils.ExchangeOpts{ + EventStartsAfter: "foo", + }, + }, + { + Name: "BadEventStartsBefore", + Opts: utils.ExchangeOpts{ + EventStartsBefore: "foo", + }, + }, + } + + // ExchangeOptionDetailLookups contains flag inputs and expected results for + // some choice input patterns. This set is not exhaustive. All inputs and + // outputs are according to the data laid out in selectors/testdata. Mocks are + // configured to return the full dataset listed in selectors/testdata. + ExchangeOptionDetailLookups = []ExchangeOptionsTest{ + { + Name: "Emails", + Expected: testdata.ExchangeEmailItems, + Opts: utils.ExchangeOpts{ + Emails: selectors.Any(), + }, + }, + { + Name: "EmailsBySubject", + Expected: testdata.ExchangeEmailItems, + Opts: utils.ExchangeOpts{ + EmailSender: "a-person", + }, + }, + { + Name: "AllExchange", + Expected: append( + append( + append( + []details.DetailsEntry{}, + testdata.ExchangeEmailItems..., + ), + testdata.ExchangeContactsItems..., + ), + testdata.ExchangeEventsItems..., + ), + }, + { + Name: "MailReceivedTime", + Expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]}, + Opts: utils.ExchangeOpts{ + EmailReceivedBefore: common.FormatTime(testdata.Time1.Add(time.Second)), + }, + }, + { + Name: "MailID", + Expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]}, + Opts: utils.ExchangeOpts{ + Emails: []string{testdata.ExchangeEmailItemPath1.Item()}, + }, + }, + { + Name: "MailShortRef", + Expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]}, + Opts: utils.ExchangeOpts{ + Emails: []string{testdata.ExchangeEmailItemPath1.ShortRef()}, + }, + }, + { + Name: "MultipleMailShortRef", + Expected: testdata.ExchangeEmailItems, + Opts: utils.ExchangeOpts{ + Emails: []string{ + testdata.ExchangeEmailItemPath1.ShortRef(), + testdata.ExchangeEmailItemPath2.ShortRef(), + }, + }, + }, + { + Name: "AllEventsAndMailWithSubject", + Expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]}, + Opts: utils.ExchangeOpts{ + EmailSubject: "foo", + Events: selectors.Any(), + }, + }, + { + Name: "EventsAndMailWithSubject", + Expected: []details.DetailsEntry{}, + Opts: utils.ExchangeOpts{ + EmailSubject: "foo", + EventSubject: "foo", + }, + }, + { + Name: "EventsAndMailByShortRef", + Expected: []details.DetailsEntry{ + testdata.ExchangeEmailItems[0], + testdata.ExchangeEventsItems[0], + }, + Opts: utils.ExchangeOpts{ + Emails: []string{testdata.ExchangeEmailItemPath1.ShortRef()}, + Events: []string{testdata.ExchangeEventsItemPath1.ShortRef()}, + }, + }, + } +) + +// MockBackupGetter implements the repo.BackupGetter interface and returns +// (selectors/testdata.GetDetailsSet(), nil, nil) when BackupDetails is called +// on the nil instance. If an instance is given or Backups is called returns an +// error. +type MockBackupGetter struct{} + +func (MockBackupGetter) Backup( + context.Context, + model.StableID, +) (*backup.Backup, error) { + return nil, errors.New("unexpected call to mock") +} + +func (MockBackupGetter) Backups(context.Context) ([]backup.Backup, error) { + return nil, errors.New("unexpected call to mock") +} + +func (bg *MockBackupGetter) BackupDetails( + ctx context.Context, + backupID string, +) (*details.Details, *backup.Backup, error) { + if bg == nil { + return testdata.GetDetailsSet(), nil, nil + } + + return nil, nil, errors.New("unexpected call to mock") +}