533 lines
14 KiB
Go
533 lines
14 KiB
Go
package operations
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/alcionai/clues"
|
|
"github.com/kopia/kopia/repo/manifest"
|
|
"github.com/kopia/kopia/snapshot"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"github.com/alcionai/corso/src/internal/data"
|
|
"github.com/alcionai/corso/src/internal/kopia"
|
|
"github.com/alcionai/corso/src/internal/tester"
|
|
"github.com/alcionai/corso/src/pkg/backup/identity"
|
|
"github.com/alcionai/corso/src/pkg/fault"
|
|
"github.com/alcionai/corso/src/pkg/path"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// interfaces
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type mockColl struct {
|
|
id string // for comparisons
|
|
p path.Path
|
|
}
|
|
|
|
func (mc mockColl) Items(context.Context, *fault.Bus) <-chan data.Item {
|
|
return nil
|
|
}
|
|
|
|
func (mc mockColl) FullPath() path.Path {
|
|
return mc.p
|
|
}
|
|
|
|
type mockBackupFinder struct {
|
|
// ResourceOwner -> returned set of data for call to FindBases. We can just
|
|
// switch on the ResourceOwner as the passed in Reasons should be the same
|
|
// beyond that and results are returned for the union of the reasons anyway.
|
|
// This does assume that the return data is properly constructed to return a
|
|
// union of the reasons etc.
|
|
data map[string]kopia.BackupBases
|
|
}
|
|
|
|
func (bf *mockBackupFinder) FindBases(
|
|
_ context.Context,
|
|
reasons []identity.Reasoner,
|
|
_ map[string]string,
|
|
) kopia.BackupBases {
|
|
if len(reasons) == 0 {
|
|
return kopia.NewMockBackupBases()
|
|
}
|
|
|
|
if bf == nil {
|
|
return kopia.NewMockBackupBases()
|
|
}
|
|
|
|
b := bf.data[reasons[0].ProtectedResource()]
|
|
if b == nil {
|
|
return kopia.NewMockBackupBases()
|
|
}
|
|
|
|
return b
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type OperationsManifestsUnitSuite struct {
|
|
tester.Suite
|
|
}
|
|
|
|
func TestOperationsManifestsUnitSuite(t *testing.T) {
|
|
suite.Run(t, &OperationsManifestsUnitSuite{Suite: tester.NewUnitSuite(t)})
|
|
}
|
|
|
|
func (suite *OperationsManifestsUnitSuite) TestCollectMetadata() {
|
|
const (
|
|
ro = "owner"
|
|
tid = "tenantid"
|
|
)
|
|
|
|
var (
|
|
emailPath = makeMetadataBasePath(
|
|
suite.T(),
|
|
tid,
|
|
path.ExchangeService,
|
|
ro,
|
|
path.EmailCategory)
|
|
contactPath = makeMetadataBasePath(
|
|
suite.T(),
|
|
tid,
|
|
path.ExchangeService,
|
|
ro,
|
|
path.ContactsCategory)
|
|
)
|
|
|
|
table := []struct {
|
|
name string
|
|
manID string
|
|
reasons []identity.Reasoner
|
|
fileNames []string
|
|
expectPaths func(*testing.T, []string) []path.Path
|
|
expectErr error
|
|
}{
|
|
{
|
|
name: "single reason, single file",
|
|
manID: "single single",
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason(tid, ro, path.ExchangeService, path.EmailCategory),
|
|
},
|
|
expectPaths: func(t *testing.T, files []string) []path.Path {
|
|
ps := make([]path.Path, 0, len(files))
|
|
|
|
for _, f := range files {
|
|
p, err := emailPath.AppendItem(f)
|
|
assert.NoError(t, err, clues.ToCore(err))
|
|
ps = append(ps, p)
|
|
}
|
|
|
|
return ps
|
|
},
|
|
fileNames: []string{"a"},
|
|
},
|
|
{
|
|
name: "single reason, multiple files",
|
|
manID: "single multi",
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason(tid, ro, path.ExchangeService, path.EmailCategory),
|
|
},
|
|
expectPaths: func(t *testing.T, files []string) []path.Path {
|
|
ps := make([]path.Path, 0, len(files))
|
|
|
|
for _, f := range files {
|
|
p, err := emailPath.AppendItem(f)
|
|
assert.NoError(t, err, clues.ToCore(err))
|
|
ps = append(ps, p)
|
|
}
|
|
|
|
return ps
|
|
},
|
|
fileNames: []string{"a", "b"},
|
|
},
|
|
{
|
|
name: "multiple reasons, single file",
|
|
manID: "multi single",
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason(tid, ro, path.ExchangeService, path.EmailCategory),
|
|
kopia.NewReason(tid, ro, path.ExchangeService, path.ContactsCategory),
|
|
},
|
|
expectPaths: func(t *testing.T, files []string) []path.Path {
|
|
ps := make([]path.Path, 0, len(files))
|
|
|
|
for _, f := range files {
|
|
p, err := emailPath.AppendItem(f)
|
|
assert.NoError(t, err, clues.ToCore(err))
|
|
ps = append(ps, p)
|
|
p, err = contactPath.AppendItem(f)
|
|
assert.NoError(t, err, clues.ToCore(err))
|
|
ps = append(ps, p)
|
|
}
|
|
|
|
return ps
|
|
},
|
|
fileNames: []string{"a"},
|
|
},
|
|
{
|
|
name: "multiple reasons, multiple file",
|
|
manID: "multi multi",
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason(tid, ro, path.ExchangeService, path.EmailCategory),
|
|
kopia.NewReason(tid, ro, path.ExchangeService, path.ContactsCategory),
|
|
},
|
|
expectPaths: func(t *testing.T, files []string) []path.Path {
|
|
ps := make([]path.Path, 0, len(files))
|
|
|
|
for _, f := range files {
|
|
p, err := emailPath.AppendItem(f)
|
|
assert.NoError(t, err, clues.ToCore(err))
|
|
ps = append(ps, p)
|
|
p, err = contactPath.AppendItem(f)
|
|
assert.NoError(t, err, clues.ToCore(err))
|
|
ps = append(ps, p)
|
|
}
|
|
|
|
return ps
|
|
},
|
|
fileNames: []string{"a", "b"},
|
|
},
|
|
}
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
t := suite.T()
|
|
|
|
ctx, flush := tester.NewContext(t)
|
|
defer flush()
|
|
|
|
paths := test.expectPaths(t, test.fileNames)
|
|
|
|
mr := mockRestoreProducer{err: test.expectErr}
|
|
mr.buildRestoreFunc(t, test.manID, paths)
|
|
|
|
man := kopia.ManifestEntry{
|
|
Manifest: &snapshot.Manifest{ID: manifest.ID(test.manID)},
|
|
Reasons: test.reasons,
|
|
}
|
|
|
|
_, err := collectMetadata(ctx, &mr, man, test.fileNames, tid, fault.New(true))
|
|
assert.ErrorIs(t, err, test.expectErr, clues.ToCore(err))
|
|
})
|
|
}
|
|
}
|
|
|
|
func buildReasons(
|
|
ro string,
|
|
service path.ServiceType,
|
|
cats ...path.CategoryType,
|
|
) []identity.Reasoner {
|
|
var reasons []identity.Reasoner
|
|
|
|
for _, cat := range cats {
|
|
reasons = append(
|
|
reasons,
|
|
kopia.NewReason("", ro, service, cat))
|
|
}
|
|
|
|
return reasons
|
|
}
|
|
|
|
func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata() {
|
|
const (
|
|
ro = "resourceowner"
|
|
tid = "tenantid"
|
|
did = "detailsid"
|
|
)
|
|
|
|
makeMan := func(id, incmpl string, cats ...path.CategoryType) kopia.ManifestEntry {
|
|
return kopia.ManifestEntry{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: manifest.ID(id),
|
|
IncompleteReason: incmpl,
|
|
},
|
|
Reasons: buildReasons(ro, path.ExchangeService, cats...),
|
|
}
|
|
}
|
|
|
|
table := []struct {
|
|
name string
|
|
bf *mockBackupFinder
|
|
rp mockRestoreProducer
|
|
reasons []identity.Reasoner
|
|
getMeta bool
|
|
dropAssist bool
|
|
assertErr assert.ErrorAssertionFunc
|
|
assertB assert.BoolAssertionFunc
|
|
expectDCS []mockColl
|
|
expectPaths func(t *testing.T, gotPaths []path.Path)
|
|
expectMans kopia.BackupBases
|
|
}{
|
|
{
|
|
name: "don't get metadata, no mans",
|
|
rp: mockRestoreProducer{},
|
|
reasons: []identity.Reasoner{},
|
|
getMeta: false,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.False,
|
|
expectDCS: nil,
|
|
expectMans: kopia.NewMockBackupBases(),
|
|
},
|
|
{
|
|
name: "don't get metadata",
|
|
bf: &mockBackupFinder{
|
|
data: map[string]kopia.BackupBases{
|
|
ro: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory),
|
|
),
|
|
},
|
|
},
|
|
rp: mockRestoreProducer{},
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason("", ro, path.ExchangeService, path.EmailCategory),
|
|
},
|
|
getMeta: false,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.False,
|
|
expectDCS: nil,
|
|
expectMans: kopia.NewMockBackupBases().WithAssistBases(
|
|
makeMan("id1", "", path.EmailCategory),
|
|
),
|
|
},
|
|
{
|
|
name: "don't get metadata, incomplete manifest",
|
|
bf: &mockBackupFinder{
|
|
data: map[string]kopia.BackupBases{
|
|
ro: kopia.NewMockBackupBases().WithAssistBases(
|
|
makeMan("id1", "checkpoint", path.EmailCategory),
|
|
),
|
|
},
|
|
},
|
|
rp: mockRestoreProducer{},
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason("", ro, path.ExchangeService, path.EmailCategory),
|
|
},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
// Doesn't matter if it's true or false as merge/assist bases are
|
|
// distinct. A future PR can go and remove the requirement to pass the
|
|
// flag to kopia and just pass it the bases instead.
|
|
assertB: assert.True,
|
|
expectDCS: nil,
|
|
expectMans: kopia.NewMockBackupBases().WithAssistBases(
|
|
makeMan("id1", "checkpoint", path.EmailCategory),
|
|
),
|
|
},
|
|
{
|
|
name: "one valid man, multiple reasons",
|
|
bf: &mockBackupFinder{
|
|
data: map[string]kopia.BackupBases{
|
|
ro: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory, path.ContactsCategory),
|
|
),
|
|
},
|
|
},
|
|
rp: mockRestoreProducer{
|
|
collsByID: map[string][]data.RestoreCollection{
|
|
"id1": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id1"}}},
|
|
},
|
|
},
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason("", ro, path.ExchangeService, path.EmailCategory),
|
|
kopia.NewReason("", ro, path.ExchangeService, path.ContactsCategory),
|
|
},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.True,
|
|
expectDCS: []mockColl{{id: "id1"}},
|
|
expectPaths: func(t *testing.T, gotPaths []path.Path) {
|
|
for _, p := range gotPaths {
|
|
assert.Equal(
|
|
t,
|
|
path.ExchangeMetadataService,
|
|
p.Service(),
|
|
"read data service")
|
|
|
|
assert.Contains(
|
|
t,
|
|
[]path.CategoryType{
|
|
path.EmailCategory,
|
|
path.ContactsCategory,
|
|
},
|
|
p.Category(),
|
|
"read data category doesn't match a given reason",
|
|
)
|
|
}
|
|
},
|
|
expectMans: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory, path.ContactsCategory),
|
|
),
|
|
},
|
|
{
|
|
name: "one valid man, extra incomplete man",
|
|
bf: &mockBackupFinder{
|
|
data: map[string]kopia.BackupBases{
|
|
ro: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory),
|
|
).WithAssistBases(
|
|
makeMan("id2", "checkpoint", path.EmailCategory),
|
|
),
|
|
},
|
|
},
|
|
rp: mockRestoreProducer{
|
|
collsByID: map[string][]data.RestoreCollection{
|
|
"id1": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id1"}}},
|
|
"id2": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id2"}}},
|
|
},
|
|
},
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason("", ro, path.ExchangeService, path.EmailCategory),
|
|
},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.True,
|
|
expectDCS: []mockColl{{id: "id1"}},
|
|
expectMans: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory),
|
|
).WithAssistBases(
|
|
makeMan("id2", "checkpoint", path.EmailCategory),
|
|
),
|
|
},
|
|
{
|
|
name: "one valid man, extra incomplete man, no assist bases",
|
|
bf: &mockBackupFinder{
|
|
data: map[string]kopia.BackupBases{
|
|
ro: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory),
|
|
).WithAssistBases(
|
|
makeMan("id2", "checkpoint", path.EmailCategory),
|
|
),
|
|
},
|
|
},
|
|
rp: mockRestoreProducer{
|
|
collsByID: map[string][]data.RestoreCollection{
|
|
"id1": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id1"}}},
|
|
"id2": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id2"}}},
|
|
},
|
|
},
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason("", ro, path.ExchangeService, path.EmailCategory),
|
|
},
|
|
getMeta: true,
|
|
dropAssist: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.True,
|
|
expectDCS: []mockColl{{id: "id1"}},
|
|
expectMans: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory),
|
|
).
|
|
ClearMockAssistBases(),
|
|
},
|
|
{
|
|
name: "multiple valid mans",
|
|
bf: &mockBackupFinder{
|
|
data: map[string]kopia.BackupBases{
|
|
ro: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory),
|
|
makeMan("id2", "", path.EmailCategory),
|
|
),
|
|
},
|
|
},
|
|
rp: mockRestoreProducer{
|
|
collsByID: map[string][]data.RestoreCollection{
|
|
"id1": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id1"}}},
|
|
"id2": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id2"}}},
|
|
},
|
|
},
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason("", ro, path.ExchangeService, path.EmailCategory),
|
|
},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.True,
|
|
expectDCS: []mockColl{{id: "id1"}, {id: "id2"}},
|
|
expectMans: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory),
|
|
makeMan("id2", "", path.EmailCategory),
|
|
),
|
|
},
|
|
{
|
|
name: "error collecting metadata",
|
|
bf: &mockBackupFinder{
|
|
data: map[string]kopia.BackupBases{
|
|
ro: kopia.NewMockBackupBases().WithMergeBases(
|
|
makeMan("id1", "", path.EmailCategory),
|
|
),
|
|
},
|
|
},
|
|
rp: mockRestoreProducer{err: assert.AnError},
|
|
reasons: []identity.Reasoner{
|
|
kopia.NewReason("", ro, path.ExchangeService, path.EmailCategory),
|
|
},
|
|
getMeta: true,
|
|
assertErr: assert.Error,
|
|
assertB: assert.False,
|
|
expectDCS: nil,
|
|
expectMans: nil,
|
|
},
|
|
}
|
|
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
t := suite.T()
|
|
|
|
ctx, flush := tester.NewContext(t)
|
|
defer flush()
|
|
|
|
mans, dcs, b, err := produceManifestsAndMetadata(
|
|
ctx,
|
|
test.bf,
|
|
&test.rp,
|
|
test.reasons,
|
|
tid,
|
|
test.getMeta,
|
|
test.dropAssist)
|
|
test.assertErr(t, err, clues.ToCore(err))
|
|
test.assertB(t, b)
|
|
|
|
kopia.AssertBackupBasesEqual(t, test.expectMans, mans)
|
|
|
|
expect, got := []string{}, []string{}
|
|
|
|
for _, dc := range test.expectDCS {
|
|
expect = append(expect, dc.id)
|
|
}
|
|
|
|
for _, dc := range dcs {
|
|
if !assert.IsTypef(
|
|
t,
|
|
data.NoFetchRestoreCollection{},
|
|
dc,
|
|
"unexpected type returned [%T]",
|
|
dc,
|
|
) {
|
|
continue
|
|
}
|
|
|
|
tmp := dc.(data.NoFetchRestoreCollection)
|
|
|
|
if !assert.IsTypef(
|
|
t,
|
|
mockColl{},
|
|
tmp.Collection,
|
|
"unexpected type returned [%T]",
|
|
tmp.Collection,
|
|
) {
|
|
continue
|
|
}
|
|
|
|
mc := tmp.Collection.(mockColl)
|
|
got = append(got, mc.id)
|
|
}
|
|
|
|
assert.ElementsMatch(t, expect, got, "expected collections are present")
|
|
|
|
if test.expectPaths != nil {
|
|
test.expectPaths(t, test.rp.gotPaths)
|
|
}
|
|
})
|
|
}
|
|
}
|