Backup list filtering (#3979)

Now that we store backup models for
backups that had errors filter them
out during backup list. This
ensures behavior doesn't change
when we merge PRs for making and
labeling backup models with the
assist backup tag

---

#### 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

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

#### Issue(s)

* #3973

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
ashmrtn 2023-08-08 17:53:31 -07:00 committed by GitHub
parent 95197872c5
commit 9ec638f763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 251 additions and 3 deletions

View File

@ -449,7 +449,7 @@ func getBackup(
return b, nil
}
// BackupsByID lists backups by ID. Returns as many backups as possible with
// Backups lists backups by ID. Returns as many backups as possible with
// errors for the backups it was unable to retrieve.
func (r repository) Backups(ctx context.Context, ids []string) ([]*backup.Backup, *fault.Bus) {
var (
@ -472,10 +472,38 @@ func (r repository) Backups(ctx context.Context, ids []string) ([]*backup.Backup
return bups, errs
}
// backups lists backups in a repository
// BackupsByTag lists all backups in a repository that contain all the tags
// specified.
func (r repository) BackupsByTag(ctx context.Context, fs ...store.FilterOption) ([]*backup.Backup, error) {
sw := store.NewKopiaStore(r.modelStore)
return sw.GetBackups(ctx, fs...)
return backupsByTag(ctx, sw, fs)
}
// backupsByTag returns all backups matching all provided tags.
//
// TODO(ashmrtn): This exists mostly for testing, but we could restructure the
// code in this file so there's a more elegant mocking solution.
func backupsByTag(
ctx context.Context,
sw store.BackupWrapper,
fs []store.FilterOption,
) ([]*backup.Backup, error) {
bs, err := sw.GetBackups(ctx, fs...)
if err != nil {
return nil, clues.Stack(err)
}
// Filter out assist backup bases as they're considered incomplete and we
// haven't been displaying them before now.
res := make([]*backup.Backup, 0, len(bs))
for _, b := range bs {
if t := b.Tags[model.BackupTypeTag]; t != model.AssistBackup {
res = append(res, b)
}
}
return res, nil
}
// BackupDetails returns the specified backup.Details

View File

@ -30,6 +30,41 @@ import (
"github.com/alcionai/corso/src/pkg/store/mock"
)
// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------
type mockBackupList struct {
backups []*backup.Backup
err error
check func(fs []store.FilterOption)
}
func (mbl mockBackupList) GetBackup(
ctx context.Context,
backupID model.StableID,
) (*backup.Backup, error) {
return nil, clues.New("not implemented")
}
func (mbl mockBackupList) DeleteBackup(
ctx context.Context,
backupID model.StableID,
) error {
return clues.New("not implemented")
}
func (mbl mockBackupList) GetBackups(
ctx context.Context,
filters ...store.FilterOption,
) ([]*backup.Backup, error) {
if mbl.check != nil {
mbl.check(filters)
}
return mbl.backups, mbl.err
}
// ---------------------------------------------------------------------------
// Unit
// ---------------------------------------------------------------------------
@ -100,6 +135,191 @@ func (suite *RepositoryBackupsUnitSuite) TestGetBackup() {
}
}
func (suite *RepositoryBackupsUnitSuite) TestBackupsByTag() {
unlabeled1 := &backup.Backup{
BaseModel: model.BaseModel{
ID: model.StableID(uuid.NewString()),
},
}
unlabeled2 := &backup.Backup{
BaseModel: model.BaseModel{
ID: model.StableID(uuid.NewString()),
},
}
merge1 := &backup.Backup{
BaseModel: model.BaseModel{
ID: model.StableID(uuid.NewString()),
Tags: map[string]string{
model.BackupTypeTag: model.MergeBackup,
},
},
}
merge2 := &backup.Backup{
BaseModel: model.BaseModel{
ID: model.StableID(uuid.NewString()),
Tags: map[string]string{
model.BackupTypeTag: model.MergeBackup,
},
},
}
assist1 := &backup.Backup{
BaseModel: model.BaseModel{
ID: model.StableID(uuid.NewString()),
Tags: map[string]string{
model.BackupTypeTag: model.AssistBackup,
},
},
}
assist2 := &backup.Backup{
BaseModel: model.BaseModel{
ID: model.StableID(uuid.NewString()),
Tags: map[string]string{
model.BackupTypeTag: model.AssistBackup,
},
},
}
table := []struct {
name string
getBackups []*backup.Backup
filters []store.FilterOption
listErr error
expectErr assert.ErrorAssertionFunc
expect []*backup.Backup
}{
{
name: "UnlabeledOnly",
getBackups: []*backup.Backup{
unlabeled1,
unlabeled2,
},
expectErr: assert.NoError,
expect: []*backup.Backup{
unlabeled1,
unlabeled2,
},
},
{
name: "MergeOnly",
getBackups: []*backup.Backup{
merge1,
merge2,
},
expectErr: assert.NoError,
expect: []*backup.Backup{
merge1,
merge2,
},
},
{
name: "AssistOnly",
getBackups: []*backup.Backup{
assist1,
assist2,
},
expectErr: assert.NoError,
},
{
name: "UnlabledAndMerge",
getBackups: []*backup.Backup{
merge1,
unlabeled1,
merge2,
unlabeled2,
},
expectErr: assert.NoError,
expect: []*backup.Backup{
merge1,
merge2,
unlabeled1,
unlabeled2,
},
},
{
name: "UnlabeledAndAssist",
getBackups: []*backup.Backup{
unlabeled1,
assist1,
unlabeled2,
assist2,
},
expectErr: assert.NoError,
expect: []*backup.Backup{
unlabeled1,
unlabeled2,
},
},
{
name: "MergeAndAssist",
getBackups: []*backup.Backup{
merge1,
assist1,
merge2,
assist2,
},
expectErr: assert.NoError,
expect: []*backup.Backup{
merge1,
merge2,
},
},
{
name: "UnlabeledAndMergeAndAssist",
getBackups: []*backup.Backup{
unlabeled1,
merge1,
assist1,
merge2,
unlabeled2,
assist2,
},
expectErr: assert.NoError,
expect: []*backup.Backup{
merge1,
merge2,
unlabeled1,
unlabeled2,
},
},
{
name: "LookupError",
getBackups: []*backup.Backup{
unlabeled1,
merge1,
assist1,
merge2,
unlabeled2,
assist2,
},
listErr: assert.AnError,
expectErr: assert.Error,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
mbl := mockBackupList{
backups: test.getBackups,
err: test.listErr,
check: func(fs []store.FilterOption) {
assert.ElementsMatch(t, test.filters, fs)
},
}
bs, err := backupsByTag(ctx, mbl, test.filters)
test.expectErr(t, err, clues.ToCore(err))
assert.ElementsMatch(t, test.expect, bs)
})
}
}
type mockSSDeleter struct {
err error
}