add sharepoint details command (#1618)
## Description Adds the `backup details sharepoint` command to the CLI. E2E support is already functional. Additional filters will be added as needs are identified. ## Type of change - [x] 🌻 Feature ## Issue(s) * #1614 ## Test Plan - [x] ⚡ Unit test
This commit is contained in:
parent
f1ea464ad6
commit
2778cd567e
@ -453,8 +453,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runDetailsExchangeCmd actually performs the lookup in backup details. Assumes
|
// runDetailsExchangeCmd actually performs the lookup in backup details.
|
||||||
// len(backupID) > 0.
|
|
||||||
func runDetailsExchangeCmd(
|
func runDetailsExchangeCmd(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r repository.BackupGetter,
|
r repository.BackupGetter,
|
||||||
|
|||||||
@ -90,7 +90,7 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
|
|||||||
c, fs = utils.AddCommand(parent, oneDriveListCmd())
|
c, fs = utils.AddCommand(parent, oneDriveListCmd())
|
||||||
|
|
||||||
fs.StringVar(&backupID,
|
fs.StringVar(&backupID,
|
||||||
"backup", "",
|
utils.BackupFN, "",
|
||||||
"ID of the backup to retrieve.")
|
"ID of the backup to retrieve.")
|
||||||
|
|
||||||
case detailsCommand:
|
case detailsCommand:
|
||||||
@ -344,8 +344,7 @@ func detailsOneDriveCmd(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runDetailsOneDriveCmd actually performs the lookup in backup details. Assumes
|
// runDetailsOneDriveCmd actually performs the lookup in backup details.
|
||||||
// len(backupID) > 0.
|
|
||||||
func runDetailsOneDriveCmd(
|
func runDetailsOneDriveCmd(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r repository.BackupGetter,
|
r repository.BackupGetter,
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@ -12,6 +14,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/kopia"
|
"github.com/alcionai/corso/src/internal/kopia"
|
||||||
"github.com/alcionai/corso/src/internal/model"
|
"github.com/alcionai/corso/src/internal/model"
|
||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/repository"
|
"github.com/alcionai/corso/src/pkg/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -23,7 +26,9 @@ import (
|
|||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
var (
|
var (
|
||||||
site []string
|
site []string
|
||||||
|
libraryPaths []string
|
||||||
|
libraryItems []string
|
||||||
|
|
||||||
sharepointData []string
|
sharepointData []string
|
||||||
)
|
)
|
||||||
@ -33,10 +38,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sharePointServiceCommand = "sharepoint"
|
sharePointServiceCommand = "sharepoint"
|
||||||
sharePointServiceCommandCreateUseSuffix = "--site <siteId> | '" + utils.Wildcard + "'"
|
sharePointServiceCommandCreateUseSuffix = "--site <siteId> | '" + utils.Wildcard + "'"
|
||||||
sharePointServiceCommandDeleteUseSuffix = "--backup <backupId>"
|
sharePointServiceCommandDeleteUseSuffix = "--backup <backupId>"
|
||||||
// sharePointServiceCommandDetailsUseSuffix = "--backup <backupId>"
|
sharePointServiceCommandDetailsUseSuffix = "--backup <backupId>"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -54,9 +59,9 @@ corso backup create sharepoint --site '*'`
|
|||||||
sharePointServiceCommandDeleteExamples = `# Delete SharePoint backup with ID 1234abcd-12ab-cd34-56de-1234abcd
|
sharePointServiceCommandDeleteExamples = `# Delete SharePoint backup with ID 1234abcd-12ab-cd34-56de-1234abcd
|
||||||
corso backup delete sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd`
|
corso backup delete sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd`
|
||||||
|
|
||||||
// sharePointServiceCommandDetailsExamples = `# Explore <site>'s files from backup 1234abcd-12ab-cd34-56de-1234abcd
|
sharePointServiceCommandDetailsExamples = `# Explore <site>'s files from backup 1234abcd-12ab-cd34-56de-1234abcd
|
||||||
//
|
|
||||||
// corso backup details sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd --site <site_id>`
|
corso backup details sharepoint --backup 1234abcd-12ab-cd34-56de-1234abcd --site <site_id>`
|
||||||
)
|
)
|
||||||
|
|
||||||
// called by backup.go to map parent subcommands to provider-specific handling.
|
// called by backup.go to map parent subcommands to provider-specific handling.
|
||||||
@ -80,18 +85,45 @@ func addSharePointCommands(parent *cobra.Command) *cobra.Command {
|
|||||||
fs.StringSliceVar(
|
fs.StringSliceVar(
|
||||||
&sharepointData,
|
&sharepointData,
|
||||||
utils.DataFN, nil,
|
utils.DataFN, nil,
|
||||||
"Select one or more types of data to backup: "+dataLibraries)
|
"Select one or more types of data to backup: "+dataLibraries+".")
|
||||||
options.AddOperationFlags(c)
|
options.AddOperationFlags(c)
|
||||||
|
|
||||||
case listCommand:
|
case listCommand:
|
||||||
c, fs = utils.AddCommand(parent, sharePointListCmd(), utils.HideCommand())
|
c, fs = utils.AddCommand(parent, sharePointListCmd(), utils.HideCommand())
|
||||||
|
|
||||||
fs.StringVar(&backupID,
|
fs.StringVar(&backupID,
|
||||||
"backup", "",
|
utils.BackupFN, "",
|
||||||
"ID of the backup to retrieve.")
|
"ID of the backup to retrieve.")
|
||||||
|
|
||||||
// case detailsCommand:
|
case detailsCommand:
|
||||||
// c, fs = utils.AddCommand(parent, sharePointDetailsCmd())
|
c, fs = utils.AddCommand(parent, sharePointDetailsCmd())
|
||||||
|
|
||||||
|
c.Use = c.Use + " " + sharePointServiceCommandDetailsUseSuffix
|
||||||
|
c.Example = sharePointServiceCommandDetailsExamples
|
||||||
|
|
||||||
|
fs.StringVar(&backupID,
|
||||||
|
utils.BackupFN, "",
|
||||||
|
"ID of the backup to retrieve.")
|
||||||
|
cobra.CheckErr(c.MarkFlagRequired(utils.BackupFN))
|
||||||
|
|
||||||
|
// sharepoint hierarchy flags
|
||||||
|
|
||||||
|
fs.StringSliceVar(
|
||||||
|
&libraryPaths,
|
||||||
|
utils.LibraryFN, nil,
|
||||||
|
"Select backup details by Library name.")
|
||||||
|
|
||||||
|
fs.StringSliceVar(
|
||||||
|
&libraryItems,
|
||||||
|
utils.LibraryItemFN, nil,
|
||||||
|
"Select backup details by library item name or ID.")
|
||||||
|
|
||||||
|
// info flags
|
||||||
|
|
||||||
|
// fs.StringVar(
|
||||||
|
// &fileCreatedAfter,
|
||||||
|
// utils.FileCreatedAfterFN, "",
|
||||||
|
// "Select backup details for items created after this datetime.")
|
||||||
|
|
||||||
case deleteCommand:
|
case deleteCommand:
|
||||||
c, fs = utils.AddCommand(parent, sharePointDeleteCmd(), utils.HideCommand())
|
c, fs = utils.AddCommand(parent, sharePointDeleteCmd(), utils.HideCommand())
|
||||||
@ -282,3 +314,93 @@ func deleteSharePointCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// backup details
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// `corso backup details onedrive [<flag>...]`
|
||||||
|
func sharePointDetailsCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: sharePointServiceCommand,
|
||||||
|
Short: "Shows the details of a M365 SharePoint service backup",
|
||||||
|
RunE: detailsSharePointCmd,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Example: sharePointServiceCommandDetailsExamples,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lists the history of backup operations
|
||||||
|
func detailsSharePointCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
|
||||||
|
if utils.HasNoFlagsAndShownHelp(cmd) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s, acct, err := config.GetStorageAndAccount(ctx, true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := repository.Connect(ctx, acct, s, options.Control())
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
|
opts := utils.SharePointOpts{
|
||||||
|
Sites: site,
|
||||||
|
LibraryPaths: libraryPaths,
|
||||||
|
LibraryItems: libraryItems,
|
||||||
|
|
||||||
|
Populated: utils.GetPopulatedFlags(cmd),
|
||||||
|
}
|
||||||
|
|
||||||
|
ds, err := runDetailsSharePointCmd(ctx, r, backupID, opts)
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ds.Entries) == 0 {
|
||||||
|
Info(ctx, selectors.ErrorNoMatchingItems)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.PrintEntries(ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runDetailsSharePointCmd actually performs the lookup in backup details.
|
||||||
|
func runDetailsSharePointCmd(
|
||||||
|
ctx context.Context,
|
||||||
|
r repository.BackupGetter,
|
||||||
|
backupID string,
|
||||||
|
opts utils.SharePointOpts,
|
||||||
|
) (*details.Details, error) {
|
||||||
|
if err := utils.ValidateSharePointRestoreFlags(backupID, opts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d, _, err := r.BackupDetails(ctx, backupID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, kopia.ErrNotFound) {
|
||||||
|
return nil, errors.Errorf("no backup exists with the id %s", backupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.Wrap(err, "Failed to get backup details in the repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
sel := selectors.NewSharePointRestore()
|
||||||
|
utils.IncludeSharePointRestoreDataSelectors(sel, opts)
|
||||||
|
utils.FilterSharePointRestoreInfoSelectors(sel, opts)
|
||||||
|
|
||||||
|
// if no selector flags were specified, get all data in the service.
|
||||||
|
if len(sel.Scopes()) == 0 {
|
||||||
|
sel.Include(sel.Sites(selectors.Any()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sel.Reduce(ctx, d), nil
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/cli/utils/testdata"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,10 +38,10 @@ func (suite *SharePointSuite) TestAddSharePointCommands() {
|
|||||||
"list sharepoint", listCommand, expectUse,
|
"list sharepoint", listCommand, expectUse,
|
||||||
sharePointListCmd().Short, listSharePointCmd,
|
sharePointListCmd().Short, listSharePointCmd,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// "details sharepoint", detailsCommand, expectUse + " " + sharePointServiceCommandDetailsUseSuffix,
|
"details sharepoint", detailsCommand, expectUse + " " + sharePointServiceCommandDetailsUseSuffix,
|
||||||
// sharePointDetailsCmd().Short, detailsSharePointCmd,
|
sharePointDetailsCmd().Short, detailsSharePointCmd,
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
"delete sharepoint", deleteCommand, expectUse + " " + sharePointServiceCommandDeleteUseSuffix,
|
"delete sharepoint", deleteCommand, expectUse + " " + sharePointServiceCommandDeleteUseSuffix,
|
||||||
sharePointDeleteCmd().Short, deleteSharePointCmd,
|
sharePointDeleteCmd().Short, deleteSharePointCmd,
|
||||||
@ -87,40 +88,40 @@ func (suite *SharePointSuite) TestValidateSharePointBackupCreateFlags() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (suite *SharePointSuite) TestSharePointBackupDetailsSelectors() {
|
func (suite *SharePointSuite) TestSharePointBackupDetailsSelectors() {
|
||||||
// ctx, flush := tester.NewContext()
|
ctx, flush := tester.NewContext()
|
||||||
// defer flush()
|
defer flush()
|
||||||
|
|
||||||
// for _, test := range testdata.SharePointOptionDetailLookups {
|
for _, test := range testdata.SharePointOptionDetailLookups {
|
||||||
// suite.T().Run(test.Name, func(t *testing.T) {
|
suite.T().Run(test.Name, func(t *testing.T) {
|
||||||
// output, err := runDetailsSharePointCmd(
|
output, err := runDetailsSharePointCmd(
|
||||||
// ctx,
|
ctx,
|
||||||
// test.BackupGetter,
|
test.BackupGetter,
|
||||||
// "backup-ID",
|
"backup-ID",
|
||||||
// test.Opts,
|
test.Opts,
|
||||||
// )
|
)
|
||||||
// assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// assert.ElementsMatch(t, test.Expected, output.Entries)
|
assert.ElementsMatch(t, test.Expected, output.Entries)
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// func (suite *SharePointSuite) TestSharePointBackupDetailsSelectorsBadFormats() {
|
func (suite *SharePointSuite) TestSharePointBackupDetailsSelectorsBadFormats() {
|
||||||
// ctx, flush := tester.NewContext()
|
ctx, flush := tester.NewContext()
|
||||||
// defer flush()
|
defer flush()
|
||||||
|
|
||||||
// for _, test := range testdata.BadSharePointOptionsFormats {
|
for _, test := range testdata.BadSharePointOptionsFormats {
|
||||||
// suite.T().Run(test.Name, func(t *testing.T) {
|
suite.T().Run(test.Name, func(t *testing.T) {
|
||||||
// output, err := runDetailsSharePointCmd(
|
output, err := runDetailsSharePointCmd(
|
||||||
// ctx,
|
ctx,
|
||||||
// test.BackupGetter,
|
test.BackupGetter,
|
||||||
// "backup-ID",
|
"backup-ID",
|
||||||
// test.Opts,
|
test.Opts,
|
||||||
// )
|
)
|
||||||
|
|
||||||
// assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
// assert.Empty(t, output)
|
assert.Empty(t, output)
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|||||||
@ -4,12 +4,21 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (suite *ExchangeUtilsSuite) TestIncludeOneDriveRestoreDataSelectors() {
|
type OneDriveUtilsSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOneDriveUtilsSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(OneDriveUtilsSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *OneDriveUtilsSuite) TestIncludeOneDriveRestoreDataSelectors() {
|
||||||
var (
|
var (
|
||||||
empty = []string{}
|
empty = []string{}
|
||||||
single = []string{"single"}
|
single = []string{"single"}
|
||||||
|
|||||||
97
src/cli/utils/sharepoint.go
Normal file
97
src/cli/utils/sharepoint.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LibraryItemFN = "library-item"
|
||||||
|
LibraryFN = "library"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SharePointOpts struct {
|
||||||
|
Sites []string
|
||||||
|
LibraryItems []string
|
||||||
|
LibraryPaths []string
|
||||||
|
|
||||||
|
Populated PopulatedFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateSharePointRestoreFlags checks common flags for correctness and interdependencies
|
||||||
|
func ValidateSharePointRestoreFlags(backupID string, opts SharePointOpts) error {
|
||||||
|
if len(backupID) == 0 {
|
||||||
|
return errors.New("a backup ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if _, ok := opts.Populated[FileCreatedAfterFN]; ok && !IsValidTimeFormat(opts.FileCreatedAfter) {
|
||||||
|
// return errors.New("invalid time format for created-after")
|
||||||
|
// }
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSharePointFilter adds the scope of the provided values to the selector's
|
||||||
|
// filter set
|
||||||
|
func AddSharePointFilter(
|
||||||
|
sel *selectors.SharePointRestore,
|
||||||
|
v string,
|
||||||
|
f func(string) []selectors.SharePointScope,
|
||||||
|
) {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sel.Filter(f(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncludeSharePointRestoreDataSelectors builds the common data-selector
|
||||||
|
// inclusions for SharePoint commands.
|
||||||
|
func IncludeSharePointRestoreDataSelectors(
|
||||||
|
sel *selectors.SharePointRestore,
|
||||||
|
opts SharePointOpts,
|
||||||
|
) {
|
||||||
|
lp, ln := len(opts.LibraryPaths), len(opts.LibraryItems)
|
||||||
|
|
||||||
|
// only use the inclusion if either a path or item name
|
||||||
|
// is specified
|
||||||
|
if lp+ln == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opts.Sites) == 0 {
|
||||||
|
opts.Sites = selectors.Any()
|
||||||
|
}
|
||||||
|
|
||||||
|
// either scope the request to a set of sites
|
||||||
|
if lp+ln == 0 {
|
||||||
|
sel.Include(sel.Sites(opts.Sites))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.LibraryPaths = trimFolderSlash(opts.LibraryPaths)
|
||||||
|
|
||||||
|
if ln == 0 {
|
||||||
|
opts.LibraryItems = selectors.Any()
|
||||||
|
}
|
||||||
|
|
||||||
|
containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.LibraryPaths)
|
||||||
|
|
||||||
|
if len(containsFolders) > 0 {
|
||||||
|
sel.Include(sel.LibraryItems(opts.Sites, containsFolders, opts.LibraryItems))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prefixFolders) > 0 {
|
||||||
|
sel.Include(sel.LibraryItems(opts.Sites, prefixFolders, opts.LibraryItems, selectors.PrefixMatch()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterSharePointRestoreInfoSelectors builds the common info-selector filters.
|
||||||
|
func FilterSharePointRestoreInfoSelectors(
|
||||||
|
sel *selectors.SharePointRestore,
|
||||||
|
opts SharePointOpts,
|
||||||
|
) {
|
||||||
|
// AddSharePointFilter(sel, opts.FileCreatedAfter, sel.CreatedAfter)
|
||||||
|
}
|
||||||
99
src/cli/utils/sharepoint_test.go
Normal file
99
src/cli/utils/sharepoint_test.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package utils_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SharePointUtilsSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharePointUtilsSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(SharePointUtilsSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ExchangeUtilsSuite) TestIncludeSharePointRestoreDataSelectors() {
|
||||||
|
var (
|
||||||
|
empty = []string{}
|
||||||
|
single = []string{"single"}
|
||||||
|
multi = []string{"more", "than", "one"}
|
||||||
|
containsOnly = []string{"contains"}
|
||||||
|
prefixOnly = []string{"/prefix"}
|
||||||
|
containsAndPrefix = []string{"contains", "/prefix"}
|
||||||
|
)
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
opts utils.SharePointOpts
|
||||||
|
expectIncludeLen int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no inputs",
|
||||||
|
opts: utils.SharePointOpts{
|
||||||
|
Sites: empty,
|
||||||
|
LibraryPaths: empty,
|
||||||
|
LibraryItems: empty,
|
||||||
|
},
|
||||||
|
expectIncludeLen: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single inputs",
|
||||||
|
opts: utils.SharePointOpts{
|
||||||
|
Sites: single,
|
||||||
|
LibraryPaths: single,
|
||||||
|
LibraryItems: single,
|
||||||
|
},
|
||||||
|
expectIncludeLen: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multi inputs",
|
||||||
|
opts: utils.SharePointOpts{
|
||||||
|
Sites: multi,
|
||||||
|
LibraryPaths: multi,
|
||||||
|
LibraryItems: multi,
|
||||||
|
},
|
||||||
|
expectIncludeLen: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library contains",
|
||||||
|
opts: utils.SharePointOpts{
|
||||||
|
Sites: empty,
|
||||||
|
LibraryPaths: containsOnly,
|
||||||
|
LibraryItems: empty,
|
||||||
|
},
|
||||||
|
expectIncludeLen: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library prefixes",
|
||||||
|
opts: utils.SharePointOpts{
|
||||||
|
Sites: empty,
|
||||||
|
LibraryPaths: prefixOnly,
|
||||||
|
LibraryItems: empty,
|
||||||
|
},
|
||||||
|
expectIncludeLen: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library prefixes and contains",
|
||||||
|
opts: utils.SharePointOpts{
|
||||||
|
Sites: empty,
|
||||||
|
LibraryPaths: containsAndPrefix,
|
||||||
|
LibraryItems: empty,
|
||||||
|
},
|
||||||
|
expectIncludeLen: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
sel := selectors.NewSharePointRestore()
|
||||||
|
// no return, mutates sel as a side effect
|
||||||
|
utils.IncludeSharePointRestoreDataSelectors(sel, test.opts)
|
||||||
|
assert.Len(t, sel.Includes, test.expectIncludeLen)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/cli/utils/testdata/opts.go
vendored
88
src/cli/utils/testdata/opts.go
vendored
@ -395,6 +395,94 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SharePointOptionsTest struct {
|
||||||
|
Name string
|
||||||
|
Opts utils.SharePointOpts
|
||||||
|
BackupGetter *MockBackupGetter
|
||||||
|
Expected []details.DetailsEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// BadSharePointOptionsFormats contains SharePointOpts with flags that should
|
||||||
|
// cause errors about the format of the input flag. Mocks are configured to
|
||||||
|
// allow the system to run if it doesn't throw an error on formatting.
|
||||||
|
BadSharePointOptionsFormats = []SharePointOptionsTest{
|
||||||
|
// {
|
||||||
|
// Name: "BadFileCreatedBefore",
|
||||||
|
// Opts: utils.OneDriveOpts{
|
||||||
|
// FileCreatedBefore: "foo",
|
||||||
|
// Populated: utils.PopulatedFlags{
|
||||||
|
// utils.FileCreatedBeforeFN: struct{}{},
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Name: "EmptyFileCreatedBefore",
|
||||||
|
// Opts: utils.OneDriveOpts{
|
||||||
|
// FileCreatedBefore: "",
|
||||||
|
// Populated: utils.PopulatedFlags{
|
||||||
|
// utils.FileCreatedBeforeFN: struct{}{},
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
|
||||||
|
// SharePointOptionDetailLookups contains flag inputs and expected results for
|
||||||
|
// some choice input patterns. This set is not exhaustive. All inputs and
|
||||||
|
// outputs are according to the data laid out in selectors/testdata. Mocks are
|
||||||
|
// configured to return the full dataset listed in selectors/testdata.
|
||||||
|
SharePointOptionDetailLookups = []SharePointOptionsTest{
|
||||||
|
{
|
||||||
|
Name: "AllLibraryItems",
|
||||||
|
Expected: testdata.SharePointLibraryItems,
|
||||||
|
Opts: utils.SharePointOpts{
|
||||||
|
LibraryPaths: selectors.Any(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "FolderPrefixMatch",
|
||||||
|
Expected: testdata.SharePointLibraryItems,
|
||||||
|
Opts: utils.SharePointOpts{
|
||||||
|
LibraryPaths: []string{testdata.SharePointLibraryFolder},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "FolderPrefixMatchTrailingSlash",
|
||||||
|
Expected: testdata.SharePointLibraryItems,
|
||||||
|
Opts: utils.SharePointOpts{
|
||||||
|
LibraryPaths: []string{testdata.SharePointLibraryFolder + "/"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "FolderPrefixMatchTrailingSlash",
|
||||||
|
Expected: testdata.SharePointLibraryItems,
|
||||||
|
Opts: utils.SharePointOpts{
|
||||||
|
LibraryPaths: []string{testdata.SharePointLibraryFolder + "/"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ShortRef",
|
||||||
|
Expected: []details.DetailsEntry{
|
||||||
|
testdata.SharePointLibraryItems[0],
|
||||||
|
testdata.SharePointLibraryItems[1],
|
||||||
|
},
|
||||||
|
Opts: utils.SharePointOpts{
|
||||||
|
LibraryItems: []string{
|
||||||
|
testdata.SharePointLibraryItems[0].ShortRef,
|
||||||
|
testdata.SharePointLibraryItems[1].ShortRef,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// Name: "CreatedBefore",
|
||||||
|
// Expected: []details.DetailsEntry{testdata.SharePointLibraryItems[1]},
|
||||||
|
// Opts: utils.SharePointOpts{
|
||||||
|
// FileCreatedBefore: common.FormatTime(testdata.Time1.Add(time.Second)),
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// MockBackupGetter implements the repo.BackupGetter interface and returns
|
// MockBackupGetter implements the repo.BackupGetter interface and returns
|
||||||
// (selectors/testdata.GetDetailsSet(), nil, nil) when BackupDetails is called
|
// (selectors/testdata.GetDetailsSet(), nil, nil) when BackupDetails is called
|
||||||
// on the nil instance. If an instance is given or Backups is called returns an
|
// on the nil instance. If an instance is given or Backups is called returns an
|
||||||
|
|||||||
@ -352,12 +352,14 @@ func (i ExchangeInfo) Values() []string {
|
|||||||
|
|
||||||
// SharePointInfo describes a sharepoint item
|
// SharePointInfo describes a sharepoint item
|
||||||
type SharePointInfo struct {
|
type SharePointInfo struct {
|
||||||
ItemType ItemType `json:"itemType,omitempty"`
|
Created time.Time `json:"created,omitempty"`
|
||||||
ItemName string `json:"itemName,omitempty"`
|
ItemName string `json:"itemName,omitempty"`
|
||||||
Created time.Time `json:"created,omitempty"`
|
ItemType ItemType `json:"itemType,omitempty"`
|
||||||
Modified time.Time `josn:"modified,omitempty"`
|
Modified time.Time `josn:"modified,omitempty"`
|
||||||
WebURL string `json:"webUrl,omitempty"`
|
Owner string `json:"owner,omitempty"`
|
||||||
Size int64 `json:"size,omitempty"`
|
ParentPath string `json:"parentPath"`
|
||||||
|
Size int64 `json:"size,omitempty"`
|
||||||
|
WebURL string `json:"webUrl,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headers returns the human-readable names of properties in a SharePointInfo
|
// Headers returns the human-readable names of properties in a SharePointInfo
|
||||||
@ -374,13 +376,13 @@ func (i SharePointInfo) Values() []string {
|
|||||||
|
|
||||||
// OneDriveInfo describes a oneDrive item
|
// OneDriveInfo describes a oneDrive item
|
||||||
type OneDriveInfo struct {
|
type OneDriveInfo struct {
|
||||||
ItemType ItemType `json:"itemType,omitempty"`
|
|
||||||
ParentPath string `json:"parentPath"`
|
|
||||||
ItemName string `json:"itemName"`
|
|
||||||
Size int64 `json:"size,omitempty"`
|
|
||||||
Owner string `json:"owner,omitempty"`
|
|
||||||
Created time.Time `json:"created,omitempty"`
|
Created time.Time `json:"created,omitempty"`
|
||||||
|
ItemName string `json:"itemName"`
|
||||||
|
ItemType ItemType `json:"itemType,omitempty"`
|
||||||
Modified time.Time `json:"modified,omitempty"`
|
Modified time.Time `json:"modified,omitempty"`
|
||||||
|
Owner string `json:"owner,omitempty"`
|
||||||
|
ParentPath string `json:"parentPath"`
|
||||||
|
Size int64 `json:"size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headers returns the human-readable names of properties in a OneDriveInfo
|
// Headers returns the human-readable names of properties in a OneDriveInfo
|
||||||
|
|||||||
68
src/pkg/selectors/testdata/details.go
vendored
68
src/pkg/selectors/testdata/details.go
vendored
@ -228,6 +228,70 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SharePointRootPath = mustParsePath("tenant-id/sharepoint/site-id/libraries/drives/foo/root:", false)
|
||||||
|
SharePointLibraryPath = mustAppendPath(SharePointRootPath, "library", false)
|
||||||
|
SharePointBasePath1 = mustAppendPath(SharePointLibraryPath, "a", false)
|
||||||
|
SharePointBasePath2 = mustAppendPath(SharePointLibraryPath, "b", false)
|
||||||
|
|
||||||
|
SharePointLibraryItemPath1 = mustAppendPath(SharePointLibraryPath, ItemName1, true)
|
||||||
|
SharePointLibraryItemPath2 = mustAppendPath(SharePointBasePath1, ItemName2, true)
|
||||||
|
SharePointLibraryItemPath3 = mustAppendPath(SharePointBasePath2, ItemName3, true)
|
||||||
|
|
||||||
|
SharePointLibraryFolder = stdpath.Join(SharePointLibraryPath.Folders()[3:]...)
|
||||||
|
SharePointParentLibrary1 = stdpath.Join(SharePointBasePath1.Folders()[3:]...)
|
||||||
|
SharePointParentLibrary2 = stdpath.Join(SharePointBasePath2.Folders()[3:]...)
|
||||||
|
|
||||||
|
SharePointLibraryItems = []details.DetailsEntry{
|
||||||
|
{
|
||||||
|
RepoRef: SharePointLibraryItemPath1.String(),
|
||||||
|
ShortRef: SharePointLibraryItemPath1.ShortRef(),
|
||||||
|
ParentRef: SharePointLibraryItemPath1.ToBuilder().Dir().ShortRef(),
|
||||||
|
ItemInfo: details.ItemInfo{
|
||||||
|
SharePoint: &details.SharePointInfo{
|
||||||
|
ItemType: details.SharePointItem,
|
||||||
|
ParentPath: SharePointLibraryFolder,
|
||||||
|
ItemName: SharePointLibraryItemPath1.Item() + "name",
|
||||||
|
Size: int64(23),
|
||||||
|
Owner: UserEmail1,
|
||||||
|
Created: Time2,
|
||||||
|
Modified: Time4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RepoRef: SharePointLibraryItemPath2.String(),
|
||||||
|
ShortRef: SharePointLibraryItemPath2.ShortRef(),
|
||||||
|
ParentRef: SharePointLibraryItemPath2.ToBuilder().Dir().ShortRef(),
|
||||||
|
ItemInfo: details.ItemInfo{
|
||||||
|
SharePoint: &details.SharePointInfo{
|
||||||
|
ItemType: details.SharePointItem,
|
||||||
|
ParentPath: SharePointParentLibrary1,
|
||||||
|
ItemName: SharePointLibraryItemPath2.Item() + "name",
|
||||||
|
Size: int64(42),
|
||||||
|
Owner: UserEmail1,
|
||||||
|
Created: Time1,
|
||||||
|
Modified: Time3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RepoRef: SharePointLibraryItemPath3.String(),
|
||||||
|
ShortRef: SharePointLibraryItemPath3.ShortRef(),
|
||||||
|
ParentRef: SharePointLibraryItemPath3.ToBuilder().Dir().ShortRef(),
|
||||||
|
ItemInfo: details.ItemInfo{
|
||||||
|
SharePoint: &details.SharePointInfo{
|
||||||
|
ItemType: details.SharePointItem,
|
||||||
|
ParentPath: SharePointParentLibrary2,
|
||||||
|
ItemName: SharePointLibraryItemPath3.Item() + "name",
|
||||||
|
Size: int64(19),
|
||||||
|
Owner: UserEmail2,
|
||||||
|
Created: Time2,
|
||||||
|
Modified: Time4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDetailsSet() *details.Details {
|
func GetDetailsSet() *details.Details {
|
||||||
@ -249,6 +313,10 @@ func GetDetailsSet() *details.Details {
|
|||||||
entries = append(entries, e)
|
entries = append(entries, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, e := range SharePointLibraryItems {
|
||||||
|
entries = append(entries, e)
|
||||||
|
}
|
||||||
|
|
||||||
return &details.Details{
|
return &details.Details{
|
||||||
DetailsModel: details.DetailsModel{
|
DetailsModel: details.DetailsModel{
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user