add locationRef to details

Adds a new reference to the details ent: location-
ref.  The location holds the human-readable
version of the item's location in whatever m365
service sourced the item.  Hookup is incomplete,
following PRs will fill out functionality.

Also adds a LocationPather interface to data_
collections to pass this data back and forth
between producers and consumers.
This commit is contained in:
ryanfkeepers 2023-02-07 12:28:43 -07:00
parent 47f5ca1d95
commit 1b1629e09c
10 changed files with 223 additions and 94 deletions

View File

@ -595,7 +595,7 @@ func (suite *FolderCacheIntegrationSuite) TestCreateContainerDestination() {
for _, test := range tests { for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
folderID, err := CreateContainerDestinaion( folderID, err := CreateContainerDestination(
ctx, ctx,
m365, m365,
test.pathFunc1(t), test.pathFunc1(t),
@ -608,7 +608,7 @@ func (suite *FolderCacheIntegrationSuite) TestCreateContainerDestination() {
_, err = resolver.IDToPath(ctx, folderID) _, err = resolver.IDToPath(ctx, folderID)
assert.NoError(t, err) assert.NoError(t, err)
secondID, err := CreateContainerDestinaion( secondID, err := CreateContainerDestination(
ctx, ctx,
m365, m365,
test.pathFunc2(t), test.pathFunc2(t),

View File

@ -344,7 +344,7 @@ func RestoreExchangeDataCollections(
userCaches = directoryCaches[userID] userCaches = directoryCaches[userID]
} }
containerID, err := CreateContainerDestinaion( containerID, err := CreateContainerDestination(
ctx, ctx,
creds, creds,
dc.FullPath(), dc.FullPath(),
@ -447,10 +447,16 @@ func restoreCollection(
continue continue
} }
// var locationRef string
// if category == path.ContactsCategory {
// locationRef = itemPath.Folder()
// }
deets.Add( deets.Add(
itemPath.String(), itemPath.String(),
itemPath.ShortRef(), itemPath.ShortRef(),
"", "",
"", // TODO: locationRef
true, true,
details.ItemInfo{ details.ItemInfo{
Exchange: info, Exchange: info,
@ -461,12 +467,12 @@ func restoreCollection(
} }
} }
// CreateContainerDestinaion builds the destination into the container // CreateContainerDestination builds the destination into the container
// at the provided path. As a precondition, the destination cannot // at the provided path. As a precondition, the destination cannot
// already exist. If it does then an error is returned. The provided // already exist. If it does then an error is returned. The provided
// containerResolver is updated with the new destination. // containerResolver is updated with the new destination.
// @ returns the container ID of the new destination container. // @ returns the container ID of the new destination container.
func CreateContainerDestinaion( func CreateContainerDestination(
ctx context.Context, ctx context.Context,
creds account.M365Config, creds account.M365Config,
directory path.Path, directory path.Path,

View File

@ -235,7 +235,13 @@ func RestoreCollection(
restoredIDs[trimmedName] = itemID restoredIDs[trimmedName] = itemID
deets.Add(itemPath.String(), itemPath.ShortRef(), "", true, itemInfo) deets.Add(
itemPath.String(),
itemPath.ShortRef(),
"",
"", // TODO: implement locationRef
true,
itemInfo)
// Mark it as success without processing .meta // Mark it as success without processing .meta
// file if we are not restoring permissions // file if we are not restoring permissions
@ -343,7 +349,13 @@ func RestoreCollection(
continue continue
} }
deets.Add(itemPath.String(), itemPath.ShortRef(), "", true, itemInfo) deets.Add(
itemPath.String(),
itemPath.ShortRef(),
"",
"", // TODO: implement locationRef
true,
itemInfo)
metrics.Successes++ metrics.Successes++
} }
} }

View File

@ -276,6 +276,7 @@ func RestoreListCollection(
itemPath.String(), itemPath.String(),
itemPath.ShortRef(), itemPath.ShortRef(),
"", "",
"", // TODO: implement locationRef
true, true,
itemInfo) itemInfo)
@ -355,6 +356,7 @@ func RestorePageCollection(
itemPath.String(), itemPath.String(),
itemPath.ShortRef(), itemPath.ShortRef(),
"", "",
"", // TODO: implement locationRef
true, true,
itemInfo, itemInfo,
) )

View File

@ -92,6 +92,12 @@ type Stream interface {
Deleted() bool Deleted() bool
} }
// Locationer provides a LocationPath describing the path with Display Names
// instead of canonical IDs
type LocationPather interface {
LocationPath() path.Path
}
// StreamInfo is used to provide service specific // StreamInfo is used to provide service specific
// information about the Stream // information about the Stream
type StreamInfo interface { type StreamInfo interface {

View File

@ -124,10 +124,11 @@ func (rw *restoreStreamReader) Read(p []byte) (n int, err error) {
} }
type itemDetails struct { type itemDetails struct {
info *details.ItemInfo info *details.ItemInfo
repoPath path.Path repoPath path.Path
prevPath path.Path prevPath path.Path
cached bool locationPath path.Path
cached bool
} }
type corsoProgress struct { type corsoProgress struct {
@ -188,20 +189,24 @@ func (cp *corsoProgress) FinishedFile(relativePath string, err error) {
parent := d.repoPath.ToBuilder().Dir() parent := d.repoPath.ToBuilder().Dir()
var locationFolders string
if d.locationPath != nil {
locationFolders = d.locationPath.Folder()
}
cp.deets.Add( cp.deets.Add(
d.repoPath.String(), d.repoPath.String(),
d.repoPath.ShortRef(), d.repoPath.ShortRef(),
parent.ShortRef(), parent.ShortRef(),
locationFolders,
!d.cached, !d.cached,
*d.info, *d.info)
)
folders := details.FolderEntriesForPath(parent) folders := details.FolderEntriesForPath(parent, d.locationPath.ToBuilder())
cp.deets.AddFoldersForItem( cp.deets.AddFoldersForItem(
folders, folders,
*d.info, *d.info,
!d.cached, !d.cached)
)
} }
// Kopia interface function used as a callback when kopia finishes hashing a file. // Kopia interface function used as a callback when kopia finishes hashing a file.
@ -311,6 +316,12 @@ func collectionEntries(
continue continue
} }
var locationPath path.Path
if lp, ok := e.(data.LocationPather); ok {
locationPath = lp.LocationPath()
}
trace.Log(ctx, "kopia:streamEntries:item", itemPath.String()) trace.Log(ctx, "kopia:streamEntries:item", itemPath.String())
if e.Deleted() { if e.Deleted() {
@ -332,7 +343,11 @@ func collectionEntries(
// previous snapshot then we should populate prevPath here and leave // previous snapshot then we should populate prevPath here and leave
// info nil. // info nil.
itemInfo := ei.Info() itemInfo := ei.Info()
d := &itemDetails{info: &itemInfo, repoPath: itemPath} d := &itemDetails{
info: &itemInfo,
repoPath: itemPath,
locationPath: locationPath,
}
progress.put(encodeAsPath(itemPath.PopFront().Elements()...), d) progress.put(encodeAsPath(itemPath.PopFront().Elements()...), d)
} }

View File

@ -581,10 +581,11 @@ func mergeDetails(
newPath.String(), newPath.String(),
newPath.ShortRef(), newPath.ShortRef(),
newPath.ToBuilder().Dir().ShortRef(), newPath.ToBuilder().Dir().ShortRef(),
"", // TODO Location Ref,
itemUpdated, itemUpdated,
item) item)
folders := details.FolderEntriesForPath(newPath.ToBuilder().Dir()) folders := details.FolderEntriesForPath(newPath.ToBuilder().Dir(), nil)
deets.AddFoldersForItem(folders, item, itemUpdated) deets.AddFoldersForItem(folders, item, itemUpdated)
// Track how many entries we added so that we know if we got them all when // Track how many entries we added so that we know if we got them all when

View File

@ -44,7 +44,7 @@ func (suite *StreamStoreIntegrationSuite) TestDetails() {
deetsBuilder := &details.Builder{} deetsBuilder := &details.Builder{}
deetsBuilder.Add("ref", "shortref", "parentref", true, deetsBuilder.Add("ref", "shortref", "parentref", "locationRef", true,
details.ItemInfo{ details.ItemInfo{
Exchange: &details.ExchangeInfo{ Exchange: &details.ExchangeInfo{
Subject: "hello world", Subject: "hello world",
@ -66,6 +66,7 @@ func (suite *StreamStoreIntegrationSuite) TestDetails() {
assert.Equal(t, deets.Entries[0].ParentRef, readDeets.Entries[0].ParentRef) assert.Equal(t, deets.Entries[0].ParentRef, readDeets.Entries[0].ParentRef)
assert.Equal(t, deets.Entries[0].ShortRef, readDeets.Entries[0].ShortRef) assert.Equal(t, deets.Entries[0].ShortRef, readDeets.Entries[0].ShortRef)
assert.Equal(t, deets.Entries[0].RepoRef, readDeets.Entries[0].RepoRef) assert.Equal(t, deets.Entries[0].RepoRef, readDeets.Entries[0].RepoRef)
assert.Equal(t, deets.Entries[0].LocationRef, readDeets.Entries[0].LocationRef)
assert.Equal(t, deets.Entries[0].Updated, readDeets.Entries[0].Updated) assert.Equal(t, deets.Entries[0].Updated, readDeets.Entries[0].Updated)
assert.NotNil(t, readDeets.Entries[0].Exchange) assert.NotNil(t, readDeets.Entries[0].Exchange)
assert.Equal(t, *deets.Entries[0].Exchange, *readDeets.Entries[0].Exchange) assert.Equal(t, *deets.Entries[0].Exchange, *readDeets.Entries[0].Exchange)

View File

@ -15,11 +15,12 @@ import (
) )
type folderEntry struct { type folderEntry struct {
RepoRef string RepoRef string
ShortRef string ShortRef string
ParentRef string ParentRef string
Updated bool LocationRef string
Info ItemInfo Updated bool
Info ItemInfo
} }
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
@ -110,10 +111,14 @@ type Builder struct {
knownFolders map[string]folderEntry `json:"-"` knownFolders map[string]folderEntry `json:"-"`
} }
func (b *Builder) Add(repoRef, shortRef, parentRef string, updated bool, info ItemInfo) { func (b *Builder) Add(
repoRef, shortRef, parentRef, locationRef string,
updated bool,
info ItemInfo,
) {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
b.d.add(repoRef, shortRef, parentRef, updated, info) b.d.add(repoRef, shortRef, parentRef, locationRef, updated, info)
} }
func (b *Builder) Details() *Details { func (b *Builder) Details() *Details {
@ -131,16 +136,23 @@ func (b *Builder) Details() *Details {
// TODO(ashmrtn): If we never need to pre-populate the modified time of a folder // TODO(ashmrtn): If we never need to pre-populate the modified time of a folder
// we should just merge this with AddFoldersForItem, have Add call // we should just merge this with AddFoldersForItem, have Add call
// AddFoldersForItem, and unexport AddFoldersForItem. // AddFoldersForItem, and unexport AddFoldersForItem.
func FolderEntriesForPath(parent *path.Builder) []folderEntry { func FolderEntriesForPath(parent, location *path.Builder) []folderEntry {
folders := []folderEntry{} folders := []folderEntry{}
lfs := locationRefOf(location)
for len(parent.Elements()) > 0 { for len(parent.Elements()) > 0 {
nextParent := parent.Dir() nextParent := parent.Dir()
var lr string
if lfs != nil {
lr = lfs.String()
}
folders = append(folders, folderEntry{ folders = append(folders, folderEntry{
RepoRef: parent.String(), RepoRef: parent.String(),
ShortRef: parent.ShortRef(), ShortRef: parent.ShortRef(),
ParentRef: nextParent.ShortRef(), ParentRef: nextParent.ShortRef(),
LocationRef: lr,
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{ Folder: &FolderInfo{
ItemType: FolderItem, ItemType: FolderItem,
@ -150,11 +162,30 @@ func FolderEntriesForPath(parent *path.Builder) []folderEntry {
}) })
parent = nextParent parent = nextParent
if lfs != nil {
lfs = lfs.Dir()
}
} }
return folders return folders
} }
// assumes the pb contains a path like:
// <tenant>/<service>/<owner>/<category>/<logical_containers>...
// and returns a string with only <logical_containers>/...
func locationRefOf(pb *path.Builder) *path.Builder {
if pb == nil {
return nil
}
for i := 0; i < 4; i++ {
pb = pb.PopFront()
}
return pb
}
// AddFoldersForItem adds entries for the given folders. It skips adding entries that // AddFoldersForItem adds entries for the given folders. It skips adding entries that
// have been added by previous calls. // have been added by previous calls.
func (b *Builder) AddFoldersForItem(folders []folderEntry, itemInfo ItemInfo, updated bool) { func (b *Builder) AddFoldersForItem(folders []folderEntry, itemInfo ItemInfo, updated bool) {
@ -202,7 +233,11 @@ type Details struct {
DetailsModel DetailsModel
} }
func (d *Details) add(repoRef, shortRef, parentRef string, updated bool, info ItemInfo) { func (d *Details) add(
repoRef, shortRef, parentRef, locationRef string,
updated bool,
info ItemInfo,
) {
d.Entries = append(d.Entries, DetailsEntry{ d.Entries = append(d.Entries, DetailsEntry{
RepoRef: repoRef, RepoRef: repoRef,
ShortRef: shortRef, ShortRef: shortRef,
@ -233,9 +268,21 @@ type DetailsEntry struct {
RepoRef string `json:"repoRef"` RepoRef string `json:"repoRef"`
ShortRef string `json:"shortRef"` ShortRef string `json:"shortRef"`
ParentRef string `json:"parentRef,omitempty"` ParentRef string `json:"parentRef,omitempty"`
// LocationRef contains the logical path structure by its human-readable
// display names. IE: If an item is located at "/Inbox/Important", we
// hold that string in the LocationRef, while the actual IDs of each
// container are used for the RepoRef.
// LocationRef only holds the container values, and does not include
// the metadata prefixes (tenant, service, owner, etc) found in the
// repoRef.
// Currently only implemented for Exchange Calendars.
LocationRef string `json:"locationRef,omitempty"`
// Indicates the item was added or updated in this backup // Indicates the item was added or updated in this backup
// Always `true` for full backups // Always `true` for full backups
Updated bool `json:"updated"` Updated bool `json:"updated"`
ItemInfo ItemInfo
} }

View File

@ -39,8 +39,9 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
{ {
name: "no info", name: "no info",
entry: DetailsEntry{ entry: DetailsEntry{
RepoRef: "reporef", RepoRef: "reporef",
ShortRef: "deadbeef", ShortRef: "deadbeef",
LocationRef: "locationref",
}, },
expectHs: []string{"ID"}, expectHs: []string{"ID"},
expectVs: []string{"deadbeef"}, expectVs: []string{"deadbeef"},
@ -48,8 +49,9 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
{ {
name: "exchange event info", name: "exchange event info",
entry: DetailsEntry{ entry: DetailsEntry{
RepoRef: "reporef", RepoRef: "reporef",
ShortRef: "deadbeef", ShortRef: "deadbeef",
LocationRef: "locationref",
ItemInfo: ItemInfo{ ItemInfo: ItemInfo{
Exchange: &ExchangeInfo{ Exchange: &ExchangeInfo{
ItemType: ExchangeEvent, ItemType: ExchangeEvent,
@ -67,8 +69,9 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
{ {
name: "exchange contact info", name: "exchange contact info",
entry: DetailsEntry{ entry: DetailsEntry{
RepoRef: "reporef", RepoRef: "reporef",
ShortRef: "deadbeef", ShortRef: "deadbeef",
LocationRef: "locationref",
ItemInfo: ItemInfo{ ItemInfo: ItemInfo{
Exchange: &ExchangeInfo{ Exchange: &ExchangeInfo{
ItemType: ExchangeContact, ItemType: ExchangeContact,
@ -82,8 +85,9 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
{ {
name: "exchange mail info", name: "exchange mail info",
entry: DetailsEntry{ entry: DetailsEntry{
RepoRef: "reporef", RepoRef: "reporef",
ShortRef: "deadbeef", ShortRef: "deadbeef",
LocationRef: "locationref",
ItemInfo: ItemInfo{ ItemInfo: ItemInfo{
Exchange: &ExchangeInfo{ Exchange: &ExchangeInfo{
ItemType: ExchangeMail, ItemType: ExchangeMail,
@ -99,8 +103,9 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
{ {
name: "sharepoint info", name: "sharepoint info",
entry: DetailsEntry{ entry: DetailsEntry{
RepoRef: "reporef", RepoRef: "reporef",
ShortRef: "deadbeef", ShortRef: "deadbeef",
LocationRef: "locationref",
ItemInfo: ItemInfo{ ItemInfo: ItemInfo{
SharePoint: &SharePointInfo{ SharePoint: &SharePointInfo{
ItemName: "itemName", ItemName: "itemName",
@ -128,8 +133,9 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
{ {
name: "oneDrive info", name: "oneDrive info",
entry: DetailsEntry{ entry: DetailsEntry{
RepoRef: "reporef", RepoRef: "reporef",
ShortRef: "deadbeef", ShortRef: "deadbeef",
LocationRef: "locationref",
ItemInfo: ItemInfo{ ItemInfo: ItemInfo{
OneDrive: &OneDriveInfo{ OneDrive: &OneDriveInfo{
ItemName: "itemName", ItemName: "itemName",
@ -157,37 +163,57 @@ func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() {
} }
var pathItemsTable = []struct { var pathItemsTable = []struct {
name string name string
ents []DetailsEntry ents []DetailsEntry
expectRefs []string expectRepoRefs []string
expectLocationRefs []string
}{ }{
{ {
name: "nil entries", name: "nil entries",
ents: nil, ents: nil,
expectRefs: []string{}, expectRepoRefs: []string{},
expectLocationRefs: []string{},
}, },
{ {
name: "single entry", name: "single entry",
ents: []DetailsEntry{ ents: []DetailsEntry{
{RepoRef: "abcde"}, {
RepoRef: "abcde",
LocationRef: "locationref",
},
}, },
expectRefs: []string{"abcde"}, expectRepoRefs: []string{"abcde"},
expectLocationRefs: []string{"locationref"},
}, },
{ {
name: "multiple entries", name: "multiple entries",
ents: []DetailsEntry{ ents: []DetailsEntry{
{RepoRef: "abcde"}, {
{RepoRef: "12345"}, RepoRef: "abcde",
LocationRef: "locationref",
},
{
RepoRef: "12345",
LocationRef: "locationref2",
},
}, },
expectRefs: []string{"abcde", "12345"}, expectRepoRefs: []string{"abcde", "12345"},
expectLocationRefs: []string{"locationref", "locationref2"},
}, },
{ {
name: "multiple entries with folder", name: "multiple entries with folder",
ents: []DetailsEntry{ ents: []DetailsEntry{
{RepoRef: "abcde"},
{RepoRef: "12345"},
{ {
RepoRef: "deadbeef", RepoRef: "abcde",
LocationRef: "locationref",
},
{
RepoRef: "12345",
LocationRef: "locationref2",
},
{
RepoRef: "deadbeef",
LocationRef: "locationref3",
ItemInfo: ItemInfo{ ItemInfo: ItemInfo{
Folder: &FolderInfo{ Folder: &FolderInfo{
DisplayName: "test folder", DisplayName: "test folder",
@ -195,7 +221,8 @@ var pathItemsTable = []struct {
}, },
}, },
}, },
expectRefs: []string{"abcde", "12345"}, expectRepoRefs: []string{"abcde", "12345"},
expectLocationRefs: []string{"locationref", "locationref2"},
}, },
} }
@ -207,7 +234,7 @@ func (suite *DetailsUnitSuite) TestDetailsModel_Path() {
Entries: test.ents, Entries: test.ents,
}, },
} }
assert.Equal(t, test.expectRefs, d.Paths()) assert.Equal(t, test.expectRepoRefs, d.Paths())
}) })
} }
} }
@ -222,10 +249,11 @@ func (suite *DetailsUnitSuite) TestDetailsModel_Items() {
} }
ents := d.Items() ents := d.Items()
assert.Len(t, ents, len(test.expectRefs)) assert.Len(t, ents, len(test.expectRepoRefs))
for _, e := range ents { for _, e := range ents {
assert.Contains(t, test.expectRefs, e.RepoRef) assert.Contains(t, test.expectRepoRefs, e.RepoRef)
assert.Contains(t, test.expectLocationRefs, e.LocationRef)
} }
}) })
} }
@ -253,9 +281,10 @@ func (suite *DetailsUnitSuite) TestDetails_AddFolders() {
name: "MultipleFolders", name: "MultipleFolders",
folders: []folderEntry{ folders: []folderEntry{
{ {
RepoRef: "rr1", RepoRef: "rr1",
ShortRef: "sr1", ShortRef: "sr1",
ParentRef: "pr1", ParentRef: "pr1",
LocationRef: "lr1",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{ Folder: &FolderInfo{
Modified: folderTimeOlderThanItem, Modified: folderTimeOlderThanItem,
@ -263,9 +292,10 @@ func (suite *DetailsUnitSuite) TestDetails_AddFolders() {
}, },
}, },
{ {
RepoRef: "rr2", RepoRef: "rr2",
ShortRef: "sr2", ShortRef: "sr2",
ParentRef: "pr2", ParentRef: "pr2",
LocationRef: "lr2",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{ Folder: &FolderInfo{
Modified: folderTimeNewerThanItem, Modified: folderTimeNewerThanItem,
@ -283,9 +313,10 @@ func (suite *DetailsUnitSuite) TestDetails_AddFolders() {
name: "MultipleFoldersWithRepeats", name: "MultipleFoldersWithRepeats",
folders: []folderEntry{ folders: []folderEntry{
{ {
RepoRef: "rr1", RepoRef: "rr1",
ShortRef: "sr1", ShortRef: "sr1",
ParentRef: "pr1", ParentRef: "pr1",
LocationRef: "lr1",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{ Folder: &FolderInfo{
Modified: folderTimeOlderThanItem, Modified: folderTimeOlderThanItem,
@ -293,9 +324,10 @@ func (suite *DetailsUnitSuite) TestDetails_AddFolders() {
}, },
}, },
{ {
RepoRef: "rr2", RepoRef: "rr2",
ShortRef: "sr2", ShortRef: "sr2",
ParentRef: "pr2", ParentRef: "pr2",
LocationRef: "lr2",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{ Folder: &FolderInfo{
Modified: folderTimeOlderThanItem, Modified: folderTimeOlderThanItem,
@ -303,9 +335,10 @@ func (suite *DetailsUnitSuite) TestDetails_AddFolders() {
}, },
}, },
{ {
RepoRef: "rr1", RepoRef: "rr1",
ShortRef: "sr1", ShortRef: "sr1",
ParentRef: "pr1", ParentRef: "pr1",
LocationRef: "lr1",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{ Folder: &FolderInfo{
Modified: folderTimeOlderThanItem, Modified: folderTimeOlderThanItem,
@ -313,9 +346,10 @@ func (suite *DetailsUnitSuite) TestDetails_AddFolders() {
}, },
}, },
{ {
RepoRef: "rr3", RepoRef: "rr3",
ShortRef: "sr3", ShortRef: "sr3",
ParentRef: "pr3", ParentRef: "pr3",
LocationRef: "lr3",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{ Folder: &FolderInfo{
Modified: folderTimeNewerThanItem, Modified: folderTimeNewerThanItem,
@ -363,18 +397,20 @@ func (suite *DetailsUnitSuite) TestDetails_AddFoldersUpdate() {
name: "ItemNotUpdated_NoChange", name: "ItemNotUpdated_NoChange",
folders: []folderEntry{ folders: []folderEntry{
{ {
RepoRef: "rr1", RepoRef: "rr1",
ShortRef: "sr1", ShortRef: "sr1",
ParentRef: "pr1", ParentRef: "pr1",
LocationRef: "lr1",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{}, Folder: &FolderInfo{},
}, },
Updated: true, Updated: true,
}, },
{ {
RepoRef: "rr2", RepoRef: "rr2",
ShortRef: "sr2", ShortRef: "sr2",
ParentRef: "pr2", ParentRef: "pr2",
LocationRef: "lr2",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{}, Folder: &FolderInfo{},
}, },
@ -390,17 +426,19 @@ func (suite *DetailsUnitSuite) TestDetails_AddFoldersUpdate() {
name: "ItemUpdated", name: "ItemUpdated",
folders: []folderEntry{ folders: []folderEntry{
{ {
RepoRef: "rr1", RepoRef: "rr1",
ShortRef: "sr1", ShortRef: "sr1",
ParentRef: "pr1", ParentRef: "pr1",
LocationRef: "lr1",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{}, Folder: &FolderInfo{},
}, },
}, },
{ {
RepoRef: "rr2", RepoRef: "rr2",
ShortRef: "sr2", ShortRef: "sr2",
ParentRef: "pr2", ParentRef: "pr2",
LocationRef: "lr2",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{}, Folder: &FolderInfo{},
}, },
@ -482,9 +520,10 @@ func (suite *DetailsUnitSuite) TestDetails_AddFoldersDifferentServices() {
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
folder := folderEntry{ folder := folderEntry{
RepoRef: "rr1", RepoRef: "rr1",
ShortRef: "sr1", ShortRef: "sr1",
ParentRef: "pr1", ParentRef: "pr1",
LocationRef: "lr1",
Info: ItemInfo{ Info: ItemInfo{
Folder: &FolderInfo{}, Folder: &FolderInfo{},
}, },