corso/src/internal/kopia/backup_bases_test.go
ashmrtn 7120164db6
Add struct functions for backup bases (#3595)
Move most of the stuff that was acting on backup bases to be functions that are defined for backup bases. Other code can be removed at a later point

New functions aren't called yet in other code

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [x] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #3525

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
2023-06-13 00:16:07 +00:00

706 lines
15 KiB
Go

package kopia
import (
"fmt"
"testing"
"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/model"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/internal/version"
"github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/path"
)
func makeManifest(id, incmpl, bID string, reasons ...Reason) ManifestEntry {
bIDKey, _ := makeTagKV(TagBackupID)
return ManifestEntry{
Manifest: &snapshot.Manifest{
ID: manifest.ID(id),
IncompleteReason: incmpl,
Tags: map[string]string{bIDKey: bID},
},
Reasons: reasons,
}
}
type BackupBasesUnitSuite struct {
tester.Suite
}
func TestBackupBasesUnitSuite(t *testing.T) {
suite.Run(t, &BackupBasesUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *BackupBasesUnitSuite) TestMinBackupVersion() {
table := []struct {
name string
bb *backupBases
expectedVersion int
}{
{
name: "Nil BackupBase",
expectedVersion: version.NoBackup,
},
{
name: "No Backups",
bb: &backupBases{},
expectedVersion: version.NoBackup,
},
{
name: "Unsorted Backups",
bb: &backupBases{
backups: []BackupEntry{
{
Backup: &backup.Backup{
Version: 4,
},
},
{
Backup: &backup.Backup{
Version: 0,
},
},
{
Backup: &backup.Backup{
Version: 2,
},
},
},
},
expectedVersion: 0,
},
}
for _, test := range table {
suite.Run(test.name, func() {
assert.Equal(suite.T(), test.expectedVersion, test.bb.MinBackupVersion())
})
}
}
func (suite *BackupBasesUnitSuite) TestRemoveMergeBaseByManifestID() {
backups := []BackupEntry{
{Backup: &backup.Backup{SnapshotID: "1"}},
{Backup: &backup.Backup{SnapshotID: "2"}},
{Backup: &backup.Backup{SnapshotID: "3"}},
}
merges := []ManifestEntry{
makeManifest("1", "", ""),
makeManifest("2", "", ""),
makeManifest("3", "", ""),
}
expected := &backupBases{
backups: []BackupEntry{backups[0], backups[1]},
mergeBases: []ManifestEntry{merges[0], merges[1]},
assistBases: []ManifestEntry{merges[0], merges[1]},
}
delID := manifest.ID("3")
table := []struct {
name string
// Below indices specify which items to add from the defined sets above.
backup []int
merge []int
assist []int
}{
{
name: "Not In Bases",
backup: []int{0, 1},
merge: []int{0, 1},
assist: []int{0, 1},
},
{
name: "Different Indexes",
backup: []int{2, 0, 1},
merge: []int{0, 2, 1},
assist: []int{0, 1, 2},
},
{
name: "First Item",
backup: []int{2, 0, 1},
merge: []int{2, 0, 1},
assist: []int{2, 0, 1},
},
{
name: "Middle Item",
backup: []int{0, 2, 1},
merge: []int{0, 2, 1},
assist: []int{0, 2, 1},
},
{
name: "Final Item",
backup: []int{0, 1, 2},
merge: []int{0, 1, 2},
assist: []int{0, 1, 2},
},
{
name: "Only In Backups",
backup: []int{0, 1, 2},
merge: []int{0, 1},
assist: []int{0, 1},
},
{
name: "Only In Merges",
backup: []int{0, 1},
merge: []int{0, 1, 2},
assist: []int{0, 1},
},
{
name: "Only In Assists",
backup: []int{0, 1},
merge: []int{0, 1},
assist: []int{0, 1, 2},
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
bb := &backupBases{}
for _, i := range test.backup {
bb.backups = append(bb.backups, backups[i])
}
for _, i := range test.merge {
bb.mergeBases = append(bb.mergeBases, merges[i])
}
for _, i := range test.assist {
bb.assistBases = append(bb.assistBases, merges[i])
}
bb.RemoveMergeBaseByManifestID(delID)
AssertBackupBasesEqual(t, expected, bb)
})
}
}
func (suite *BackupBasesUnitSuite) TestClearMergeBases() {
bb := &backupBases{
backups: make([]BackupEntry, 2),
mergeBases: make([]ManifestEntry, 2),
}
bb.ClearMergeBases()
assert.Empty(suite.T(), bb.Backups())
assert.Empty(suite.T(), bb.MergeBases())
}
func (suite *BackupBasesUnitSuite) TestClearAssistBases() {
bb := &backupBases{assistBases: make([]ManifestEntry, 2)}
bb.ClearAssistBases()
assert.Empty(suite.T(), bb.AssistBases())
}
func (suite *BackupBasesUnitSuite) TestMergeBackupBases() {
ro := "resource_owner"
type testInput struct {
id int
incomplete bool
cat []path.CategoryType
}
// Make a function so tests can modify things without messing with each other.
makeBackupBases := func(ti []testInput) *backupBases {
res := &backupBases{}
for _, i := range ti {
baseID := fmt.Sprintf("id%d", i.id)
ir := ""
if i.incomplete {
ir = "checkpoint"
}
reasons := make([]Reason, 0, len(i.cat))
for _, c := range i.cat {
reasons = append(reasons, Reason{
ResourceOwner: ro,
Service: path.ExchangeService,
Category: c,
})
}
m := makeManifest(baseID, ir, "b"+baseID, reasons...)
res.assistBases = append(res.assistBases, m)
if i.incomplete {
continue
}
b := BackupEntry{
Backup: &backup.Backup{
BaseModel: model.BaseModel{ID: model.StableID("b" + baseID)},
SnapshotID: baseID,
StreamStoreID: "ss" + baseID,
},
Reasons: reasons,
}
res.backups = append(res.backups, b)
res.mergeBases = append(res.mergeBases, m)
}
return res
}
table := []struct {
name string
bb []testInput
other []testInput
expect []testInput
}{
{
name: "Other Empty",
bb: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
},
expect: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
},
},
{
name: "BB Empty",
other: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
},
expect: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
},
},
{
name: "Other overlaps Complete And Incomplete",
bb: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
{
id: 1,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
},
other: []testInput{
{
id: 2,
cat: []path.CategoryType{path.EmailCategory},
},
{
id: 3,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
},
expect: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
{
id: 1,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
},
},
{
name: "Other Overlaps Complete",
bb: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
},
other: []testInput{
{
id: 2,
cat: []path.CategoryType{path.EmailCategory},
},
},
expect: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
},
},
{
name: "Other Overlaps Incomplete",
bb: []testInput{
{
id: 1,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
},
other: []testInput{
{
id: 2,
cat: []path.CategoryType{path.EmailCategory},
},
{
id: 3,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
},
expect: []testInput{
{
id: 1,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
{
id: 2,
cat: []path.CategoryType{path.EmailCategory},
},
},
},
{
name: "Other Disjoint",
bb: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
{
id: 1,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
},
other: []testInput{
{
id: 2,
cat: []path.CategoryType{path.ContactsCategory},
},
{
id: 3,
cat: []path.CategoryType{path.ContactsCategory},
incomplete: true,
},
},
expect: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
{
id: 1,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
{
id: 2,
cat: []path.CategoryType{path.ContactsCategory},
},
{
id: 3,
cat: []path.CategoryType{path.ContactsCategory},
incomplete: true,
},
},
},
{
name: "Other Reduced Reasons",
bb: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
{
id: 1,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
},
other: []testInput{
{
id: 2,
cat: []path.CategoryType{
path.EmailCategory,
path.ContactsCategory,
},
},
{
id: 3,
cat: []path.CategoryType{
path.EmailCategory,
path.ContactsCategory,
},
incomplete: true,
},
},
expect: []testInput{
{cat: []path.CategoryType{path.EmailCategory}},
{
id: 1,
cat: []path.CategoryType{path.EmailCategory},
incomplete: true,
},
{
id: 2,
cat: []path.CategoryType{path.ContactsCategory},
},
{
id: 3,
cat: []path.CategoryType{path.ContactsCategory},
incomplete: true,
},
},
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
bb := makeBackupBases(test.bb)
other := makeBackupBases(test.other)
expect := makeBackupBases(test.expect)
ctx, flush := tester.NewContext(t)
defer flush()
got := bb.MergeBackupBases(
ctx,
other,
func(reason Reason) string {
return reason.Service.String() + reason.Category.String()
})
AssertBackupBasesEqual(t, expect, got)
})
}
}
func (suite *BackupBasesUnitSuite) TestFixupAndVerify() {
ro := "resource_owner"
makeMan := func(pct path.CategoryType, id, incmpl, bID string) ManifestEntry {
reason := Reason{
ResourceOwner: ro,
Service: path.ExchangeService,
Category: pct,
}
return makeManifest(id, incmpl, bID, reason)
}
// Make a function so tests can modify things without messing with each other.
validMail1 := func() *backupBases {
return &backupBases{
backups: []BackupEntry{
{
Backup: &backup.Backup{
BaseModel: model.BaseModel{
ID: "bid1",
},
SnapshotID: "id1",
StreamStoreID: "ssid1",
},
},
},
mergeBases: []ManifestEntry{
makeMan(path.EmailCategory, "id1", "", "bid1"),
},
assistBases: []ManifestEntry{
makeMan(path.EmailCategory, "id1", "", "bid1"),
},
}
}
table := []struct {
name string
bb *backupBases
expect BackupBases
}{
{
name: "empty BaseBackups",
bb: &backupBases{},
},
{
name: "Merge Base Without Backup",
bb: func() *backupBases {
res := validMail1()
res.backups = nil
return res
}(),
},
{
name: "Backup Missing Snapshot ID",
bb: func() *backupBases {
res := validMail1()
res.backups[0].SnapshotID = ""
return res
}(),
},
{
name: "Backup Missing Deets ID",
bb: func() *backupBases {
res := validMail1()
res.backups[0].StreamStoreID = ""
return res
}(),
},
{
name: "Incomplete Snapshot",
bb: func() *backupBases {
res := validMail1()
res.mergeBases[0].IncompleteReason = "ir"
res.assistBases[0].IncompleteReason = "ir"
return res
}(),
},
{
name: "Duplicate Reason",
bb: func() *backupBases {
res := validMail1()
res.mergeBases[0].Reasons = append(
res.mergeBases[0].Reasons,
res.mergeBases[0].Reasons[0])
res.assistBases = res.mergeBases
return res
}(),
},
{
name: "Single Valid Entry",
bb: validMail1(),
expect: validMail1(),
},
{
name: "Single Valid Entry With Incomplete Assist With Same Reason",
bb: func() *backupBases {
res := validMail1()
res.assistBases = append(
res.assistBases,
makeMan(path.EmailCategory, "id2", "checkpoint", "bid2"))
return res
}(),
expect: func() *backupBases {
res := validMail1()
res.assistBases = append(
res.assistBases,
makeMan(path.EmailCategory, "id2", "checkpoint", "bid2"))
return res
}(),
},
{
name: "Single Valid Entry With Backup With Old Deets ID",
bb: func() *backupBases {
res := validMail1()
res.backups[0].DetailsID = res.backups[0].StreamStoreID
res.backups[0].StreamStoreID = ""
return res
}(),
expect: func() *backupBases {
res := validMail1()
res.backups[0].DetailsID = res.backups[0].StreamStoreID
res.backups[0].StreamStoreID = ""
return res
}(),
},
{
name: "Single Valid Entry With Multiple Reasons",
bb: func() *backupBases {
res := validMail1()
res.mergeBases[0].Reasons = append(
res.mergeBases[0].Reasons,
Reason{
ResourceOwner: ro,
Service: path.ExchangeService,
Category: path.ContactsCategory,
})
res.assistBases = res.mergeBases
return res
}(),
expect: func() *backupBases {
res := validMail1()
res.mergeBases[0].Reasons = append(
res.mergeBases[0].Reasons,
Reason{
ResourceOwner: ro,
Service: path.ExchangeService,
Category: path.ContactsCategory,
})
res.assistBases = res.mergeBases
return res
}(),
},
{
name: "Two Entries Overlapping Reasons",
bb: func() *backupBases {
res := validMail1()
res.mergeBases = append(
res.mergeBases,
makeMan(path.EmailCategory, "id2", "", "bid2"))
res.assistBases = res.mergeBases
return res
}(),
},
{
name: "Three Entries One Invalid",
bb: func() *backupBases {
res := validMail1()
res.backups = append(
res.backups,
BackupEntry{
Backup: &backup.Backup{
BaseModel: model.BaseModel{
ID: "bid2",
},
},
},
BackupEntry{
Backup: &backup.Backup{
BaseModel: model.BaseModel{
ID: "bid3",
},
SnapshotID: "id3",
StreamStoreID: "ssid3",
},
})
res.mergeBases = append(
res.mergeBases,
makeMan(path.ContactsCategory, "id2", "checkpoint", "bid2"),
makeMan(path.EventsCategory, "id3", "", "bid3"))
res.assistBases = res.mergeBases
return res
}(),
expect: func() *backupBases {
res := validMail1()
res.backups = append(
res.backups,
BackupEntry{
Backup: &backup.Backup{
BaseModel: model.BaseModel{
ID: "bid3",
},
SnapshotID: "id3",
StreamStoreID: "ssid3",
},
})
res.mergeBases = append(
res.mergeBases,
makeMan(path.EventsCategory, "id3", "", "bid3"))
res.assistBases = res.mergeBases
return res
}(),
},
}
for _, test := range table {
suite.Run(test.name, func() {
ctx, flush := tester.NewContext(suite.T())
defer flush()
test.bb.fixupAndVerify(ctx)
AssertBackupBasesEqual(suite.T(), test.expect, test.bb)
})
}
}