corso/src/internal/operations/restore_test.go
ryanfkeepers 7b50d2401a remove errors from connector status
Now that fault is in place, we can remove the error
tracking functionality of graph status, and let that
focus purely on metrics.
2023-02-20 17:21:03 -07:00

344 lines
9.3 KiB
Go

package operations
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/connector/exchange"
"github.com/alcionai/corso/src/internal/connector/mockconnector"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/events"
evmock "github.com/alcionai/corso/src/internal/events/mock"
"github.com/alcionai/corso/src/internal/kopia"
"github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/internal/stats"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/selectors"
"github.com/alcionai/corso/src/pkg/store"
)
// ---------------------------------------------------------------------------
// unit
// ---------------------------------------------------------------------------
type RestoreOpSuite struct {
suite.Suite
}
func TestRestoreOpSuite(t *testing.T) {
suite.Run(t, new(RestoreOpSuite))
}
func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
ctx, flush := tester.NewContext()
defer flush()
var (
kw = &kopia.Wrapper{}
sw = &store.Wrapper{}
acct = account.Account{}
now = time.Now()
dest = tester.DefaultTestRestoreDestination()
)
table := []struct {
expectStatus opStatus
expectErr assert.ErrorAssertionFunc
stats restoreStats
}{
{
expectStatus: Completed,
expectErr: assert.NoError,
stats: restoreStats{
resourceCount: 1,
bytesRead: &stats.ByteCounter{
NumBytes: 42,
},
cs: []data.RestoreCollection{
data.NotFoundRestoreCollection{
Collection: &mockconnector.MockExchangeDataCollection{},
},
},
gc: &support.ConnectorOperationStatus{
Metrics: support.CollectionMetrics{
Objects: 1,
Successes: 1,
},
},
},
},
{
expectStatus: Failed,
expectErr: assert.Error,
stats: restoreStats{
readErr: assert.AnError,
bytesRead: &stats.ByteCounter{},
gc: &support.ConnectorOperationStatus{},
},
},
{
expectStatus: NoData,
expectErr: assert.NoError,
stats: restoreStats{
bytesRead: &stats.ByteCounter{},
cs: []data.RestoreCollection{},
gc: &support.ConnectorOperationStatus{},
},
},
}
for _, test := range table {
suite.T().Run(test.expectStatus.String(), func(t *testing.T) {
op, err := NewRestoreOperation(
ctx,
control.Options{},
kw,
sw,
acct,
"foo",
selectors.Selector{DiscreteOwner: "test"},
dest,
evmock.NewBus())
require.NoError(t, err)
test.expectErr(t, op.persistResults(ctx, now, &test.stats))
assert.Equal(t, test.expectStatus.String(), op.Status.String(), "status")
assert.Equal(t, len(test.stats.cs), op.Results.ItemsRead, "items read")
assert.Equal(t, test.stats.gc.Metrics.Successes, op.Results.ItemsWritten, "items written")
assert.Equal(t, test.stats.bytesRead.NumBytes, op.Results.BytesRead, "resource owners")
assert.Equal(t, test.stats.resourceCount, op.Results.ResourceOwners, "resource owners")
assert.Equal(t, test.stats.readErr, op.Results.ReadErrors, "read errors")
assert.Equal(t, test.stats.writeErr, op.Results.WriteErrors, "write errors")
assert.Equal(t, now, op.Results.StartedAt, "started at")
assert.Less(t, now, op.Results.CompletedAt, "completed at")
})
}
}
// ---------------------------------------------------------------------------
// integration
// ---------------------------------------------------------------------------
type RestoreOpIntegrationSuite struct {
suite.Suite
backupID model.StableID
numItems int
kopiaCloser func(ctx context.Context)
kw *kopia.Wrapper
sw *store.Wrapper
ms *kopia.ModelStore
}
func TestRestoreOpIntegrationSuite(t *testing.T) {
tester.RunOnAny(
t,
tester.CorsoCITests,
tester.CorsoOperationTests)
suite.Run(t, new(RestoreOpIntegrationSuite))
}
func (suite *RestoreOpIntegrationSuite) SetupSuite() {
ctx, flush := tester.NewContext()
defer flush()
tester.MustGetEnvSets(suite.T(), tester.M365AcctCredEnvs)
t := suite.T()
m365UserID := tester.M365UserID(t)
acct := tester.NewM365Account(t)
// need to initialize the repository before we can test connecting to it.
st := tester.NewPrefixedS3Storage(t)
k := kopia.NewConn(st)
require.NoError(t, k.Initialize(ctx))
suite.kopiaCloser = func(ctx context.Context) {
k.Close(ctx)
}
kw, err := kopia.NewWrapper(k)
require.NoError(t, err)
suite.kw = kw
ms, err := kopia.NewModelStore(k)
require.NoError(t, err)
suite.ms = ms
sw := store.NewKopiaStore(ms)
suite.sw = sw
users := []string{m365UserID}
bsel := selectors.NewExchangeBackup(users)
bsel.DiscreteOwner = m365UserID
bsel.Include(
bsel.MailFolders([]string{exchange.DefaultMailFolder}, selectors.PrefixMatch()),
bsel.ContactFolders([]string{exchange.DefaultContactFolder}, selectors.PrefixMatch()),
bsel.EventCalendars([]string{exchange.DefaultCalendar}, selectors.PrefixMatch()),
)
bo, err := NewBackupOperation(
ctx,
control.Options{},
kw,
sw,
acct,
bsel.Selector,
evmock.NewBus())
require.NoError(t, err)
require.NoError(t, bo.Run(ctx))
require.NotEmpty(t, bo.Results.BackupID)
suite.backupID = bo.Results.BackupID
// Discount metadata files (3 paths, 3 deltas) as
// they are not part of the data restored.
suite.numItems = bo.Results.ItemsWritten - 6
}
func (suite *RestoreOpIntegrationSuite) TearDownSuite() {
ctx, flush := tester.NewContext()
defer flush()
if suite.ms != nil {
suite.ms.Close(ctx)
}
if suite.kw != nil {
suite.kw.Close(ctx)
}
if suite.kopiaCloser != nil {
suite.kopiaCloser(ctx)
}
}
func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
kw := &kopia.Wrapper{}
sw := &store.Wrapper{}
acct := tester.NewM365Account(suite.T())
dest := tester.DefaultTestRestoreDestination()
table := []struct {
name string
opts control.Options
kw *kopia.Wrapper
sw *store.Wrapper
acct account.Account
targets []string
errCheck assert.ErrorAssertionFunc
}{
{"good", control.Options{}, kw, sw, acct, nil, assert.NoError},
{"missing kopia", control.Options{}, nil, sw, acct, nil, assert.Error},
{"missing modelstore", control.Options{}, kw, nil, acct, nil, assert.Error},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
ctx, flush := tester.NewContext()
defer flush()
_, err := NewRestoreOperation(
ctx,
test.opts,
test.kw,
test.sw,
test.acct,
"backup-id",
selectors.Selector{DiscreteOwner: "test"},
dest,
evmock.NewBus())
test.errCheck(t, err)
})
}
}
func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
users := []string{tester.M365UserID(t)}
rsel := selectors.NewExchangeRestore(users)
rsel.Include(rsel.AllData())
dest := tester.DefaultTestRestoreDestination()
mb := evmock.NewBus()
ro, err := NewRestoreOperation(
ctx,
control.Options{},
suite.kw,
suite.sw,
tester.NewM365Account(t),
suite.backupID,
rsel.Selector,
dest,
mb)
require.NoError(t, err)
ds, err := ro.Run(ctx)
require.NoError(t, err, "restoreOp.Run()")
require.NotEmpty(t, ro.Results, "restoreOp results")
require.NotNil(t, ds, "restored details")
assert.Equal(t, ro.Status, Completed, "restoreOp status")
assert.Equal(t, ro.Results.ItemsWritten, len(ds.Entries), "count of items written matches restored entries in details")
assert.Less(t, 0, ro.Results.ItemsRead, "restore items read")
assert.Less(t, 0, ro.Results.ItemsWritten, "restored items written")
assert.Less(t, int64(0), ro.Results.BytesRead, "bytes read")
assert.Equal(t, 1, ro.Results.ResourceOwners, "resource Owners")
assert.NoError(t, ro.Errors.Failure(), "non-recoverable error")
assert.Empty(t, ro.Errors.Recovered(), "recoverable errors")
assert.NoError(t, ro.Results.ReadErrors, "errors while reading restore data")
assert.NoError(t, ro.Results.WriteErrors, "errors while writing restore data")
assert.Equal(t, suite.numItems, ro.Results.ItemsWritten, "backup and restore wrote the same num of items")
assert.Equal(t, 1, mb.TimesCalled[events.RestoreStart], "restore-start events")
assert.Equal(t, 1, mb.TimesCalled[events.RestoreEnd], "restore-end events")
}
func (suite *RestoreOpIntegrationSuite) TestRestore_Run_ErrorNoResults() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
rsel := selectors.NewExchangeRestore(selectors.None())
rsel.Include(rsel.AllData())
dest := tester.DefaultTestRestoreDestination()
mb := evmock.NewBus()
ro, err := NewRestoreOperation(
ctx,
control.Options{},
suite.kw,
suite.sw,
tester.NewM365Account(t),
suite.backupID,
rsel.Selector,
dest,
mb)
require.NoError(t, err)
ds, err := ro.Run(ctx)
require.Error(t, err, "restoreOp.Run() should have errored")
require.Nil(t, ds, "restoreOp.Run() should not produce details")
assert.Zero(t, ro.Results.ResourceOwners, "resource owners")
assert.Zero(t, ro.Results.BytesRead, "bytes read")
assert.Equal(t, 1, mb.TimesCalled[events.RestoreStart], "restore-start events")
assert.Zero(t, mb.TimesCalled[events.RestoreEnd], "restore-end events")
}