## Description * No new functionality exposed to CLI users * generate restore folder names in CLI and pass down the stack * update tests for new parameters * centralize generation of restore container names ## Type of change <!--- Please check the type of change your PR introduces: ---> - [x] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Test - [ ] 💻 CI/Deployment - [ ] 🐹 Trivial/Minor ## Issue(s) * #897 * #913 ## Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [x] ⚡ Unit test - [x] 💚 E2E
285 lines
7.8 KiB
Go
285 lines
7.8 KiB
Go
package operations
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
multierror "github.com/hashicorp/go-multierror"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"github.com/alcionai/corso/src/internal/common"
|
|
"github.com/alcionai/corso/src/internal/connector/exchange"
|
|
"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))
|
|
}
|
|
|
|
// TODO: after modelStore integration is added, mock the store and/or
|
|
// move this to an integration test.
|
|
func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
|
|
var (
|
|
t = suite.T()
|
|
ctx = context.Background()
|
|
|
|
kw = &kopia.Wrapper{}
|
|
sw = &store.Wrapper{}
|
|
acct = account.Account{}
|
|
now = time.Now()
|
|
rs = restoreStats{
|
|
started: true,
|
|
readErr: multierror.Append(nil, assert.AnError),
|
|
writeErr: assert.AnError,
|
|
resourceCount: 1,
|
|
bytesRead: &stats.ByteCounter{
|
|
NumBytes: 42,
|
|
},
|
|
cs: []data.Collection{&exchange.Collection{}},
|
|
gc: &support.ConnectorOperationStatus{
|
|
ObjectCount: 1,
|
|
},
|
|
}
|
|
dest = control.DefaultRestoreDestination(common.SimpleDateTimeFormat)
|
|
)
|
|
|
|
op, err := NewRestoreOperation(
|
|
ctx,
|
|
control.Options{},
|
|
kw,
|
|
sw,
|
|
acct,
|
|
"foo",
|
|
selectors.Selector{},
|
|
dest,
|
|
evmock.NewBus())
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, op.persistResults(ctx, now, &rs))
|
|
|
|
assert.Equal(t, op.Status.String(), Completed.String(), "status")
|
|
assert.Equal(t, op.Results.ItemsRead, len(rs.cs), "items read")
|
|
assert.Equal(t, op.Results.ReadErrors, rs.readErr, "read errors")
|
|
assert.Equal(t, op.Results.ItemsWritten, rs.gc.Successful, "items written")
|
|
assert.Equal(t, rs.bytesRead.NumBytes, op.Results.BytesRead, "resource owners")
|
|
assert.Equal(t, rs.resourceCount, op.Results.ResourceOwners, "resource owners")
|
|
assert.Equal(t, op.Results.WriteErrors, rs.writeErr, "write errors")
|
|
assert.Equal(t, op.Results.StartedAt, now, "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) {
|
|
if err := tester.RunOnAny(
|
|
tester.CorsoCITests,
|
|
tester.CorsoOperationTests,
|
|
); err != nil {
|
|
t.Skip(err)
|
|
}
|
|
|
|
suite.Run(t, new(RestoreOpIntegrationSuite))
|
|
}
|
|
|
|
func (suite *RestoreOpIntegrationSuite) SetupSuite() {
|
|
_, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...)
|
|
require.NoError(suite.T(), err)
|
|
|
|
t := suite.T()
|
|
ctx := context.Background()
|
|
|
|
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
|
|
|
|
bsel := selectors.NewExchangeBackup()
|
|
bsel.Include(bsel.MailFolders([]string{m365UserID}, []string{exchange.DefaultMailFolder}))
|
|
|
|
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
|
|
suite.numItems = bo.Results.ItemsWritten
|
|
}
|
|
|
|
func (suite *RestoreOpIntegrationSuite) TearDownSuite() {
|
|
ctx := context.Background()
|
|
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 := control.DefaultRestoreDestination(common.SimpleDateTimeFormat)
|
|
|
|
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) {
|
|
_, err := NewRestoreOperation(
|
|
context.Background(),
|
|
test.opts,
|
|
test.kw,
|
|
test.sw,
|
|
test.acct,
|
|
"backup-id",
|
|
selectors.Selector{},
|
|
dest,
|
|
evmock.NewBus())
|
|
test.errCheck(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
|
t := suite.T()
|
|
ctx := context.Background()
|
|
|
|
rsel := selectors.NewExchangeRestore()
|
|
rsel.Include(rsel.Users([]string{tester.M365UserID(t)}))
|
|
|
|
dest := control.DefaultRestoreDestination(common.SimpleDateTimeFormat)
|
|
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)
|
|
|
|
require.NoError(t, ro.Run(ctx), "restoreOp.Run()")
|
|
require.NotEmpty(t, ro.Results, "restoreOp results")
|
|
assert.Equal(t, ro.Status, Completed, "restoreOp status")
|
|
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.Zero(t, ro.Results.ReadErrors, "errors while reading restore data")
|
|
assert.Zero(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() {
|
|
t := suite.T()
|
|
ctx := context.Background()
|
|
|
|
rsel := selectors.NewExchangeRestore()
|
|
rsel.Include(rsel.Users(selectors.None()))
|
|
|
|
dest := control.DefaultRestoreDestination(common.SimpleDateTimeFormat)
|
|
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)
|
|
require.Error(t, ro.Run(ctx), "restoreOp.Run() should have 0 results")
|
|
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")
|
|
}
|