add indeets test helper, implement in exchange op (#3295)
Adds a helper for building expected details entries and checking them after a backup. Implements the helper in the exchange backup tests in operations/backup integration. Will follow with a onedrive implementation. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🤖 Supportability/Tests #### Issue(s) * #3240 #### Test Plan - [x] 💪 Manual - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
c0725b9cf9
commit
c5b388a721
@ -112,7 +112,7 @@ func runDisplayM365JSON(
|
|||||||
creds account.M365Config,
|
creds account.M365Config,
|
||||||
user, itemID string,
|
user, itemID string,
|
||||||
) error {
|
) error {
|
||||||
drive, err := api.GetDriveByID(ctx, srv, user)
|
drive, err := api.GetUsersDrive(ctx, srv, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -441,10 +441,7 @@ func restoreCollection(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
locationRef := &path.Builder{}
|
locationRef := path.Builder{}.Append(itemPath.Folders()...)
|
||||||
if category == path.ContactsCategory {
|
|
||||||
locationRef = locationRef.Append(itemPath.Folders()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = deets.Add(
|
err = deets.Add(
|
||||||
itemPath,
|
itemPath,
|
||||||
|
|||||||
@ -336,18 +336,33 @@ func GetItemPermission(
|
|||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDriveByID(
|
func GetUsersDrive(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
srv graph.Servicer,
|
srv graph.Servicer,
|
||||||
userID string,
|
user string,
|
||||||
) (models.Driveable, error) {
|
) (models.Driveable, error) {
|
||||||
//revive:enable:context-as-argument
|
|
||||||
d, err := srv.Client().
|
d, err := srv.Client().
|
||||||
UsersById(userID).
|
UsersById(user).
|
||||||
Drive().
|
Drive().
|
||||||
Get(ctx, nil)
|
Get(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, graph.Wrap(ctx, err, "getting drive")
|
return nil, graph.Wrap(ctx, err, "getting user's drive")
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSitesDefaultDrive(
|
||||||
|
ctx context.Context,
|
||||||
|
srv graph.Servicer,
|
||||||
|
site string,
|
||||||
|
) (models.Driveable, error) {
|
||||||
|
d, err := srv.Client().
|
||||||
|
SitesById(site).
|
||||||
|
Drive().
|
||||||
|
Get(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, graph.Wrap(ctx, err, "getting site's drive")
|
||||||
}
|
}
|
||||||
|
|
||||||
return d, nil
|
return d, nil
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
368
src/pkg/backup/details/testdata/in_deets.go
vendored
Normal file
368
src/pkg/backup/details/testdata/in_deets.go
vendored
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
package testdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/kopia"
|
||||||
|
"github.com/alcionai/corso/src/internal/model"
|
||||||
|
"github.com/alcionai/corso/src/internal/streamstore"
|
||||||
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// location set handling
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var exists = struct{}{}
|
||||||
|
|
||||||
|
type locSet struct {
|
||||||
|
// map [locationRef] map [itemRef] {}
|
||||||
|
// refs may be either the canonical ent refs, or something else,
|
||||||
|
// so long as they are consistent for the test in question
|
||||||
|
Locations map[string]map[string]struct{}
|
||||||
|
Deleted map[string]map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLocSet() *locSet {
|
||||||
|
return &locSet{
|
||||||
|
Locations: map[string]map[string]struct{}{},
|
||||||
|
Deleted: map[string]map[string]struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *locSet) AddItem(locationRef, itemRef string) {
|
||||||
|
ls.AddLocation(locationRef)
|
||||||
|
|
||||||
|
ls.Locations[locationRef][itemRef] = exists
|
||||||
|
delete(ls.Deleted[locationRef], itemRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *locSet) RemoveItem(locationRef, itemRef string) {
|
||||||
|
delete(ls.Locations[locationRef], itemRef)
|
||||||
|
|
||||||
|
if _, ok := ls.Deleted[locationRef]; !ok {
|
||||||
|
ls.Deleted[locationRef] = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
ls.Deleted[locationRef][itemRef] = exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *locSet) MoveItem(fromLocation, toLocation, ir string) {
|
||||||
|
ls.RemoveItem(fromLocation, ir)
|
||||||
|
ls.AddItem(toLocation, ir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *locSet) AddLocation(locationRef string) {
|
||||||
|
if _, ok := ls.Locations[locationRef]; !ok {
|
||||||
|
ls.Locations[locationRef] = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
// don't purge previously deleted items, or child locations.
|
||||||
|
// Assumption is that their itemRef is unique, and still deleted.
|
||||||
|
delete(ls.Deleted, locationRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *locSet) RemoveLocation(locationRef string) {
|
||||||
|
ss := ls.Subset(locationRef)
|
||||||
|
|
||||||
|
for lr := range ss.Locations {
|
||||||
|
items := ls.Locations[lr]
|
||||||
|
|
||||||
|
delete(ls.Locations, lr)
|
||||||
|
|
||||||
|
if _, ok := ls.Deleted[lr]; !ok {
|
||||||
|
ls.Deleted[lr] = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ir := range items {
|
||||||
|
ls.Deleted[lr][ir] = exists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveLocation takes the LAST elemet in the fromLocation (and all)
|
||||||
|
// children matching the prefix, and relocates it as a child of toLocation.
|
||||||
|
// ex: MoveLocation("/a/b/c", "/d") will move all entries with the prefix
|
||||||
|
// "/a/b/c" into "/d/c". This also deletes all "/a/b/c" entries and children.
|
||||||
|
// assumes item IDs don't change across the migration. If item IDs do change,
|
||||||
|
// that difference will need to be handled manually by the caller.
|
||||||
|
// returns the base folder's new location (ex: /d/c)
|
||||||
|
func (ls *locSet) MoveLocation(fromLocation, toLocation string) string {
|
||||||
|
fromBuilder := path.Builder{}.Append(path.Split(fromLocation)...)
|
||||||
|
toBuilder := path.Builder{}.Append(path.Split(toLocation)...).Append(fromBuilder.LastElem())
|
||||||
|
|
||||||
|
ls.RenameLocation(fromBuilder.String(), toBuilder.String())
|
||||||
|
|
||||||
|
return toBuilder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *locSet) RenameLocation(fromLocation, toLocation string) {
|
||||||
|
ss := ls.Subset(fromLocation)
|
||||||
|
fromBuilder := path.Builder{}.Append(path.Split(fromLocation)...)
|
||||||
|
toBuilder := path.Builder{}.Append(path.Split(toLocation)...)
|
||||||
|
|
||||||
|
for lr, items := range ss.Locations {
|
||||||
|
lrBuilder := path.Builder{}.Append(path.Split(lr)...)
|
||||||
|
lrBuilder.UpdateParent(fromBuilder, toBuilder)
|
||||||
|
|
||||||
|
newLoc := lrBuilder.String()
|
||||||
|
|
||||||
|
for ir := range items {
|
||||||
|
ls.RemoveItem(lr, ir)
|
||||||
|
ls.AddItem(newLoc, ir)
|
||||||
|
}
|
||||||
|
|
||||||
|
ls.RemoveLocation(lr)
|
||||||
|
ls.AddLocation(newLoc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subset produces a new locSet containing only Items and Locations
|
||||||
|
// whose location matches the locationPfx
|
||||||
|
func (ls *locSet) Subset(locationPfx string) *locSet {
|
||||||
|
ss := newLocSet()
|
||||||
|
|
||||||
|
for lr, items := range ls.Locations {
|
||||||
|
if strings.HasPrefix(lr, locationPfx) {
|
||||||
|
ss.AddLocation(lr)
|
||||||
|
|
||||||
|
for ir := range items {
|
||||||
|
ss.AddItem(lr, ir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// The goal of InDeets is to provide a struct and interface which allows
|
||||||
|
// tests to predict not just the elements within a set of details entries,
|
||||||
|
// but also their changes (relocation, renaming, etc) in a way that consolidates
|
||||||
|
// building an "expected set" of details entries that can be compared against
|
||||||
|
// the details results after a backup.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// InDeets is a helper for comparing details state in tests
|
||||||
|
// across backup instances.
|
||||||
|
type InDeets struct {
|
||||||
|
// only: tenantID/service/resourceOwnerID
|
||||||
|
RRPrefix string
|
||||||
|
// map of container setting the uniqueness boundary for location
|
||||||
|
// ref entries (eg, data type like email, contacts, etc, or
|
||||||
|
// drive id) to the unique entries in that set.
|
||||||
|
Sets map[string]*locSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInDeets(repoRefPrefix string) *InDeets {
|
||||||
|
return &InDeets{
|
||||||
|
RRPrefix: repoRefPrefix,
|
||||||
|
Sets: map[string]*locSet{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id *InDeets) getSet(set string) *locSet {
|
||||||
|
s, ok := id.Sets[set]
|
||||||
|
if ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return newLocSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id *InDeets) AddAll(deets details.Details, ws whatSet) {
|
||||||
|
if id.Sets == nil {
|
||||||
|
id.Sets = map[string]*locSet{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ent := range deets.Entries {
|
||||||
|
set, err := ws(ent)
|
||||||
|
if err != nil {
|
||||||
|
set = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := ent.LocationRef
|
||||||
|
|
||||||
|
if ent.Folder != nil {
|
||||||
|
dir = dir + ent.Folder.DisplayName
|
||||||
|
id.AddLocation(set, dir)
|
||||||
|
} else {
|
||||||
|
id.AddItem(set, ent.LocationRef, ent.ItemRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id *InDeets) AddItem(set, locationRef, itemRef string) {
|
||||||
|
id.getSet(set).AddItem(locationRef, itemRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id *InDeets) RemoveItem(set, locationRef, itemRef string) {
|
||||||
|
id.getSet(set).RemoveItem(locationRef, itemRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id *InDeets) MoveItem(set, fromLocation, toLocation, ir string) {
|
||||||
|
id.getSet(set).MoveItem(fromLocation, toLocation, ir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id *InDeets) AddLocation(set, locationRef string) {
|
||||||
|
id.getSet(set).AddLocation(locationRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveLocation removes the provided location, and all children
|
||||||
|
// of that location.
|
||||||
|
func (id *InDeets) RemoveLocation(set, locationRef string) {
|
||||||
|
id.getSet(set).RemoveLocation(locationRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveLocation takes the LAST elemet in the fromLocation (and all)
|
||||||
|
// children matching the prefix, and relocates it as a child of toLocation.
|
||||||
|
// ex: MoveLocation("/a/b/c", "/d") will move all entries with the prefix
|
||||||
|
// "/a/b/c" into "/d/c". This also deletes all "/a/b/c" entries and children.
|
||||||
|
// assumes item IDs don't change across the migration. If item IDs do change,
|
||||||
|
// that difference will need to be handled manually by the caller.
|
||||||
|
// returns the base folder's new location (ex: /d/c)
|
||||||
|
func (id *InDeets) MoveLocation(set, fromLocation, toLocation string) string {
|
||||||
|
return id.getSet(set).MoveLocation(fromLocation, toLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id *InDeets) RenameLocation(set, fromLocation, toLocation string) {
|
||||||
|
id.getSet(set).RenameLocation(fromLocation, toLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subset produces a new locSet containing only Items and Locations
|
||||||
|
// whose location matches the locationPfx
|
||||||
|
func (id *InDeets) Subset(set, locationPfx string) *locSet {
|
||||||
|
return id.getSet(set).Subset(locationPfx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// whatSet helpers for extracting a set identifier from an arbitrary repoRef
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type whatSet func(details.Entry) (string, error)
|
||||||
|
|
||||||
|
// common whatSet parser that extracts the service category from
|
||||||
|
// a repoRef.
|
||||||
|
func CategoryFromRepoRef(ent details.Entry) (string, error) {
|
||||||
|
p, err := path.FromDataLayerPath(ent.RepoRef, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Category().String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// common whatSet parser that extracts the driveID from a repoRef.
|
||||||
|
func DriveIDFromRepoRef(ent details.Entry) (string, error) {
|
||||||
|
p, err := path.FromDataLayerPath(ent.RepoRef, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
odp, err := path.ToDrivePath(p)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return odp.DriveID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// helpers and comparators
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func CheckBackupDetails(
|
||||||
|
t *testing.T,
|
||||||
|
ctx context.Context, //revive:disable-line:context-as-argument
|
||||||
|
backupID model.StableID,
|
||||||
|
ws whatSet,
|
||||||
|
ms *kopia.ModelStore,
|
||||||
|
ssr streamstore.Reader,
|
||||||
|
expect *InDeets,
|
||||||
|
// standard check is assert.Subset due to issues of external data cross-
|
||||||
|
// pollination. This should be true if the backup contains a unique directory
|
||||||
|
// of data.
|
||||||
|
mustEqualFolders bool,
|
||||||
|
) {
|
||||||
|
deets, result := GetDeetsInBackup(t, ctx, backupID, "", "", path.UnknownService, ws, ms, ssr)
|
||||||
|
|
||||||
|
t.Log("details entries in result")
|
||||||
|
|
||||||
|
for _, ent := range deets.Entries {
|
||||||
|
if ent.Folder == nil {
|
||||||
|
t.Log(ent.LocationRef)
|
||||||
|
t.Log(ent.ItemRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Truef(
|
||||||
|
t,
|
||||||
|
strings.HasPrefix(ent.RepoRef, expect.RRPrefix),
|
||||||
|
"all details should begin with the expected prefix\nwant: %s\ngot: %s",
|
||||||
|
expect.RRPrefix, ent.RepoRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
for set := range expect.Sets {
|
||||||
|
check := assert.Subsetf
|
||||||
|
|
||||||
|
if mustEqualFolders {
|
||||||
|
check = assert.ElementsMatchf
|
||||||
|
}
|
||||||
|
|
||||||
|
check(
|
||||||
|
t,
|
||||||
|
maps.Keys(result.Sets[set].Locations),
|
||||||
|
maps.Keys(expect.Sets[set].Locations),
|
||||||
|
"results in %s missing expected location", set)
|
||||||
|
|
||||||
|
for lr, items := range expect.Sets[set].Deleted {
|
||||||
|
_, ok := result.Sets[set].Locations[lr]
|
||||||
|
assert.Falsef(t, ok, "deleted location in %s found in result: %s", set, lr)
|
||||||
|
|
||||||
|
for ir := range items {
|
||||||
|
_, ok := result.Sets[set].Locations[lr][ir]
|
||||||
|
assert.Falsef(t, ok, "deleted item in %s found in result: %s", set, lr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDeetsInBackup(
|
||||||
|
t *testing.T,
|
||||||
|
ctx context.Context, //revive:disable-line:context-as-argument
|
||||||
|
backupID model.StableID,
|
||||||
|
tid, resourceOwner string,
|
||||||
|
service path.ServiceType,
|
||||||
|
ws whatSet,
|
||||||
|
ms *kopia.ModelStore,
|
||||||
|
ssr streamstore.Reader,
|
||||||
|
) (details.Details, *InDeets) {
|
||||||
|
bup := backup.Backup{}
|
||||||
|
|
||||||
|
err := ms.Get(ctx, model.BackupSchema, backupID, &bup)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ssid := bup.StreamStoreID
|
||||||
|
require.NotEmpty(t, ssid, "stream store ID")
|
||||||
|
|
||||||
|
var deets details.Details
|
||||||
|
err = ssr.Read(
|
||||||
|
ctx,
|
||||||
|
ssid,
|
||||||
|
streamstore.DetailsReader(details.UnmarshalTo(&deets)),
|
||||||
|
fault.New(true))
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
id := NewInDeets(path.Builder{}.Append(tid, service.String(), resourceOwner).String())
|
||||||
|
id.AddAll(deets, ws)
|
||||||
|
|
||||||
|
return deets, id
|
||||||
|
}
|
||||||
445
src/pkg/backup/details/testdata/in_deets_test.go
vendored
Normal file
445
src/pkg/backup/details/testdata/in_deets_test.go
vendored
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
package testdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LocSetUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocSetUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &LocSetUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
l1 = "lr_1"
|
||||||
|
l2 = "lr_2"
|
||||||
|
l13 = "lr_1/lr_3"
|
||||||
|
l14 = "lr_1/lr_4"
|
||||||
|
i1 = "ir_1"
|
||||||
|
i2 = "ir_2"
|
||||||
|
i3 = "ir_3"
|
||||||
|
i4 = "ir_4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *LocSetUnitSuite) TestAdd() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ls := newLocSet()
|
||||||
|
|
||||||
|
ls.AddItem(l1, i1)
|
||||||
|
ls.AddLocation(l2)
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, []string{l1, l2}, maps.Keys(ls.Locations))
|
||||||
|
assert.ElementsMatch(t, []string{i1}, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l2]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l13]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LocSetUnitSuite) TestRemove() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ls := newLocSet()
|
||||||
|
|
||||||
|
ls.AddItem(l1, i1)
|
||||||
|
ls.AddItem(l1, i2)
|
||||||
|
ls.AddLocation(l13)
|
||||||
|
ls.AddItem(l14, i3)
|
||||||
|
ls.AddItem(l14, i4)
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, l14}, maps.Keys(ls.Locations))
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l13]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ls.Locations[l14]))
|
||||||
|
|
||||||
|
// nop removal
|
||||||
|
ls.RemoveItem(l2, i1)
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ls.Locations[l1]))
|
||||||
|
|
||||||
|
// item removal
|
||||||
|
ls.RemoveItem(l1, i2)
|
||||||
|
assert.ElementsMatch(t, []string{i1}, maps.Keys(ls.Locations[l1]))
|
||||||
|
|
||||||
|
// nop location removal
|
||||||
|
ls.RemoveLocation(l2)
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, l14}, maps.Keys(ls.Locations))
|
||||||
|
|
||||||
|
// non-cascading location removal
|
||||||
|
ls.RemoveLocation(l13)
|
||||||
|
assert.ElementsMatch(t, []string{l1, l14}, maps.Keys(ls.Locations))
|
||||||
|
assert.ElementsMatch(t, []string{i1}, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ls.Locations[l14]))
|
||||||
|
|
||||||
|
// cascading location removal
|
||||||
|
ls.RemoveLocation(l1)
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l13]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l14]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LocSetUnitSuite) TestSubset() {
|
||||||
|
ls := newLocSet()
|
||||||
|
|
||||||
|
ls.AddItem(l1, i1)
|
||||||
|
ls.AddItem(l1, i2)
|
||||||
|
ls.AddLocation(l13)
|
||||||
|
ls.AddItem(l14, i3)
|
||||||
|
ls.AddItem(l14, i4)
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
locPfx string
|
||||||
|
expect func(*testing.T, *locSet)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nop",
|
||||||
|
locPfx: l2,
|
||||||
|
expect: func(t *testing.T, ss *locSet) {
|
||||||
|
assert.Empty(t, maps.Keys(ss.Locations))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no items",
|
||||||
|
locPfx: l13,
|
||||||
|
expect: func(t *testing.T, ss *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l13}, maps.Keys(ss.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ss.Locations[l13]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-cascading",
|
||||||
|
locPfx: l14,
|
||||||
|
expect: func(t *testing.T, ss *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l14}, maps.Keys(ss.Locations))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ss.Locations[l14]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cascading",
|
||||||
|
locPfx: l1,
|
||||||
|
expect: func(t *testing.T, ss *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, l14}, maps.Keys(ss.Locations))
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ss.Locations[l1]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ss.Locations[l14]))
|
||||||
|
assert.Empty(t, maps.Keys(ss.Locations[l13]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
test.expect(t, ls.Subset(test.locPfx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LocSetUnitSuite) TestRename() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
makeSet := func() *locSet {
|
||||||
|
ls := newLocSet()
|
||||||
|
|
||||||
|
ls.AddItem(l1, i1)
|
||||||
|
ls.AddItem(l1, i2)
|
||||||
|
ls.AddLocation(l13)
|
||||||
|
ls.AddItem(l14, i3)
|
||||||
|
ls.AddItem(l14, i4)
|
||||||
|
|
||||||
|
return ls
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := makeSet()
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, l14}, maps.Keys(ts.Locations))
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ts.Locations[l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ts.Locations[l13]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ts.Locations[l14]))
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
from string
|
||||||
|
to string
|
||||||
|
expect func(*testing.T, *locSet)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nop",
|
||||||
|
from: l2,
|
||||||
|
to: "foo",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, l14}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l2]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations["foo"]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no items",
|
||||||
|
from: l13,
|
||||||
|
to: "foo",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l1, "foo", l14}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l13]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations["foo"]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with items",
|
||||||
|
from: l14,
|
||||||
|
to: "foo",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, "foo"}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l14]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ls.Locations["foo"]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cascading locations",
|
||||||
|
from: l1,
|
||||||
|
to: "foo",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{"foo", "foo/lr_3", "foo/lr_4"}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l14]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l13]))
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ls.Locations["foo"]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations["foo/lr_3"]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ls.Locations["foo/lr_4"]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "to existing location",
|
||||||
|
from: l14,
|
||||||
|
to: l1,
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l14]))
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2, i3, i4}, maps.Keys(ls.Locations[l1]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
ls := makeSet()
|
||||||
|
|
||||||
|
ls.RenameLocation(test.from, test.to)
|
||||||
|
test.expect(t, ls)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LocSetUnitSuite) TestItem() {
|
||||||
|
t := suite.T()
|
||||||
|
b4 := "bar/lr_4"
|
||||||
|
|
||||||
|
makeSet := func() *locSet {
|
||||||
|
ls := newLocSet()
|
||||||
|
|
||||||
|
ls.AddItem(l1, i1)
|
||||||
|
ls.AddItem(l1, i2)
|
||||||
|
ls.AddLocation(l13)
|
||||||
|
ls.AddItem(l14, i3)
|
||||||
|
ls.AddItem(l14, i4)
|
||||||
|
ls.AddItem(b4, "fnord")
|
||||||
|
|
||||||
|
return ls
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := makeSet()
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, l14, b4}, maps.Keys(ts.Locations))
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ts.Locations[l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ts.Locations[l13]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ts.Locations[l14]))
|
||||||
|
assert.ElementsMatch(t, []string{"fnord"}, maps.Keys(ts.Locations[b4]))
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
item string
|
||||||
|
from string
|
||||||
|
to string
|
||||||
|
expect func(*testing.T, *locSet)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nop item",
|
||||||
|
item: "floob",
|
||||||
|
from: l2,
|
||||||
|
to: l1,
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2, "floob"}, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l2]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nop origin",
|
||||||
|
item: i1,
|
||||||
|
from: "smarf",
|
||||||
|
to: l2,
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.ElementsMatch(t, []string{i1}, maps.Keys(ls.Locations[l2]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations["smarf"]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "new location",
|
||||||
|
item: i1,
|
||||||
|
from: l1,
|
||||||
|
to: "fnords",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{i2}, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.ElementsMatch(t, []string{i1}, maps.Keys(ls.Locations["fnords"]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing location",
|
||||||
|
item: i1,
|
||||||
|
from: l1,
|
||||||
|
to: l2,
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{i2}, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.ElementsMatch(t, []string{i1}, maps.Keys(ls.Locations[l2]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same location",
|
||||||
|
item: i1,
|
||||||
|
from: l1,
|
||||||
|
to: l1,
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ls.Locations[l1]))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
ls := makeSet()
|
||||||
|
|
||||||
|
ls.MoveItem(test.from, test.to, test.item)
|
||||||
|
test.expect(t, ls)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LocSetUnitSuite) TestMoveLocation() {
|
||||||
|
t := suite.T()
|
||||||
|
b4 := "bar/lr_4"
|
||||||
|
|
||||||
|
makeSet := func() *locSet {
|
||||||
|
ls := newLocSet()
|
||||||
|
|
||||||
|
ls.AddItem(l1, i1)
|
||||||
|
ls.AddItem(l1, i2)
|
||||||
|
ls.AddLocation(l13)
|
||||||
|
ls.AddItem(l14, i3)
|
||||||
|
ls.AddItem(l14, i4)
|
||||||
|
ls.AddItem(b4, "fnord")
|
||||||
|
|
||||||
|
return ls
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := makeSet()
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, l14, b4}, maps.Keys(ts.Locations))
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ts.Locations[l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ts.Locations[l13]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ts.Locations[l14]))
|
||||||
|
assert.ElementsMatch(t, []string{"fnord"}, maps.Keys(ts.Locations[b4]))
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
from string
|
||||||
|
to string
|
||||||
|
expect func(*testing.T, *locSet)
|
||||||
|
expectNewLoc string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nop root",
|
||||||
|
from: l2,
|
||||||
|
to: "",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, l14, b4}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l2]))
|
||||||
|
},
|
||||||
|
expectNewLoc: l2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nop child",
|
||||||
|
from: l2,
|
||||||
|
to: "foo",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, l14, b4}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations["foo"]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations["foo/"+l2]))
|
||||||
|
},
|
||||||
|
expectNewLoc: "foo/" + l2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no items",
|
||||||
|
from: l13,
|
||||||
|
to: "foo",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
newLoc := "foo/lr_3"
|
||||||
|
assert.ElementsMatch(t, []string{l1, newLoc, l14, b4}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l13]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[newLoc]))
|
||||||
|
},
|
||||||
|
expectNewLoc: "foo/lr_3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with items",
|
||||||
|
from: l14,
|
||||||
|
to: "foo",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
newLoc := "foo/lr_4"
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, newLoc, b4}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l14]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ls.Locations[newLoc]))
|
||||||
|
},
|
||||||
|
expectNewLoc: "foo/lr_4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cascading locations",
|
||||||
|
from: l1,
|
||||||
|
to: "foo",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
pfx := "foo/"
|
||||||
|
assert.ElementsMatch(t, []string{pfx + l1, pfx + l13, pfx + l14, b4}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l14]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l13]))
|
||||||
|
assert.ElementsMatch(t, []string{i1, i2}, maps.Keys(ls.Locations[pfx+l1]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[pfx+l13]))
|
||||||
|
assert.ElementsMatch(t, []string{i3, i4}, maps.Keys(ls.Locations[pfx+l14]))
|
||||||
|
},
|
||||||
|
expectNewLoc: "foo/" + l1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "to existing location",
|
||||||
|
from: l14,
|
||||||
|
to: "bar",
|
||||||
|
expect: func(t *testing.T, ls *locSet) {
|
||||||
|
assert.ElementsMatch(t, []string{l1, l13, b4}, maps.Keys(ls.Locations))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations[l14]))
|
||||||
|
assert.Empty(t, maps.Keys(ls.Locations["bar"]))
|
||||||
|
assert.ElementsMatch(t, []string{"fnord", i3, i4}, maps.Keys(ls.Locations[b4]))
|
||||||
|
},
|
||||||
|
expectNewLoc: b4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
ls := makeSet()
|
||||||
|
|
||||||
|
newLoc := ls.MoveLocation(test.from, test.to)
|
||||||
|
test.expect(t, ls)
|
||||||
|
assert.Equal(t, test.expectNewLoc, newLoc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -85,7 +85,7 @@ type Path interface {
|
|||||||
Category() CategoryType
|
Category() CategoryType
|
||||||
Tenant() string
|
Tenant() string
|
||||||
ResourceOwner() string
|
ResourceOwner() string
|
||||||
Folder(bool) string
|
Folder(escaped bool) string
|
||||||
Folders() Elements
|
Folders() Elements
|
||||||
Item() string
|
Item() string
|
||||||
// UpdateParent updates parent from old to new if the item/folder was
|
// UpdateParent updates parent from old to new if the item/folder was
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user