Take the new base finder code, wrap it in a thin wrapper and use it in place of the previous way of finding base snapshots This solution is not focused on efficiency as it does result in looking up backup models multiple times. It's more to get the new way of finding bases out and then we can go back and smooth things over when we have the chance --- #### Does this PR need a docs update or release note? - [x] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change - [x] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) * #3202 #### Test Plan - [ ] 💪 Manual - [x] ⚡ Unit test - [x] 💚 E2E
1312 lines
30 KiB
Go
1312 lines
30 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/require"
|
|
"github.com/stretchr/testify/suite"
|
|
"golang.org/x/exp/maps"
|
|
|
|
"github.com/alcionai/corso/src/internal/data"
|
|
"github.com/alcionai/corso/src/internal/kopia"
|
|
"github.com/alcionai/corso/src/internal/model"
|
|
"github.com/alcionai/corso/src/internal/tester"
|
|
"github.com/alcionai/corso/src/pkg/backup"
|
|
"github.com/alcionai/corso/src/pkg/fault"
|
|
"github.com/alcionai/corso/src/pkg/path"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// interfaces
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type mockManifestRestorer struct {
|
|
mockRestoreProducer
|
|
mans []kopia.ManifestEntry
|
|
mrErr error // err varname already claimed by mockRestoreProducer
|
|
}
|
|
|
|
func (mmr mockManifestRestorer) FindBases(
|
|
ctx context.Context,
|
|
reasons []kopia.Reason,
|
|
tags map[string]string,
|
|
) ([]kopia.ManifestEntry, error) {
|
|
mans := map[string]kopia.ManifestEntry{}
|
|
|
|
for _, r := range reasons {
|
|
for _, m := range mmr.mans {
|
|
for _, mr := range m.Reasons {
|
|
if mr.ResourceOwner == r.ResourceOwner {
|
|
mans[string(m.ID)] = m
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return maps.Values(mans), mmr.mrErr
|
|
}
|
|
|
|
type mockGetBackuper struct {
|
|
detailsID string
|
|
streamstoreID string
|
|
err error
|
|
}
|
|
|
|
func (mg mockGetBackuper) GetBackup(
|
|
ctx context.Context,
|
|
backupID model.StableID,
|
|
) (*backup.Backup, error) {
|
|
return &backup.Backup{
|
|
DetailsID: mg.detailsID,
|
|
StreamStoreID: mg.streamstoreID,
|
|
}, mg.err
|
|
}
|
|
|
|
type mockColl struct {
|
|
id string // for comparisons
|
|
p path.Path
|
|
}
|
|
|
|
func (mc mockColl) Items(context.Context, *fault.Bus) <-chan data.Stream {
|
|
return nil
|
|
}
|
|
|
|
func (mc mockColl) FullPath() path.Path {
|
|
return mc.p
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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 []kopia.Reason
|
|
fileNames []string
|
|
expectPaths func(*testing.T, []string) []path.Path
|
|
expectErr error
|
|
}{
|
|
{
|
|
name: "single reason, single file",
|
|
manID: "single single",
|
|
reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: 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: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: 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: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: 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: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: 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 (suite *OperationsManifestsUnitSuite) TestVerifyDistinctBases() {
|
|
ro := "resource_owner"
|
|
|
|
table := []struct {
|
|
name string
|
|
mans []kopia.ManifestEntry
|
|
expect assert.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "one manifest, one reason",
|
|
mans: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expect: assert.NoError,
|
|
},
|
|
{
|
|
name: "one incomplete manifest",
|
|
mans: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{IncompleteReason: "ir"},
|
|
},
|
|
},
|
|
expect: assert.NoError,
|
|
},
|
|
{
|
|
name: "one manifest, multiple reasons",
|
|
mans: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.ContactsCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expect: assert.NoError,
|
|
},
|
|
{
|
|
name: "one manifest, duplicate reasons",
|
|
mans: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expect: assert.Error,
|
|
},
|
|
{
|
|
name: "two manifests, non-overlapping reasons",
|
|
mans: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.ContactsCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expect: assert.NoError,
|
|
},
|
|
{
|
|
name: "two manifests, overlapping reasons",
|
|
mans: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expect: assert.Error,
|
|
},
|
|
{
|
|
name: "two manifests, overlapping reasons, one snapshot incomplete",
|
|
mans: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Manifest: &snapshot.Manifest{IncompleteReason: "ir"},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expect: assert.NoError,
|
|
},
|
|
}
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
ctx, flush := tester.NewContext(suite.T())
|
|
defer flush()
|
|
|
|
err := verifyDistinctBases(ctx, test.mans)
|
|
test.expect(suite.T(), err, clues.ToCore(err))
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata() {
|
|
const (
|
|
ro = "resourceowner"
|
|
tid = "tenantid"
|
|
did = "detailsid"
|
|
)
|
|
|
|
makeMan := func(pct path.CategoryType, id, incmpl, bid string) kopia.ManifestEntry {
|
|
tags := map[string]string{}
|
|
if len(bid) > 0 {
|
|
tags = map[string]string{"tag:" + kopia.TagBackupID: bid}
|
|
}
|
|
|
|
return kopia.ManifestEntry{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: manifest.ID(id),
|
|
IncompleteReason: incmpl,
|
|
Tags: tags,
|
|
},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: pct,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
table := []struct {
|
|
name string
|
|
mr mockManifestRestorer
|
|
gb mockGetBackuper
|
|
getMeta bool
|
|
assertErr assert.ErrorAssertionFunc
|
|
assertB assert.BoolAssertionFunc
|
|
expectDCS []mockColl
|
|
expectNilMans bool
|
|
}{
|
|
{
|
|
name: "don't get metadata, no mans",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{},
|
|
mans: []kopia.ManifestEntry{},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: false,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.False,
|
|
expectDCS: nil,
|
|
},
|
|
{
|
|
name: "don't get metadata",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{},
|
|
mans: []kopia.ManifestEntry{makeMan(path.EmailCategory, "id1", "", "")},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: false,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.False,
|
|
expectDCS: nil,
|
|
},
|
|
{
|
|
name: "don't get metadata, incomplete manifest",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{},
|
|
mans: []kopia.ManifestEntry{makeMan(path.EmailCategory, "id1", "ir", "")},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: false,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.False,
|
|
expectDCS: nil,
|
|
},
|
|
{
|
|
name: "fetch manifests errors",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{},
|
|
mrErr: assert.AnError,
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: true,
|
|
assertErr: assert.Error,
|
|
assertB: assert.False,
|
|
expectDCS: nil,
|
|
},
|
|
{
|
|
name: "verify distinct bases fails",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{},
|
|
mans: []kopia.ManifestEntry{
|
|
makeMan(path.EmailCategory, "id1", "", ""),
|
|
makeMan(path.EmailCategory, "id2", "", ""),
|
|
},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: true,
|
|
assertErr: assert.NoError, // No error, even though verify failed.
|
|
assertB: assert.False,
|
|
expectDCS: nil,
|
|
},
|
|
{
|
|
name: "no manifests",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{},
|
|
mans: []kopia.ManifestEntry{},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.True,
|
|
expectDCS: nil,
|
|
},
|
|
{
|
|
name: "only incomplete manifests",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{},
|
|
mans: []kopia.ManifestEntry{
|
|
makeMan(path.EmailCategory, "id1", "ir", ""),
|
|
makeMan(path.ContactsCategory, "id2", "ir", ""),
|
|
},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.True,
|
|
expectDCS: nil,
|
|
},
|
|
{
|
|
name: "man missing backup id",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{
|
|
collsByID: map[string][]data.RestoreCollection{
|
|
"id": {data.NotFoundRestoreCollection{Collection: mockColl{id: "id_coll"}}},
|
|
},
|
|
},
|
|
mans: []kopia.ManifestEntry{makeMan(path.EmailCategory, "id", "", "")},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: true,
|
|
assertErr: assert.Error,
|
|
assertB: assert.False,
|
|
expectNilMans: true,
|
|
},
|
|
{
|
|
name: "backup missing details id",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{},
|
|
mans: []kopia.ManifestEntry{makeMan(path.EmailCategory, "id1", "", "bid")},
|
|
},
|
|
gb: mockGetBackuper{},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.False,
|
|
},
|
|
{
|
|
name: "one complete, one incomplete",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{
|
|
collsByID: map[string][]data.RestoreCollection{
|
|
"id": {data.NotFoundRestoreCollection{Collection: mockColl{id: "id_coll"}}},
|
|
"incmpl_id": {data.NotFoundRestoreCollection{Collection: mockColl{id: "incmpl_id_coll"}}},
|
|
},
|
|
},
|
|
mans: []kopia.ManifestEntry{
|
|
makeMan(path.EmailCategory, "id", "", "bid"),
|
|
makeMan(path.EmailCategory, "incmpl_id", "ir", ""),
|
|
},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.True,
|
|
expectDCS: []mockColl{{id: "id_coll"}},
|
|
},
|
|
{
|
|
name: "single valid man",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{
|
|
collsByID: map[string][]data.RestoreCollection{
|
|
"id": {data.NotFoundRestoreCollection{Collection: mockColl{id: "id_coll"}}},
|
|
},
|
|
},
|
|
mans: []kopia.ManifestEntry{makeMan(path.EmailCategory, "id", "", "bid")},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.True,
|
|
expectDCS: []mockColl{{id: "id_coll"}},
|
|
},
|
|
{
|
|
name: "multiple valid mans",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{
|
|
collsByID: map[string][]data.RestoreCollection{
|
|
"mail": {data.NotFoundRestoreCollection{Collection: mockColl{id: "mail_coll"}}},
|
|
"contact": {data.NotFoundRestoreCollection{Collection: mockColl{id: "contact_coll"}}},
|
|
},
|
|
},
|
|
mans: []kopia.ManifestEntry{
|
|
makeMan(path.EmailCategory, "mail", "", "bid"),
|
|
makeMan(path.ContactsCategory, "contact", "", "bid"),
|
|
},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: true,
|
|
assertErr: assert.NoError,
|
|
assertB: assert.True,
|
|
expectDCS: []mockColl{
|
|
{id: "mail_coll"},
|
|
{id: "contact_coll"},
|
|
},
|
|
},
|
|
{
|
|
name: "error collecting metadata",
|
|
mr: mockManifestRestorer{
|
|
mockRestoreProducer: mockRestoreProducer{err: assert.AnError},
|
|
mans: []kopia.ManifestEntry{makeMan(path.EmailCategory, "id1", "", "bid")},
|
|
},
|
|
gb: mockGetBackuper{detailsID: did},
|
|
getMeta: true,
|
|
assertErr: assert.Error,
|
|
assertB: assert.False,
|
|
expectDCS: nil,
|
|
expectNilMans: true,
|
|
},
|
|
}
|
|
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.mr,
|
|
&test.gb,
|
|
[]kopia.Reason{{ResourceOwner: ro}}, nil,
|
|
tid,
|
|
test.getMeta)
|
|
test.assertErr(t, err, clues.ToCore(err))
|
|
test.assertB(t, b)
|
|
|
|
expectMans := test.mr.mans
|
|
if test.expectNilMans {
|
|
expectMans = nil
|
|
}
|
|
|
|
assert.ElementsMatch(t, 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.NotFoundRestoreCollection{},
|
|
dc,
|
|
"unexpected type returned [%T]",
|
|
dc,
|
|
) {
|
|
continue
|
|
}
|
|
|
|
tmp := dc.(data.NotFoundRestoreCollection)
|
|
|
|
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")
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata_fallbackReasons() {
|
|
const (
|
|
ro = "resourceowner"
|
|
manComplete = "complete"
|
|
manIncomplete = "incmpl"
|
|
|
|
fbro = "fb_resourceowner"
|
|
fbComplete = "fb_complete"
|
|
fbIncomplete = "fb_incmpl"
|
|
)
|
|
|
|
makeMan := func(id, incmpl string, reasons []kopia.Reason) kopia.ManifestEntry {
|
|
return kopia.ManifestEntry{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: manifest.ID(id),
|
|
IncompleteReason: incmpl,
|
|
Tags: map[string]string{},
|
|
},
|
|
Reasons: reasons,
|
|
}
|
|
}
|
|
|
|
type testInput struct {
|
|
id string
|
|
incomplete bool
|
|
}
|
|
|
|
table := []struct {
|
|
name string
|
|
man []testInput
|
|
fallback []testInput
|
|
reasons []kopia.Reason
|
|
fallbackReasons []kopia.Reason
|
|
manCategories []path.CategoryType
|
|
fbCategories []path.CategoryType
|
|
assertErr assert.ErrorAssertionFunc
|
|
expectManIDs []string
|
|
expectNilMans bool
|
|
expectReasons map[string][]path.CategoryType
|
|
}{
|
|
{
|
|
name: "only mans, no fallbacks",
|
|
man: []testInput{
|
|
{
|
|
id: manComplete,
|
|
},
|
|
{
|
|
id: manIncomplete,
|
|
incomplete: true,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.EmailCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory},
|
|
expectManIDs: []string{manComplete, manIncomplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
manComplete: {path.EmailCategory},
|
|
manIncomplete: {path.EmailCategory},
|
|
},
|
|
},
|
|
{
|
|
name: "no mans, only fallbacks",
|
|
fallback: []testInput{
|
|
{
|
|
id: fbComplete,
|
|
},
|
|
{
|
|
id: fbIncomplete,
|
|
incomplete: true,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.EmailCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory},
|
|
expectManIDs: []string{fbComplete, fbIncomplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
fbComplete: {path.EmailCategory},
|
|
fbIncomplete: {path.EmailCategory},
|
|
},
|
|
},
|
|
{
|
|
name: "complete mans and fallbacks",
|
|
man: []testInput{
|
|
{
|
|
id: manComplete,
|
|
},
|
|
},
|
|
fallback: []testInput{
|
|
{
|
|
id: fbComplete,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.EmailCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory},
|
|
expectManIDs: []string{manComplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
manComplete: {path.EmailCategory},
|
|
},
|
|
},
|
|
{
|
|
name: "incomplete mans and fallbacks",
|
|
man: []testInput{
|
|
{
|
|
id: manIncomplete,
|
|
incomplete: true,
|
|
},
|
|
},
|
|
fallback: []testInput{
|
|
{
|
|
id: fbIncomplete,
|
|
incomplete: true,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.EmailCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory},
|
|
expectManIDs: []string{manIncomplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
manIncomplete: {path.EmailCategory},
|
|
},
|
|
},
|
|
{
|
|
name: "complete and incomplete mans and fallbacks",
|
|
man: []testInput{
|
|
{
|
|
id: manComplete,
|
|
},
|
|
{
|
|
id: manIncomplete,
|
|
incomplete: true,
|
|
},
|
|
},
|
|
fallback: []testInput{
|
|
{
|
|
id: fbComplete,
|
|
},
|
|
{
|
|
id: fbIncomplete,
|
|
incomplete: true,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.EmailCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory},
|
|
expectManIDs: []string{manComplete, manIncomplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
manComplete: {path.EmailCategory},
|
|
manIncomplete: {path.EmailCategory},
|
|
},
|
|
},
|
|
{
|
|
name: "incomplete mans, complete fallbacks",
|
|
man: []testInput{
|
|
{
|
|
id: manIncomplete,
|
|
incomplete: true,
|
|
},
|
|
},
|
|
fallback: []testInput{
|
|
{
|
|
id: fbComplete,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.EmailCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory},
|
|
expectManIDs: []string{fbComplete, manIncomplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
fbComplete: {path.EmailCategory},
|
|
manIncomplete: {path.EmailCategory},
|
|
},
|
|
},
|
|
{
|
|
name: "complete mans, incomplete fallbacks",
|
|
man: []testInput{
|
|
{
|
|
id: manComplete,
|
|
},
|
|
},
|
|
fallback: []testInput{
|
|
{
|
|
id: fbIncomplete,
|
|
incomplete: true,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.EmailCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory},
|
|
expectManIDs: []string{manComplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
manComplete: {path.EmailCategory},
|
|
},
|
|
},
|
|
{
|
|
name: "complete mans, complete fallbacks, multiple reasons",
|
|
man: []testInput{
|
|
{
|
|
id: manComplete,
|
|
},
|
|
},
|
|
fallback: []testInput{
|
|
{
|
|
id: fbComplete,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.EmailCategory, path.ContactsCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory, path.ContactsCategory},
|
|
expectManIDs: []string{manComplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
manComplete: {path.EmailCategory, path.ContactsCategory},
|
|
},
|
|
},
|
|
{
|
|
name: "complete mans, complete fallbacks, distinct reasons",
|
|
man: []testInput{
|
|
{
|
|
id: manComplete,
|
|
},
|
|
},
|
|
fallback: []testInput{
|
|
{
|
|
id: fbComplete,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.ContactsCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory},
|
|
expectManIDs: []string{manComplete, fbComplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
manComplete: {path.ContactsCategory},
|
|
fbComplete: {path.EmailCategory},
|
|
},
|
|
},
|
|
{
|
|
name: "fb has superset of mans reasons",
|
|
man: []testInput{
|
|
{
|
|
id: manComplete,
|
|
},
|
|
},
|
|
fallback: []testInput{
|
|
{
|
|
id: fbComplete,
|
|
},
|
|
},
|
|
manCategories: []path.CategoryType{path.ContactsCategory},
|
|
fbCategories: []path.CategoryType{path.EmailCategory, path.ContactsCategory, path.EventsCategory},
|
|
expectManIDs: []string{manComplete, fbComplete},
|
|
expectReasons: map[string][]path.CategoryType{
|
|
manComplete: {path.ContactsCategory},
|
|
fbComplete: {path.EmailCategory, path.EventsCategory},
|
|
},
|
|
},
|
|
}
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
t := suite.T()
|
|
|
|
ctx, flush := tester.NewContext(t)
|
|
defer flush()
|
|
|
|
mainReasons := []kopia.Reason{}
|
|
fbReasons := []kopia.Reason{}
|
|
|
|
for _, cat := range test.manCategories {
|
|
mainReasons = append(
|
|
mainReasons,
|
|
kopia.Reason{
|
|
ResourceOwner: ro,
|
|
Service: path.ExchangeService,
|
|
Category: cat,
|
|
})
|
|
}
|
|
|
|
for _, cat := range test.fbCategories {
|
|
fbReasons = append(
|
|
fbReasons,
|
|
kopia.Reason{
|
|
ResourceOwner: fbro,
|
|
Service: path.ExchangeService,
|
|
Category: cat,
|
|
})
|
|
}
|
|
|
|
mans := []kopia.ManifestEntry{}
|
|
|
|
for _, m := range test.man {
|
|
incomplete := ""
|
|
if m.incomplete {
|
|
incomplete = "ir"
|
|
}
|
|
|
|
mans = append(mans, makeMan(m.id, incomplete, mainReasons))
|
|
}
|
|
|
|
for _, m := range test.fallback {
|
|
incomplete := ""
|
|
if m.incomplete {
|
|
incomplete = "ir"
|
|
}
|
|
|
|
mans = append(mans, makeMan(m.id, incomplete, fbReasons))
|
|
}
|
|
|
|
mr := mockManifestRestorer{mans: mans}
|
|
|
|
gotMans, _, b, err := produceManifestsAndMetadata(
|
|
ctx,
|
|
&mr,
|
|
nil,
|
|
mainReasons,
|
|
fbReasons,
|
|
"tid",
|
|
false)
|
|
require.NoError(t, err, clues.ToCore(err))
|
|
assert.False(t, b, "no-metadata is forced for this test")
|
|
|
|
manIDs := []string{}
|
|
|
|
for _, m := range gotMans {
|
|
manIDs = append(manIDs, string(m.ID))
|
|
|
|
reasons := test.expectReasons[string(m.ID)]
|
|
|
|
mrs := []path.CategoryType{}
|
|
for _, r := range m.Reasons {
|
|
mrs = append(mrs, r.Category)
|
|
}
|
|
|
|
assert.ElementsMatch(t, reasons, mrs)
|
|
}
|
|
|
|
assert.ElementsMatch(t, test.expectManIDs, manIDs)
|
|
})
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// older tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type BackupManifestUnitSuite struct {
|
|
tester.Suite
|
|
}
|
|
|
|
func TestBackupManifestUnitSuite(t *testing.T) {
|
|
suite.Run(t, &BackupManifestUnitSuite{Suite: tester.NewUnitSuite(t)})
|
|
}
|
|
|
|
func (suite *BackupManifestUnitSuite) TestBackupOperation_VerifyDistinctBases() {
|
|
const user = "a-user"
|
|
|
|
table := []struct {
|
|
name string
|
|
input []kopia.ManifestEntry
|
|
errCheck assert.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "SingleManifestMultipleReasons",
|
|
input: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: "id1",
|
|
},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: user,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
{
|
|
ResourceOwner: user,
|
|
Service: path.ExchangeService,
|
|
Category: path.EventsCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
errCheck: assert.NoError,
|
|
},
|
|
{
|
|
name: "MultipleManifestsDistinctReason",
|
|
input: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: "id1",
|
|
},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: user,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: "id2",
|
|
},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: user,
|
|
Service: path.ExchangeService,
|
|
Category: path.EventsCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
errCheck: assert.NoError,
|
|
},
|
|
{
|
|
name: "MultipleManifestsSameReason",
|
|
input: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: "id1",
|
|
},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: user,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: "id2",
|
|
},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: user,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
errCheck: assert.Error,
|
|
},
|
|
{
|
|
name: "MultipleManifestsSameReasonOneIncomplete",
|
|
input: []kopia.ManifestEntry{
|
|
{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: "id1",
|
|
},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: user,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Manifest: &snapshot.Manifest{
|
|
ID: "id2",
|
|
IncompleteReason: "checkpoint",
|
|
},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: user,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
errCheck: assert.NoError,
|
|
},
|
|
}
|
|
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
ctx, flush := tester.NewContext(suite.T())
|
|
defer flush()
|
|
|
|
err := verifyDistinctBases(ctx, test.input)
|
|
test.errCheck(suite.T(), err, clues.ToCore(err))
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *BackupManifestUnitSuite) TestBackupOperation_CollectMetadata() {
|
|
var (
|
|
tenant = "a-tenant"
|
|
resourceOwner = "a-user"
|
|
fileNames = []string{
|
|
"delta",
|
|
"paths",
|
|
}
|
|
|
|
emailDeltaPath = makeMetadataPath(
|
|
suite.T(),
|
|
tenant,
|
|
path.ExchangeService,
|
|
resourceOwner,
|
|
path.EmailCategory,
|
|
fileNames[0],
|
|
)
|
|
emailPathsPath = makeMetadataPath(
|
|
suite.T(),
|
|
tenant,
|
|
path.ExchangeService,
|
|
resourceOwner,
|
|
path.EmailCategory,
|
|
fileNames[1],
|
|
)
|
|
contactsDeltaPath = makeMetadataPath(
|
|
suite.T(),
|
|
tenant,
|
|
path.ExchangeService,
|
|
resourceOwner,
|
|
path.ContactsCategory,
|
|
fileNames[0],
|
|
)
|
|
contactsPathsPath = makeMetadataPath(
|
|
suite.T(),
|
|
tenant,
|
|
path.ExchangeService,
|
|
resourceOwner,
|
|
path.ContactsCategory,
|
|
fileNames[1],
|
|
)
|
|
)
|
|
|
|
table := []struct {
|
|
name string
|
|
inputMan kopia.ManifestEntry
|
|
inputFiles []string
|
|
expected []path.Path
|
|
}{
|
|
{
|
|
name: "SingleReasonSingleFile",
|
|
inputMan: kopia.ManifestEntry{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: resourceOwner,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
inputFiles: []string{fileNames[0]},
|
|
expected: []path.Path{emailDeltaPath},
|
|
},
|
|
{
|
|
name: "SingleReasonMultipleFiles",
|
|
inputMan: kopia.ManifestEntry{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: resourceOwner,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
},
|
|
},
|
|
inputFiles: fileNames,
|
|
expected: []path.Path{emailDeltaPath, emailPathsPath},
|
|
},
|
|
{
|
|
name: "MultipleReasonsMultipleFiles",
|
|
inputMan: kopia.ManifestEntry{
|
|
Manifest: &snapshot.Manifest{},
|
|
Reasons: []kopia.Reason{
|
|
{
|
|
ResourceOwner: resourceOwner,
|
|
Service: path.ExchangeService,
|
|
Category: path.EmailCategory,
|
|
},
|
|
{
|
|
ResourceOwner: resourceOwner,
|
|
Service: path.ExchangeService,
|
|
Category: path.ContactsCategory,
|
|
},
|
|
},
|
|
},
|
|
inputFiles: fileNames,
|
|
expected: []path.Path{
|
|
emailDeltaPath,
|
|
emailPathsPath,
|
|
contactsDeltaPath,
|
|
contactsPathsPath,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range table {
|
|
suite.Run(test.name, func() {
|
|
t := suite.T()
|
|
|
|
ctx, flush := tester.NewContext(t)
|
|
defer flush()
|
|
|
|
mr := &mockRestoreProducer{}
|
|
|
|
_, err := collectMetadata(ctx, mr, test.inputMan, test.inputFiles, tenant, fault.New(true))
|
|
assert.NoError(t, err, clues.ToCore(err))
|
|
|
|
checkPaths(t, test.expected, mr.gotPaths)
|
|
})
|
|
}
|
|
}
|