From 20ec708ea357c26afe0076e31f05eacb8597a688 Mon Sep 17 00:00:00 2001 From: Danny Date: Tue, 27 Dec 2022 13:26:10 -0500 Subject: [PATCH] GC: Restore: SharePoint: List restore API (#1961) ## Description Restore function for `SharePoint.List` logic within PR. Mock data changed as `SetName` is not allowed on item creation. Format for list restore is verified as below: ```java TestSharePointCollectionSuite/TestRestoreList collection_test.go:126: List created: Corso_Restore_26-Dec-2022_21-53-01.459183_MockListing ``` The expectation is that the correct folder name is passed to the function from the CLI. The restore pipeline is not connected for `SharePoint.List`. ## Does this PR need a docs update or release note? - [x] :no_entry: No ## Type of change - [x] :sunflower: Feature ## Issue(s) * Related to #1935 # ## Test Plan - [x] :zap: Unit test --- .../connector/data_collections_test.go | 15 +++++ .../connector/sharepoint/collection_test.go | 39 ++++++++++++ src/internal/connector/sharepoint/restore.go | 60 +++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/src/internal/connector/data_collections_test.go b/src/internal/connector/data_collections_test.go index e7ca6c791..be479bb8b 100644 --- a/src/internal/connector/data_collections_test.go +++ b/src/internal/connector/data_collections_test.go @@ -2,6 +2,7 @@ package connector import ( "bytes" + "io" "testing" "github.com/stretchr/testify/assert" @@ -334,6 +335,20 @@ func (suite *ConnectorCreateSharePointCollectionIntegrationSuite) TestCreateShar cols, err := gc.DataCollections(ctx, test.sel(), nil, control.Options{}) require.NoError(t, err) test.comparator(t, 0, len(cols)) + + if test.name == "SharePoint.Lists" { + for _, collection := range cols { + t.Logf("Path: %s\n", collection.FullPath().String()) + for item := range collection.Items() { + t.Log("File: " + item.UUID()) + + bytes, err := io.ReadAll(item.ToReader()) + require.NoError(t, err) + t.Log(string(bytes)) + + } + } + } }) } } diff --git a/src/internal/connector/sharepoint/collection_test.go b/src/internal/connector/sharepoint/collection_test.go index 82fd5e713..e1ba0b1fe 100644 --- a/src/internal/connector/sharepoint/collection_test.go +++ b/src/internal/connector/sharepoint/collection_test.go @@ -87,6 +87,45 @@ func (suite *SharePointCollectionSuite) TestSharePointListCollection() { assert.Equal(t, testName, shareInfo.Info().SharePoint.ItemName) } +func (suite *SharePointCollectionSuite) TestRestoreList() { + ctx, flush := tester.NewContext() + defer flush() + + t := suite.T() + a := tester.NewM365Account(t) + account, err := a.M365Config() + require.NoError(t, err) + + service, err := createTestService(account) + require.NoError(t, err) + siteID := tester.M365SiteID(t) + + ow := kw.NewJsonSerializationWriter() + listing := mockconnector.GetMockList("Mock List") + testName := "MockListing" + listing.SetDisplayName(&testName) + + err = ow.WriteObjectValue("", listing) + require.NoError(t, err) + + byteArray, err := ow.GetSerializedContent() + require.NoError(t, err) + + require.NoError(t, err) + + listData := &Item{ + id: testName, + data: io.NopCloser(bytes.NewReader(byteArray)), + info: sharePointListInfo(listing, int64(len(byteArray))), + } + + destName := "Corso_Restore_" + common.FormatNow(common.SimpleTimeTesting) + + deets, err := restoreListItem(ctx, service, listData, siteID, destName) + assert.NoError(t, err) + t.Logf("List created: %s\n", deets.SharePoint.ItemName) +} + // TestRestoreLocation temporary test for greater restore operation // TODO delete after full functionality tested in GraphConnector func (suite *SharePointCollectionSuite) TestRestoreLocation() { diff --git a/src/internal/connector/sharepoint/restore.go b/src/internal/connector/sharepoint/restore.go index 97993df94..5e98829b8 100644 --- a/src/internal/connector/sharepoint/restore.go +++ b/src/internal/connector/sharepoint/restore.go @@ -2,6 +2,8 @@ package sharepoint import ( "context" + "fmt" + "io" "github.com/pkg/errors" @@ -9,6 +11,7 @@ import ( "github.com/alcionai/corso/src/internal/connector/onedrive" "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/data" + D "github.com/alcionai/corso/src/internal/diagnostics" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/path" @@ -85,3 +88,60 @@ func createRestoreFolders(ctx context.Context, service graph.Servicer, siteID st return onedrive.CreateRestoreFolders(ctx, service, *mainDrive.GetId(), restoreFolders) } + +// restoreListItem utility function restores a List to the siteID. +// The name is changed to to Corso_Restore_{timeStame}_name +// API Reference: https://learn.microsoft.com/en-us/graph/api/list-create?view=graph-rest-1.0&tabs=http +// Restored List can be verified within the Site contents +func restoreListItem( + ctx context.Context, + service graph.Servicer, + itemData data.Stream, + siteID, destName string, +) (details.ItemInfo, error) { + ctx, end := D.Span(ctx, "gc:sharepoint:restoreList", D.Label("item_uuid", itemData.UUID())) + defer end() + + var ( + dii = details.ItemInfo{} + itemName = itemData.UUID() + displayName = itemName + ) + + byteArray, err := io.ReadAll(itemData.ToReader()) + if err != nil { + return dii, errors.Wrap(err, "sharepoint restoreItem failed to retrieve bytes from data.Stream") + } + // Create Item + newItem, err := support.CreateListFromBytes(byteArray) + if err != nil { + return dii, errors.Wrapf(err, "failed to construct list item %s", itemName) + } + + // If field "name" is set, this will trigger the following error: + // invalidRequest Cannot define a 'name' for a list as it is assigned by the server. Instead, provide 'displayName' + if newItem.GetName() != nil { + adtlData := newItem.GetAdditionalData() + adtlData["list_name"] = *newItem.GetName() + newItem.SetName(nil) + } + + if newItem.GetDisplayName() != nil { + displayName = *newItem.GetDisplayName() + } + + newName := fmt.Sprintf("%s_%s", destName, displayName) + newItem.SetDisplayName(&newName) + + // Restore to M365 store + restoredList, err := service.Client().SitesById(siteID).Lists().Post(ctx, newItem, nil) + if err != nil { + return dii, errors.Wrap(err, support.ConnectorStackErrorTrace(err)) + } + + written := int64(len(byteArray)) + + dii.SharePoint = sharePointListInfo(restoredList, written) + + return dii, nil +}