package details import ( "bytes" "fmt" "io" "strings" "testing" "time" "github.com/alcionai/clues" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/alcionai/corso/src/internal/m365/collection/drive/metadata" odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts" "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/pkg/dttm" "github.com/alcionai/corso/src/pkg/path" ) // ------------------------------------------------------------ // unit tests // ------------------------------------------------------------ type DetailsUnitSuite struct { tester.Suite } func TestDetailsUnitSuite(t *testing.T) { suite.Run(t, &DetailsUnitSuite{Suite: tester.NewUnitSuite(t)}) } func (suite *DetailsUnitSuite) TestDetailsEntry_HeadersValues() { initial := time.Now() nowStr := dttm.FormatTo(initial, dttm.TabularOutput) now, err := dttm.ParseTime(nowStr) require.NoError(suite.T(), err, clues.ToCore(err)) table := []struct { name string entry Entry expectHs []string expectVs []string }{ { name: "no info", entry: Entry{ RepoRef: "reporef", ShortRef: "deadbeef", LocationRef: "locationref", ItemRef: "itemref", }, expectHs: []string{"ID"}, expectVs: []string{"deadbeef"}, }, { name: "exchange event info", entry: Entry{ RepoRef: "reporef", ShortRef: "deadbeef", LocationRef: "locationref", ItemRef: "itemref", ItemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeEvent, EventStart: now, EventEnd: now, Organizer: "organizer", EventRecurs: true, Subject: "subject", }, }, }, expectHs: []string{"ID", "Organizer", "Subject", "Starts", "Ends", "Recurring"}, expectVs: []string{"deadbeef", "organizer", "subject", nowStr, nowStr, "true"}, }, { name: "exchange contact info", entry: Entry{ RepoRef: "reporef", ShortRef: "deadbeef", LocationRef: "locationref", ItemRef: "itemref", ItemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeContact, ContactName: "contactName", }, }, }, expectHs: []string{"ID", "Contact Name"}, expectVs: []string{"deadbeef", "contactName"}, }, { name: "exchange mail info", entry: Entry{ RepoRef: "reporef", ShortRef: "deadbeef", LocationRef: "locationref", ItemRef: "itemref", ItemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeMail, Sender: "sender", ParentPath: "Parent", Recipient: []string{"receiver"}, Subject: "subject", Received: now, }, }, }, expectHs: []string{"ID", "Sender", "Folder", "Subject", "Received"}, expectVs: []string{"deadbeef", "sender", "Parent", "subject", nowStr}, }, { name: "sharepoint library info", entry: Entry{ RepoRef: "reporef", ShortRef: "deadbeef", LocationRef: "locationref", ItemRef: "itemref", ItemInfo: ItemInfo{ SharePoint: &SharePointInfo{ ItemName: "itemName", ItemType: SharePointLibrary, ParentPath: "parentPath", Size: 1000, WebURL: "https://not.a.real/url", DriveName: "aLibrary", Owner: "user@email.com", Created: now, Modified: now, }, }, }, expectHs: []string{"ID", "ItemName", "Library", "ParentPath", "Size", "Owner", "Created", "Modified"}, expectVs: []string{ "deadbeef", "itemName", "aLibrary", "parentPath", "1.0 kB", "user@email.com", nowStr, nowStr, }, }, { name: "sharepoint list info for genericList template", entry: Entry{ RepoRef: "reporef", ShortRef: "deadbeef", LocationRef: "locationref", ItemRef: "itemref", ItemInfo: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: SharePointList, Created: now, Modified: now, WebURL: "https://example.com/Lists/list1", List: &ListInfo{ Name: "list1", ItemCount: 50, Template: "genericList", }, }, }, }, expectHs: []string{"ID", "List", "Items", "Created", "Modified"}, expectVs: []string{ "deadbeef", "list1", "50", nowStr, nowStr, }, }, { name: "sharepoint list info for documentLibrary template", entry: Entry{ RepoRef: "reporef", ShortRef: "deadbeef", LocationRef: "locationref", ItemRef: "itemref", ItemInfo: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: SharePointList, Created: now, Modified: now, WebURL: "https://example.com/Lists/Shared%20Documents", List: &ListInfo{ Name: "Shared%20Documents", ItemCount: 50, Template: "documentLibrary", }, }, }, }, expectHs: []string{"ID", "List", "Items", "Created", "Modified"}, expectVs: []string{ "deadbeef", "Shared%20Documents", "50", nowStr, nowStr, }, }, { name: "oneDrive info", entry: Entry{ RepoRef: "reporef", ShortRef: "deadbeef", LocationRef: "locationref", ItemRef: "itemref", ItemInfo: ItemInfo{ OneDrive: &OneDriveInfo{ ItemName: "itemName", ParentPath: "parentPath", Size: 1000, Owner: "user@email.com", Created: now, Modified: now, }, }, }, expectHs: []string{"ID", "ItemName", "ParentPath", "Size", "Owner", "Created", "Modified"}, expectVs: []string{"deadbeef", "itemName", "parentPath", "1.0 kB", "user@email.com", nowStr, nowStr}, }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() hs := test.entry.Headers(false) assert.Equal(t, test.expectHs, hs) vs := test.entry.Values(false) assert.Equal(t, test.expectVs, vs) hs = test.entry.Headers(true) assert.Equal(t, test.expectHs[1:], hs) vs = test.entry.Values(true) assert.Equal(t, test.expectVs[1:], vs) }) } } func exchangeEntry(t *testing.T, id string, size int, it ItemType) Entry { rr := makeItemPath( t, path.ExchangeService, path.EmailCategory, "tenant-id", "user-id", []string{"Inbox", "folder1", id}) return Entry{ RepoRef: rr.String(), ShortRef: rr.ShortRef(), ParentRef: rr.ToBuilder().Dir().ShortRef(), LocationRef: rr.Folder(true), ItemRef: rr.Item(), ItemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: it, Modified: time.Now(), Size: int64(size), }, }, } } func oneDriveishEntry(t *testing.T, id string, size int, it ItemType, service path.ServiceType) Entry { var ( category path.CategoryType info ItemInfo ) switch it { case OneDriveItem: category = path.FilesCategory info = ItemInfo{ OneDrive: &OneDriveInfo{ ItemName: "bar", DriveID: "drive-id", DriveName: "drive-name", Modified: time.Now(), ItemType: it, Size: int64(size), }, } case SharePointLibrary: category = path.LibrariesCategory switch service { case path.SharePointService: info = ItemInfo{ SharePoint: &SharePointInfo{ ItemName: "bar", DriveID: "drive-id", DriveName: "drive-name", Modified: time.Now(), ItemType: it, Size: int64(size), }, } case path.GroupsService: info = ItemInfo{ Groups: &GroupsInfo{ ItemName: "bar", DriveID: "drive-id", DriveName: "drive-name", Modified: time.Now(), ItemType: it, Size: int64(size), }, } } } rr := makeItemPath( t, service, category, "tenant-id", "user-id", []string{ odConsts.DrivesPathDir, "drive-id", odConsts.RootPathDir, "Inbox", "folder1", id, }) loc := path.Builder{}.Append(rr.Folders()...).PopFront().PopFront() return Entry{ RepoRef: rr.String(), ShortRef: rr.ShortRef(), ParentRef: rr.ToBuilder().Dir().ShortRef(), LocationRef: loc.String(), ItemRef: rr.Item(), ItemInfo: info, } } func (suite *DetailsUnitSuite) TestDetailsAdd_NoLocationFolders() { itemID := "foo" t := suite.T() table := []struct { name string entry Entry }{ { name: "Exchange Email", entry: exchangeEntry(t, itemID, 42, ExchangeMail), }, { name: "OneDrive File", entry: oneDriveishEntry(t, itemID, 42, OneDriveItem, path.OneDriveService), }, { name: "SharePoint File", entry: oneDriveishEntry(t, itemID, 42, SharePointLibrary, path.SharePointService), }, { name: "Legacy SharePoint File", entry: func() Entry { res := oneDriveishEntry(t, itemID, 42, SharePointLibrary, path.SharePointService) res.SharePoint.ItemType = OneDriveItem return res }(), }, { name: "Group SharePoint File", entry: oneDriveishEntry(t, itemID, 42, SharePointLibrary, path.GroupsService), }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() rr, err := path.FromDataLayerPath(test.entry.RepoRef, true) require.NoError(t, err, clues.ToCore(err)) db := &Builder{} // Make a local copy so we can modify it. localItem := test.entry err = db.Add(rr, &path.Builder{}, localItem.ItemInfo) require.NoError(t, err, clues.ToCore(err)) // Clear LocationRef that's automatically populated since we passed an // empty builder above. localItem.LocationRef = "" expectedShortRef := localItem.ShortRef localItem.ShortRef = "" deets := db.Details() assert.Len(t, deets.Entries, 1) got := deets.Entries[0] gotShortRef := got.ShortRef got.ShortRef = "" assert.Equal(t, localItem, got, "DetailsEntry") assert.Equal(t, expectedShortRef, gotShortRef, "ShortRef") }) } } func (suite *DetailsUnitSuite) TestDetailsAdd_LocationFolders() { t := suite.T() exchangeMail1 := exchangeEntry(t, "foo1", 42, ExchangeMail) oneDrive1 := oneDriveishEntry(t, "foo1", 42, OneDriveItem, path.OneDriveService) sharePoint1 := oneDriveishEntry(t, "foo1", 42, SharePointLibrary, path.SharePointService) sharePointLegacy1 := oneDriveishEntry(t, "foo1", 42, SharePointLibrary, path.SharePointService) sharePointLegacy1.SharePoint.ItemType = OneDriveItem group1 := oneDriveishEntry(t, "foo1", 42, SharePointLibrary, path.GroupsService) // Sleep for a little so we get a larger difference in mod times between the // earlier and later entries. time.Sleep(100 * time.Millisecond) // Get fresh item IDs so we can check that folders populate with the latest // mod time. Also the details API is built with the idea that duplicate items // aren't added (it has no checking for that). exchangeMail2 := exchangeEntry(t, "foo2", 43, ExchangeMail) exchangeContact1 := exchangeEntry(t, "foo3", 44, ExchangeContact) exchangeFolders := []Entry{ { ItemInfo: ItemInfo{ Folder: &FolderInfo{ DisplayName: "Inbox", ItemType: FolderItem, DataType: ExchangeMail, }, }, }, { LocationRef: "Inbox", ItemInfo: ItemInfo{ Folder: &FolderInfo{ DisplayName: "folder1", ItemType: FolderItem, DataType: ExchangeMail, }, }, }, } exchangeContactFolders := []Entry{ { ItemInfo: ItemInfo{ Folder: &FolderInfo{ DisplayName: "Inbox", ItemType: FolderItem, DataType: ExchangeContact, }, }, }, { LocationRef: "Inbox", ItemInfo: ItemInfo{ Folder: &FolderInfo{ DisplayName: "folder1", ItemType: FolderItem, DataType: ExchangeContact, }, }, }, } oneDriveishFolders := []Entry{ { ItemInfo: ItemInfo{ Folder: &FolderInfo{ DisplayName: odConsts.RootPathDir, ItemType: FolderItem, DriveName: "drive-name", DriveID: "drive-id", }, }, }, { LocationRef: odConsts.RootPathDir, ItemInfo: ItemInfo{ Folder: &FolderInfo{ DisplayName: "Inbox", ItemType: FolderItem, DriveName: "drive-name", DriveID: "drive-id", }, }, }, { LocationRef: "root:/Inbox", ItemInfo: ItemInfo{ Folder: &FolderInfo{ DisplayName: "folder1", ItemType: FolderItem, DriveName: "drive-name", DriveID: "drive-id", }, }, }, } table := []struct { name string entries func() []Entry expectedDirs func() []Entry }{ { name: "One Exchange Email", entries: func() []Entry { e := exchangeMail1 ei := *exchangeMail1.Exchange e.Exchange = &ei return []Entry{e} }, expectedDirs: func() []Entry { res := []Entry{} for _, entry := range exchangeFolders { e := entry ei := *entry.Folder e.Folder = &ei e.Folder.Size = exchangeMail1.Exchange.Size e.Folder.Modified = exchangeMail1.Exchange.Modified res = append(res, e) } return res }, }, { name: "Two Exchange Emails", entries: func() []Entry { res := []Entry{} for _, entry := range []Entry{exchangeMail1, exchangeMail2} { e := entry ei := *entry.Exchange e.Exchange = &ei res = append(res, e) } return res }, expectedDirs: func() []Entry { res := []Entry{} for _, entry := range exchangeFolders { e := entry ei := *entry.Folder e.Folder = &ei e.Folder.Size = exchangeMail1.Exchange.Size + exchangeMail2.Exchange.Size e.Folder.Modified = exchangeMail2.Exchange.Modified res = append(res, e) } return res }, }, { name: "One Email And One Contact", entries: func() []Entry { res := []Entry{} for _, entry := range []Entry{exchangeMail1, exchangeContact1} { e := entry ei := *entry.Exchange e.Exchange = &ei res = append(res, e) } return res }, expectedDirs: func() []Entry { res := []Entry{} for _, entry := range exchangeFolders { e := entry ei := *entry.Folder e.Folder = &ei e.Folder.Size = exchangeMail1.Exchange.Size e.Folder.Modified = exchangeMail1.Exchange.Modified res = append(res, e) } for _, entry := range exchangeContactFolders { e := entry ei := *entry.Folder e.Folder = &ei e.Folder.Size = exchangeContact1.Exchange.Size e.Folder.Modified = exchangeContact1.Exchange.Modified res = append(res, e) } return res }, }, { name: "One OneDrive Item", entries: func() []Entry { e := oneDrive1 ei := *oneDrive1.OneDrive e.OneDrive = &ei return []Entry{e} }, expectedDirs: func() []Entry { res := []Entry{} for _, entry := range oneDriveishFolders { e := entry ei := *entry.Folder e.Folder = &ei e.Folder.DataType = OneDriveItem e.Folder.Size = oneDrive1.OneDrive.Size e.Folder.Modified = oneDrive1.OneDrive.Modified res = append(res, e) } return res }, }, { name: "One SharePoint Item", entries: func() []Entry { e := sharePoint1 ei := *sharePoint1.SharePoint e.SharePoint = &ei return []Entry{e} }, expectedDirs: func() []Entry { res := []Entry{} for _, entry := range oneDriveishFolders { e := entry ei := *entry.Folder e.Folder = &ei e.Folder.DataType = SharePointLibrary e.Folder.Size = sharePoint1.SharePoint.Size e.Folder.Modified = sharePoint1.SharePoint.Modified res = append(res, e) } return res }, }, { name: "One SharePoint Legacy Item", entries: func() []Entry { e := sharePoint1 ei := *sharePoint1.SharePoint e.SharePoint = &ei return []Entry{e} }, expectedDirs: func() []Entry { res := []Entry{} for _, entry := range oneDriveishFolders { e := entry ei := *entry.Folder e.Folder = &ei e.Folder.DataType = SharePointLibrary e.Folder.Size = sharePoint1.SharePoint.Size e.Folder.Modified = sharePoint1.SharePoint.Modified res = append(res, e) } return res }, }, { name: "One Group SharePoint Item", entries: func() []Entry { e := group1 ei := *group1.Groups e.Groups = &ei return []Entry{e} }, expectedDirs: func() []Entry { res := []Entry{} for _, entry := range oneDriveishFolders { e := entry ei := *entry.Folder e.Folder = &ei e.Folder.DataType = SharePointLibrary e.Folder.Size = group1.Groups.Size e.Folder.Modified = group1.Groups.Modified res = append(res, e) } return res }, }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() db := &Builder{} for _, entry := range test.entries() { rr, err := path.FromDataLayerPath(entry.RepoRef, true) require.NoError(t, err, clues.ToCore(err)) loc, err := path.Builder{}.SplitUnescapeAppend(entry.LocationRef) require.NoError(t, err, clues.ToCore(err)) err = db.Add(rr, loc, entry.ItemInfo) require.NoError(t, err, clues.ToCore(err)) } deets := db.Details() gotDirs := []Entry{} for _, entry := range deets.Entries { // Other test checks items are populated properly. if entry.infoType() != FolderItem { continue } // Not Comparing these right now. entry.RepoRef = "" entry.ShortRef = "" entry.ParentRef = "" gotDirs = append(gotDirs, entry) } assert.ElementsMatch(t, test.expectedDirs(), gotDirs) }) } } var pathItemsTable = []struct { name string ents []Entry expectRepoRefs []string expectLocationRefs []string }{ { name: "nil entries", ents: nil, expectRepoRefs: []string{}, expectLocationRefs: []string{}, }, { name: "single entry", ents: []Entry{ { RepoRef: "abcde", LocationRef: "locationref", ItemRef: "itemref", }, }, expectRepoRefs: []string{"abcde"}, expectLocationRefs: []string{"locationref"}, }, { name: "multiple entries", ents: []Entry{ { RepoRef: "abcde", LocationRef: "locationref", ItemRef: "itemref", }, { RepoRef: "12345", LocationRef: "locationref2", ItemRef: "itemref2", }, }, expectRepoRefs: []string{"abcde", "12345"}, expectLocationRefs: []string{"locationref", "locationref2"}, }, { name: "multiple entries with folder", ents: []Entry{ { RepoRef: "abcde", LocationRef: "locationref", ItemRef: "itemref", }, { RepoRef: "12345", LocationRef: "locationref2", ItemRef: "itemref2", }, { RepoRef: "deadbeef", LocationRef: "locationref3", ItemInfo: ItemInfo{ Folder: &FolderInfo{ DisplayName: "test folder", }, }, }, }, expectRepoRefs: []string{"abcde", "12345"}, expectLocationRefs: []string{"locationref", "locationref2"}, }, { name: "multiple entries with meta file", ents: []Entry{ { RepoRef: "abcde", LocationRef: "locationref", }, { RepoRef: "foo.meta", LocationRef: "locationref.dirmeta", ItemRef: "itemref.meta", ItemInfo: ItemInfo{ OneDrive: &OneDriveInfo{IsMeta: false}, }, }, { RepoRef: "is-meta-file", LocationRef: "locationref-meta-file", ItemRef: "itemref-meta-file", ItemInfo: ItemInfo{ OneDrive: &OneDriveInfo{IsMeta: true}, }, }, }, expectRepoRefs: []string{"abcde", "foo.meta"}, expectLocationRefs: []string{"locationref", "locationref.dirmeta"}, }, { name: "multiple entries with folder and meta file", ents: []Entry{ { RepoRef: "abcde", LocationRef: "locationref", ItemRef: "itemref", }, { RepoRef: "12345", LocationRef: "locationref2", ItemRef: "itemref2", }, { RepoRef: "foo.meta", LocationRef: "locationref.dirmeta", ItemRef: "itemref.dirmeta", ItemInfo: ItemInfo{ OneDrive: &OneDriveInfo{IsMeta: false}, }, }, { RepoRef: "is-meta-file", LocationRef: "locationref-meta-file", ItemRef: "itemref-meta-file", ItemInfo: ItemInfo{ OneDrive: &OneDriveInfo{IsMeta: true}, }, }, { RepoRef: "deadbeef", LocationRef: "locationref3", ItemRef: "itemref3", ItemInfo: ItemInfo{ Folder: &FolderInfo{ DisplayName: "test folder", }, }, }, }, expectRepoRefs: []string{"abcde", "12345", "foo.meta"}, expectLocationRefs: []string{"locationref", "locationref2", "locationref.dirmeta"}, }, } func (suite *DetailsUnitSuite) TestDetailsModel_Path() { for _, test := range pathItemsTable { suite.Run(test.name, func() { t := suite.T() d := Details{ DetailsModel: DetailsModel{ Entries: test.ents, }, } assert.ElementsMatch(t, test.expectRepoRefs, d.Paths()) }) } } func (suite *DetailsUnitSuite) TestDetailsModel_Items() { for _, test := range pathItemsTable { suite.Run(test.name, func() { t := suite.T() d := Details{ DetailsModel: DetailsModel{ Entries: test.ents, }, } ents := d.Items() assert.Len(t, ents, len(test.expectRepoRefs)) for _, e := range ents { assert.Contains(t, test.expectRepoRefs, e.RepoRef) assert.Contains(t, test.expectLocationRefs, e.LocationRef) } }) } } func (suite *DetailsUnitSuite) TestDetailsModel_FilterMetaFiles() { t := suite.T() d := &DetailsModel{ Entries: []Entry{ { RepoRef: "a.data", ItemInfo: ItemInfo{ OneDrive: &OneDriveInfo{IsMeta: false}, }, }, { RepoRef: "b.meta", ItemInfo: ItemInfo{ OneDrive: &OneDriveInfo{IsMeta: false}, }, }, { RepoRef: "c.meta", ItemInfo: ItemInfo{ OneDrive: &OneDriveInfo{IsMeta: true}, }, }, }, } d2 := d.FilterMetaFiles() assert.Len(t, d2.Entries, 2) assert.Len(t, d.Entries, 3) } func (suite *DetailsUnitSuite) TestBuilder_Add_shortRefsUniqueFromFolder() { t := suite.T() b := Builder{} name := "itemName" info := ItemInfo{ OneDrive: &OneDriveInfo{ ItemType: OneDriveItem, ItemName: name, DriveName: "drive-name", DriveID: "drive-id", }, } itemPath := makeItemPath( t, path.OneDriveService, path.FilesCategory, "a-tenant", "a-user", []string{ "drive-id", odConsts.RootPathDir, "folder", name + "-id", }) otherItemPath := makeItemPath( t, path.OneDriveService, path.FilesCategory, "a-tenant", "a-user", []string{ "drive-id", odConsts.RootPathDir, "folder", name + "-id", name, }) err := b.Add( itemPath, // Don't need to generate folders for this entry we just want the ShortRef. &path.Builder{}, info) require.NoError(t, err, clues.ToCore(err)) items := b.Details().Items() require.Len(t, items, 1) // If the ShortRefs match then it means it's possible for the user to // construct folder names such that they'll generate a ShortRef collision. assert.NotEqual(t, otherItemPath.ShortRef(), items[0].ShortRef, "same ShortRef as subfolder item") } func (suite *DetailsUnitSuite) TestBuilder_Add_cleansFileIDSuffixes() { var ( t = suite.T() b = Builder{} svc = path.OneDriveService cat = path.FilesCategory info = ItemInfo{ OneDrive: &OneDriveInfo{ ItemType: OneDriveItem, ItemName: "in", DriveName: "dn", DriveID: "d", }, } dataSfx = makeItemPath(t, svc, cat, "t", "u", []string{"d", "r:", "f", "i1" + metadata.DataFileSuffix}) dirMetaSfx = makeItemPath(t, svc, cat, "t", "u", []string{"d", "r:", "f", "i1" + metadata.DirMetaFileSuffix}) metaSfx = makeItemPath(t, svc, cat, "t", "u", []string{"d", "r:", "f", "i1" + metadata.MetaFileSuffix}) ) // Don't need to generate folders for this entry, we just want the itemRef loc := &path.Builder{} err := b.Add(dataSfx, loc, info) require.NoError(t, err, clues.ToCore(err)) err = b.Add(dirMetaSfx, loc, info) require.NoError(t, err, clues.ToCore(err)) err = b.Add(metaSfx, loc, info) require.NoError(t, err, clues.ToCore(err)) for _, ent := range b.Details().Items() { assert.False(t, strings.HasSuffix(ent.ItemRef, metadata.DirMetaFileSuffix)) assert.False(t, strings.HasSuffix(ent.ItemRef, metadata.MetaFileSuffix)) assert.False(t, strings.HasSuffix(ent.ItemRef, metadata.DataFileSuffix)) } } func (suite *DetailsUnitSuite) TestBuilder_DetailsNoDuplicate() { var ( t = suite.T() b = Builder{} svc = path.OneDriveService cat = path.FilesCategory info = ItemInfo{ OneDrive: &OneDriveInfo{ ItemType: OneDriveItem, ItemName: "in", DriveName: "dn", DriveID: "d", }, } dataSfx = makeItemPath(t, svc, cat, "t", "u", []string{"d", "r:", "f", "i1" + metadata.DataFileSuffix}) dataSfx2 = makeItemPath(t, svc, cat, "t", "u", []string{"d", "r:", "f", "i2" + metadata.DataFileSuffix}) dirMetaSfx = makeItemPath(t, svc, cat, "t", "u", []string{"d", "r:", "f", "i1" + metadata.DirMetaFileSuffix}) metaSfx = makeItemPath(t, svc, cat, "t", "u", []string{"d", "r:", "f", "i1" + metadata.MetaFileSuffix}) ) // Don't need to generate folders for this entry, we just want the itemRef loc := &path.Builder{} err := b.Add(dataSfx, loc, info) require.NoError(t, err, clues.ToCore(err)) err = b.Add(dataSfx2, loc, info) require.NoError(t, err, clues.ToCore(err)) err = b.Add(dirMetaSfx, loc, info) require.NoError(t, err, clues.ToCore(err)) err = b.Add(metaSfx, loc, info) require.NoError(t, err, clues.ToCore(err)) b.knownFolders = map[string]Entry{ "dummy": { RepoRef: "xyz", ShortRef: "abcd", ParentRef: "1234", LocationRef: "ab", ItemRef: "cd", ItemInfo: info, }, "dummy2": { RepoRef: "xyz2", ShortRef: "abcd2", ParentRef: "12342", LocationRef: "ab2", ItemRef: "cd2", ItemInfo: info, }, "dummy3": { RepoRef: "xyz3", ShortRef: "abcd3", ParentRef: "12343", LocationRef: "ab3", ItemRef: "cd3", ItemInfo: info, }, } // mark the capacity prior to calling details. // if the entries slice gets modified and grows to a // 5th space, then the capacity would grow as well. capCheck := cap(b.d.Entries) assert.Len(t, b.Details().Entries, 7) // 4 ents + 3 known folders assert.Len(t, b.Details().Entries, 7) // possible reason for err: knownFolders got added twice assert.Len(t, b.d.Entries, 4) // len should not have grown assert.Equal(t, capCheck, cap(b.d.Entries)) // capacity should not have grown } func makeItemPath( t *testing.T, service path.ServiceType, category path.CategoryType, tenant, resourceOwner string, elems []string, ) path.Path { t.Helper() p, err := path.Build( tenant, resourceOwner, service, category, true, elems...) require.NoError(t, err, clues.ToCore(err)) return p } func (suite *DetailsUnitSuite) TestUpdateItem() { const ( folder1 = "f1" folder2 = "f2" ) newExchangePB := path.Builder{}.Append(folder2) newOneDrivePB := path.Builder{}.Append(odConsts.RootPathDir, folder2) table := []struct { name string input ItemInfo locPath *path.Builder expectedItem ItemInfo }{ { name: "ExchangeEvent", input: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeEvent, ParentPath: folder1, }, }, locPath: newExchangePB, expectedItem: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeEvent, ParentPath: folder2, }, }, }, { name: "ExchangeContact", input: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeContact, ParentPath: folder1, }, }, locPath: newExchangePB, expectedItem: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeContact, ParentPath: folder2, }, }, }, { name: "ExchangeMail", input: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeMail, ParentPath: folder1, }, }, locPath: newExchangePB, expectedItem: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeMail, ParentPath: folder2, }, }, }, { name: "OneDrive", input: ItemInfo{ OneDrive: &OneDriveInfo{ ItemType: OneDriveItem, ParentPath: folder1, }, }, locPath: newOneDrivePB, expectedItem: ItemInfo{ OneDrive: &OneDriveInfo{ ItemType: OneDriveItem, ParentPath: folder2, }, }, }, { name: "SharePoint", input: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: SharePointLibrary, ParentPath: folder1, }, }, locPath: newOneDrivePB, expectedItem: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: SharePointLibrary, ParentPath: folder2, }, }, }, { name: "SharePoint Old Format", input: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: OneDriveItem, ParentPath: folder1, }, }, locPath: newOneDrivePB, expectedItem: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: SharePointLibrary, ParentPath: folder2, }, }, }, { name: "Empty Item Doesn't Fail", input: ItemInfo{}, locPath: newOneDrivePB, expectedItem: ItemInfo{}, }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() item := test.input UpdateItem(&item, test.locPath) assert.Equal(t, test.expectedItem, item) }) } } func (suite *DetailsUnitSuite) TestDetails_Marshal() { for _, test := range pathItemsTable { suite.Run(test.name, func() { d := &Details{DetailsModel: DetailsModel{ Entries: test.ents, }} bs, err := d.Marshal() require.NoError(suite.T(), err, clues.ToCore(err)) assert.NotEmpty(suite.T(), bs) }) } } func (suite *DetailsUnitSuite) TestUnarshalTo() { for _, test := range pathItemsTable { suite.Run(test.name, func() { orig := &Details{DetailsModel: DetailsModel{ Entries: test.ents, }} bs, err := orig.Marshal() require.NoError(suite.T(), err, clues.ToCore(err)) assert.NotEmpty(suite.T(), bs) var result Details umt := UnmarshalTo(&result) err = umt(io.NopCloser(bytes.NewReader(bs))) t := suite.T() require.NoError(t, err, clues.ToCore(err)) require.NotNil(t, result) assert.ElementsMatch(t, orig.Entries, result.Entries) }) } } func (suite *DetailsUnitSuite) TestLocationIDer_FromEntry() { const ( rrString = "tenant-id/%s/user-id/%s/drives/drive-id/root:/some/folder/stuff/item" listsRrString = "tenant-id/%s/site-id/%s/lists/list-id/list-id" driveID = "driveID" listID = "listID" expectedUniqueLocFmt = "%s/" + driveID + "/root:/some/folder/stuff" expectedExchangeUniqueLocFmt = "%s/root:/some/folder/stuff" expectedDetailsLoc = "root:/some/folder/stuff" expectedListUniqueLocFmt = "%s/" + listID expectedListDetailsLoc = listID ) table := []struct { name string service string category string itemInfo ItemInfo hasLocRef bool backupVersion int expectedErr require.ErrorAssertionFunc expectedUniqueLoc string }{ { name: "OneDrive With Drive ID Old Version", service: path.OneDriveService.String(), category: path.FilesCategory.String(), itemInfo: ItemInfo{ OneDrive: &OneDriveInfo{ ItemType: OneDriveItem, DriveID: driveID, }, }, backupVersion: version.OneDrive7LocationRef - 1, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedUniqueLocFmt, path.FilesCategory), }, { name: "OneDrive With Drive ID And LocationRef", service: path.OneDriveService.String(), category: path.FilesCategory.String(), itemInfo: ItemInfo{ OneDrive: &OneDriveInfo{ ItemType: OneDriveItem, DriveID: driveID, }, }, backupVersion: version.OneDrive7LocationRef, hasLocRef: true, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedUniqueLocFmt, path.FilesCategory), }, { name: "OneDrive With Drive ID New Version Errors", service: path.OneDriveService.String(), category: path.FilesCategory.String(), itemInfo: ItemInfo{ OneDrive: &OneDriveInfo{ ItemType: OneDriveItem, DriveID: driveID, }, }, backupVersion: version.OneDrive7LocationRef, expectedErr: require.Error, }, { name: "SharePoint With Drive ID Old Version", service: path.SharePointService.String(), category: path.LibrariesCategory.String(), itemInfo: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: SharePointLibrary, DriveID: driveID, }, }, backupVersion: version.OneDrive7LocationRef - 1, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedUniqueLocFmt, path.LibrariesCategory), }, { name: "SharePoint With Drive ID And LocationRef", service: path.SharePointService.String(), category: path.LibrariesCategory.String(), itemInfo: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: SharePointLibrary, DriveID: driveID, }, }, backupVersion: version.OneDrive7LocationRef, hasLocRef: true, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedUniqueLocFmt, path.LibrariesCategory), }, { name: "SharePoint With Drive ID New Version Errors", service: path.SharePointService.String(), category: path.LibrariesCategory.String(), itemInfo: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: SharePointLibrary, DriveID: driveID, }, }, backupVersion: version.OneDrive7LocationRef, expectedErr: require.Error, }, { name: "SharePoint List With LocationRef", service: path.SharePointService.String(), category: path.ListsCategory.String(), itemInfo: ItemInfo{ SharePoint: &SharePointInfo{ ItemType: SharePointList, List: &ListInfo{}, }, }, backupVersion: version.OneDrive7LocationRef, hasLocRef: true, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedListUniqueLocFmt, path.ListsCategory), }, { name: "Exchange Email With LocationRef Old Version", service: path.ExchangeService.String(), category: path.EmailCategory.String(), itemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeMail, }, }, backupVersion: version.OneDrive7LocationRef - 1, hasLocRef: true, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedExchangeUniqueLocFmt, path.EmailCategory), }, { name: "Exchange Email With LocationRef New Version", service: path.ExchangeService.String(), category: path.EmailCategory.String(), itemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeMail, }, }, backupVersion: version.OneDrive7LocationRef, hasLocRef: true, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedExchangeUniqueLocFmt, path.EmailCategory), }, { name: "Exchange Email Without LocationRef Old Version", service: path.ExchangeService.String(), category: path.EmailCategory.String(), itemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeMail, }, }, backupVersion: version.OneDrive7LocationRef - 1, hasLocRef: true, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedExchangeUniqueLocFmt, path.EmailCategory), }, { name: "Exchange Email Without LocationRef New Version", service: path.ExchangeService.String(), category: path.EmailCategory.String(), itemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeMail, }, }, backupVersion: version.OneDrive7LocationRef, hasLocRef: true, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedExchangeUniqueLocFmt, path.EmailCategory), }, { name: "Exchange Email Bad RepoRef Fails", service: path.OneDriveService.String(), category: path.EmailCategory.String(), itemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeMail, }, }, backupVersion: version.OneDrive7LocationRef, expectedErr: require.Error, }, { name: "Exchange Event Empty LocationRef New Version Fails", service: path.ExchangeService.String(), category: path.EventsCategory.String(), itemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeEvent, }, }, backupVersion: 2, expectedErr: require.Error, }, { name: "Exchange Event Empty LocationRef Old Version", service: path.ExchangeService.String(), category: path.EventsCategory.String(), itemInfo: ItemInfo{ Exchange: &ExchangeInfo{ ItemType: ExchangeEvent, }, }, backupVersion: version.OneDrive1DataAndMetaFiles, hasLocRef: true, expectedErr: require.NoError, expectedUniqueLoc: fmt.Sprintf(expectedExchangeUniqueLocFmt, path.EventsCategory), }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() rr := "" detailsLoc := "" if test.category == path.ListsCategory.String() { rr = fmt.Sprintf(listsRrString, test.service, test.category) detailsLoc = expectedListDetailsLoc } else { rr = fmt.Sprintf(rrString, test.service, test.category) detailsLoc = expectedDetailsLoc } entry := Entry{ RepoRef: rr, ItemInfo: test.itemInfo, } if test.hasLocRef { entry.LocationRef = detailsLoc } loc, err := entry.ToLocationIDer(test.backupVersion) test.expectedErr(t, err, clues.ToCore(err)) if err != nil { return } assert.Equal( t, test.expectedUniqueLoc, loc.ID().String(), "unique location") assert.Equal( t, detailsLoc, loc.InDetails().String(), "details location") }) } }