Skip to content

Commit

Permalink
Backup list filtering (#3979)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ashmrtn authored Aug 9, 2023
1 parent 9519787 commit 9ec638f
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 3 deletions.
34 changes: 31 additions & 3 deletions src/pkg/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down
220 changes: 220 additions & 0 deletions src/pkg/repository/repository_unexported_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 9ec638f

Please sign in to comment.