adds boilerplate cli for chats backup
All code is copied and amended from existing cli boilerplate.
This commit is contained in:
parent
7d9d0e04cc
commit
bd50d8eeaa
@ -46,6 +46,7 @@ var serviceCommands = []func(cmd *cobra.Command) *cobra.Command{
|
|||||||
addOneDriveCommands,
|
addOneDriveCommands,
|
||||||
addSharePointCommands,
|
addSharePointCommands,
|
||||||
addGroupsCommands,
|
addGroupsCommands,
|
||||||
|
addTeamsChatsCommands,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCommands attaches all `corso backup * *` commands to the parent.
|
// AddCommands attaches all `corso backup * *` commands to the parent.
|
||||||
|
|||||||
@ -310,7 +310,7 @@ func groupsBackupCreateSelectors(
|
|||||||
group, cats []string,
|
group, cats []string,
|
||||||
) *selectors.GroupsBackup {
|
) *selectors.GroupsBackup {
|
||||||
if filters.PathContains(group).Compare(flags.Wildcard) {
|
if filters.PathContains(group).Compare(flags.Wildcard) {
|
||||||
return includeAllGroupWithCategories(ins, cats)
|
return includeAllGroupsWithCategories(ins, cats)
|
||||||
}
|
}
|
||||||
|
|
||||||
sel := selectors.NewGroupsBackup(slices.Clone(group))
|
sel := selectors.NewGroupsBackup(slices.Clone(group))
|
||||||
@ -318,6 +318,6 @@ func groupsBackupCreateSelectors(
|
|||||||
return utils.AddGroupsCategories(sel, cats)
|
return utils.AddGroupsCategories(sel, cats)
|
||||||
}
|
}
|
||||||
|
|
||||||
func includeAllGroupWithCategories(ins idname.Cacher, categories []string) *selectors.GroupsBackup {
|
func includeAllGroupsWithCategories(ins idname.Cacher, categories []string) *selectors.GroupsBackup {
|
||||||
return utils.AddGroupsCategories(selectors.NewGroupsBackup(ins.IDs()), categories)
|
return utils.AddGroupsCategories(selectors.NewGroupsBackup(ins.IDs()), categories)
|
||||||
}
|
}
|
||||||
|
|||||||
305
src/cli/backup/teamschats.go
Normal file
305
src/cli/backup/teamschats.go
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
|
. "github.com/alcionai/corso/src/cli/print"
|
||||||
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/filters"
|
||||||
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// setup and globals
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const (
|
||||||
|
teamschatsServiceCommand = "chats"
|
||||||
|
teamschatsServiceCommandCreateUseSuffix = "--user <userEmail> | '" + flags.Wildcard + "'"
|
||||||
|
teamschatsServiceCommandDeleteUseSuffix = "--backups <backupId>"
|
||||||
|
teamschatsServiceCommandDetailsUseSuffix = "--backup <backupId>"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
teamschatsServiceCommandCreateExamples = `# Backup all chats with bob@company.hr
|
||||||
|
corso backup create chats --user bob@company.hr
|
||||||
|
|
||||||
|
# Backup all chats for all users
|
||||||
|
corso backup create chats --user '*'`
|
||||||
|
|
||||||
|
teamschatsServiceCommandDeleteExamples = `# Delete chats backup with ID 1234abcd-12ab-cd34-56de-1234abcd \
|
||||||
|
and 1234abcd-12ab-cd34-56de-1234abce
|
||||||
|
corso backup delete chats --backups 1234abcd-12ab-cd34-56de-1234abcd,1234abcd-12ab-cd34-56de-1234abce`
|
||||||
|
|
||||||
|
teamschatsServiceCommandDetailsExamples = `# Explore chats in Bob's latest backup (1234abcd...)
|
||||||
|
corso backup details chats --backup 1234abcd-12ab-cd34-56de-1234abcd`
|
||||||
|
)
|
||||||
|
|
||||||
|
// called by backup.go to map subcommands to provider-specific handling.
|
||||||
|
func addTeamsChatsCommands(cmd *cobra.Command) *cobra.Command {
|
||||||
|
var c *cobra.Command
|
||||||
|
|
||||||
|
switch cmd.Use {
|
||||||
|
case createCommand:
|
||||||
|
c, _ = utils.AddCommand(cmd, teamschatsCreateCmd(), utils.MarkPreReleaseCommand())
|
||||||
|
|
||||||
|
c.Use = c.Use + " " + teamschatsServiceCommandCreateUseSuffix
|
||||||
|
c.Example = teamschatsServiceCommandCreateExamples
|
||||||
|
|
||||||
|
// Flags addition ordering should follow the order we want them to appear in help and docs:
|
||||||
|
flags.AddUserFlag(c)
|
||||||
|
flags.AddDataFlag(c, []string{flags.DataChats}, false)
|
||||||
|
flags.AddGenericBackupFlags(c)
|
||||||
|
|
||||||
|
case listCommand:
|
||||||
|
c, _ = utils.AddCommand(cmd, teamschatsListCmd(), utils.MarkPreReleaseCommand())
|
||||||
|
|
||||||
|
flags.AddBackupIDFlag(c, false)
|
||||||
|
flags.AddAllBackupListFlags(c)
|
||||||
|
|
||||||
|
case detailsCommand:
|
||||||
|
c, _ = utils.AddCommand(cmd, teamschatsDetailsCmd(), utils.MarkPreReleaseCommand())
|
||||||
|
|
||||||
|
c.Use = c.Use + " " + teamschatsServiceCommandDetailsUseSuffix
|
||||||
|
c.Example = teamschatsServiceCommandDetailsExamples
|
||||||
|
|
||||||
|
flags.AddSkipReduceFlag(c)
|
||||||
|
|
||||||
|
// Flags addition ordering should follow the order we want them to appear in help and docs:
|
||||||
|
// More generic (ex: --user) and more frequently used flags take precedence.
|
||||||
|
flags.AddBackupIDFlag(c, true)
|
||||||
|
flags.AddTeamsChatsDetailsAndRestoreFlags(c)
|
||||||
|
|
||||||
|
case deleteCommand:
|
||||||
|
c, _ = utils.AddCommand(cmd, teamschatsDeleteCmd(), utils.MarkPreReleaseCommand())
|
||||||
|
|
||||||
|
c.Use = c.Use + " " + teamschatsServiceCommandDeleteUseSuffix
|
||||||
|
c.Example = teamschatsServiceCommandDeleteExamples
|
||||||
|
|
||||||
|
flags.AddMultipleBackupIDsFlag(c, false)
|
||||||
|
flags.AddBackupIDFlag(c, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// backup create
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// `corso backup create chats [<flag>...]`
|
||||||
|
func teamschatsCreateCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: teamschatsServiceCommand,
|
||||||
|
Aliases: []string{teamsServiceCommand},
|
||||||
|
Short: "Backup M365 Chats service data",
|
||||||
|
RunE: createTeamsChatsCmd,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processes a teamschats service backup.
|
||||||
|
func createTeamsChatsCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
|
||||||
|
if utils.HasNoFlagsAndShownHelp(cmd) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.RunModeFV == flags.RunModeFlagTest {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateTeamsChatsBackupCreateFlags(flags.UserFV, flags.CategoryDataFV); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, acct, err := utils.AccountConnectAndWriteRepoConfig(
|
||||||
|
ctx,
|
||||||
|
cmd,
|
||||||
|
path.TeamsChatsService)
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
|
// TODO: log/print recoverable errors
|
||||||
|
errs := fault.New(false)
|
||||||
|
|
||||||
|
svcCli, err := m365.NewM365Client(ctx, *acct)
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, clues.Stack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ins, err := svcCli.AC.Users().GetAllIDsAndNames(ctx, errs)
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, clues.Wrap(err, "Failed to retrieve M365 teamschats"))
|
||||||
|
}
|
||||||
|
|
||||||
|
sel := teamschatsBackupCreateSelectors(ctx, ins, flags.UserFV, flags.CategoryDataFV)
|
||||||
|
selectorSet := []selectors.Selector{}
|
||||||
|
|
||||||
|
for _, discSel := range sel.SplitByResourceOwner(ins.IDs()) {
|
||||||
|
selectorSet = append(selectorSet, discSel.Selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
return genericCreateCommand(
|
||||||
|
ctx,
|
||||||
|
r,
|
||||||
|
"Chats",
|
||||||
|
selectorSet,
|
||||||
|
ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// backup list
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// `corso backup list teamschats [<flag>...]`
|
||||||
|
func teamschatsListCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: teamschatsServiceCommand,
|
||||||
|
Short: "List the history of M365 TeamsChats service backups",
|
||||||
|
RunE: listTeamsChatsCmd,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lists the history of backup operations
|
||||||
|
func listTeamsChatsCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
return genericListCommand(cmd, flags.BackupIDFV, path.TeamsChatsService, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// backup details
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// `corso backup details teamschats [<flag>...]`
|
||||||
|
func teamschatsDetailsCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: teamschatsServiceCommand,
|
||||||
|
Short: "Shows the details of a M365 TeamsChats service backup",
|
||||||
|
RunE: detailsTeamsChatsCmd,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processes a teamschats service backup.
|
||||||
|
func detailsTeamsChatsCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
if utils.HasNoFlagsAndShownHelp(cmd) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.RunModeFV == flags.RunModeFlagTest {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return runDetailsTeamsChatsCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDetailsTeamsChatsCmd(cmd *cobra.Command) error {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
opts := utils.MakeTeamsChatsOpts(cmd)
|
||||||
|
|
||||||
|
sel := utils.IncludeTeamsChatsRestoreDataSelectors(ctx, opts)
|
||||||
|
sel.Configure(selectors.Config{OnlyMatchItemNames: true})
|
||||||
|
utils.FilterTeamsChatsRestoreInfoSelectors(sel, opts)
|
||||||
|
|
||||||
|
ds, err := genericDetailsCommand(cmd, flags.BackupIDFV, sel.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ds.Entries) > 0 {
|
||||||
|
ds.PrintEntries(ctx)
|
||||||
|
} else {
|
||||||
|
Info(ctx, selectors.ErrorNoMatchingItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// backup delete
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// `corso backup delete teamschats [<flag>...]`
|
||||||
|
func teamschatsDeleteCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: teamschatsServiceCommand,
|
||||||
|
Short: "Delete backed-up M365 TeamsChats service data",
|
||||||
|
RunE: deleteTeamsChatsCmd,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deletes an teamschats service backup.
|
||||||
|
func deleteTeamsChatsCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
backupIDValue := []string{}
|
||||||
|
|
||||||
|
if len(flags.BackupIDsFV) > 0 {
|
||||||
|
backupIDValue = flags.BackupIDsFV
|
||||||
|
} else if len(flags.BackupIDFV) > 0 {
|
||||||
|
backupIDValue = append(backupIDValue, flags.BackupIDFV)
|
||||||
|
} else {
|
||||||
|
return clues.New("either --backup or --backups flag is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return genericDeleteCommand(cmd, path.TeamsChatsService, "TeamsChats", backupIDValue, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func validateTeamsChatsBackupCreateFlags(teamschats, cats []string) error {
|
||||||
|
if len(teamschats) == 0 {
|
||||||
|
return clues.New(
|
||||||
|
"requires one or more --" +
|
||||||
|
flags.UserFN + " ids, or the wildcard --" +
|
||||||
|
flags.UserFN + " *")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(
|
||||||
|
" is an unrecognized data type; only %s is supported",
|
||||||
|
flags.DataChats)
|
||||||
|
|
||||||
|
allowedCats := utils.TeamsChatsAllowedCategories()
|
||||||
|
|
||||||
|
for _, d := range cats {
|
||||||
|
if _, ok := allowedCats[d]; !ok {
|
||||||
|
return clues.New(d + msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func teamschatsBackupCreateSelectors(
|
||||||
|
ctx context.Context,
|
||||||
|
ins idname.Cacher,
|
||||||
|
users, cats []string,
|
||||||
|
) *selectors.TeamsChatsBackup {
|
||||||
|
if filters.PathContains(users).Compare(flags.Wildcard) {
|
||||||
|
return includeAllTeamsChatsWithCategories(ins, cats)
|
||||||
|
}
|
||||||
|
|
||||||
|
sel := selectors.NewTeamsChatsBackup(slices.Clone(users))
|
||||||
|
|
||||||
|
return utils.AddTeamsChatsCategories(sel, cats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func includeAllTeamsChatsWithCategories(ins idname.Cacher, categories []string) *selectors.TeamsChatsBackup {
|
||||||
|
return utils.AddTeamsChatsCategories(selectors.NewTeamsChatsBackup(ins.IDs()), categories)
|
||||||
|
}
|
||||||
631
src/cli/backup/teamschats_e2e_test.go
Normal file
631
src/cli/backup/teamschats_e2e_test.go
Normal file
@ -0,0 +1,631 @@
|
|||||||
|
package backup_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/cli"
|
||||||
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
|
"github.com/alcionai/corso/src/cli/print"
|
||||||
|
cliTD "github.com/alcionai/corso/src/cli/testdata"
|
||||||
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
|
"github.com/alcionai/corso/src/internal/operations"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
|
"github.com/alcionai/corso/src/pkg/config"
|
||||||
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
|
selTD "github.com/alcionai/corso/src/pkg/selectors/testdata"
|
||||||
|
storeTD "github.com/alcionai/corso/src/pkg/storage/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// tests that require no existing backups
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type NoBackupTeamsChatsE2ESuite struct {
|
||||||
|
tester.Suite
|
||||||
|
dpnd dependencies
|
||||||
|
its intgTesterSetup
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoBackupTeamsChatsE2ESuite(t *testing.T) {
|
||||||
|
suite.Run(t, &BackupTeamsChatsE2ESuite{Suite: tester.NewE2ESuite(
|
||||||
|
t,
|
||||||
|
[][]string{storeTD.AWSStorageCredEnvs, tconfig.M365AcctCredEnvs})})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *NoBackupTeamsChatsE2ESuite) SetupSuite() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
suite.its = newIntegrationTesterSetup(t)
|
||||||
|
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *NoBackupTeamsChatsE2ESuite) TestTeamsChatsBackupListCmd_noBackups() {
|
||||||
|
t := suite.T()
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
suite.dpnd.recorder.Reset()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "list", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
cmd.SetErr(&suite.dpnd.recorder)
|
||||||
|
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
result := suite.dpnd.recorder.String()
|
||||||
|
|
||||||
|
// as an offhand check: the result should contain the m365 teamschat id
|
||||||
|
assert.True(t, strings.HasSuffix(result, "No backups available\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// tests with no prior backup
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type BackupTeamsChatsE2ESuite struct {
|
||||||
|
tester.Suite
|
||||||
|
dpnd dependencies
|
||||||
|
its intgTesterSetup
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupTeamsChatsE2ESuite(t *testing.T) {
|
||||||
|
suite.Run(t, &BackupTeamsChatsE2ESuite{Suite: tester.NewE2ESuite(
|
||||||
|
t,
|
||||||
|
[][]string{storeTD.AWSStorageCredEnvs, tconfig.M365AcctCredEnvs})})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupTeamsChatsE2ESuite) SetupSuite() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
suite.its = newIntegrationTesterSetup(t)
|
||||||
|
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupTeamsChatsE2ESuite) TestTeamsChatsBackupCmd_chats() {
|
||||||
|
runTeamsChatsBackupCategoryTest(suite, flags.DataChats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTeamsChatsBackupCategoryTest(suite *BackupTeamsChatsE2ESuite, category string) {
|
||||||
|
recorder := strings.Builder{}
|
||||||
|
recorder.Reset()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
cmd, ctx := buildTeamsChatsBackupCmd(
|
||||||
|
ctx,
|
||||||
|
suite.dpnd.configFilePath,
|
||||||
|
suite.its.user.ID,
|
||||||
|
category,
|
||||||
|
&recorder)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
result := recorder.String()
|
||||||
|
t.Log("backup results", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupTeamsChatsE2ESuite) TestTeamsChatsBackupCmd_teamschatNotFound_chats() {
|
||||||
|
runTeamsChatsBackupTeamsChatNotFoundTest(suite, flags.DataChats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTeamsChatsBackupTeamsChatNotFoundTest(suite *BackupTeamsChatsE2ESuite, category string) {
|
||||||
|
recorder := strings.Builder{}
|
||||||
|
recorder.Reset()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
cmd, ctx := buildTeamsChatsBackupCmd(
|
||||||
|
ctx,
|
||||||
|
suite.dpnd.configFilePath,
|
||||||
|
"foo@not-there.com",
|
||||||
|
category,
|
||||||
|
&recorder)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.Error(t, err, clues.ToCore(err))
|
||||||
|
assert.Contains(
|
||||||
|
t,
|
||||||
|
err.Error(),
|
||||||
|
"not found",
|
||||||
|
"error missing user not found")
|
||||||
|
assert.NotContains(t, err.Error(), "runtime error", "panic happened")
|
||||||
|
|
||||||
|
t.Logf("backup error message: %s", err.Error())
|
||||||
|
|
||||||
|
result := recorder.String()
|
||||||
|
t.Log("backup results", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupTeamsChatsE2ESuite) TestBackupCreateTeamsChats_badAzureClientIDFlag() {
|
||||||
|
t := suite.T()
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
suite.dpnd.recorder.Reset()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "create", "chats",
|
||||||
|
"--teamschat", suite.its.user.ID,
|
||||||
|
"--azure-client-id", "invalid-value")
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
cmd.SetErr(&suite.dpnd.recorder)
|
||||||
|
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.Error(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupTeamsChatsE2ESuite) TestBackupCreateTeamsChats_fromConfigFile() {
|
||||||
|
t := suite.T()
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
suite.dpnd.recorder.Reset()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "create", "chats",
|
||||||
|
"--teamschat", suite.its.user.ID,
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
cmd.SetOut(&suite.dpnd.recorder)
|
||||||
|
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AWS flags
|
||||||
|
func (suite *BackupTeamsChatsE2ESuite) TestBackupCreateTeamsChats_badAWSFlags() {
|
||||||
|
t := suite.T()
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
suite.dpnd.recorder.Reset()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "create", "chats",
|
||||||
|
"--teamschat", suite.its.user.ID,
|
||||||
|
"--aws-access-key", "invalid-value",
|
||||||
|
"--aws-secret-access-key", "some-invalid-value")
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
cmd.SetOut(&suite.dpnd.recorder)
|
||||||
|
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
// since invalid aws creds are explicitly set, should see a failure
|
||||||
|
require.Error(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// tests prepared with a previous backup
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type PreparedBackupTeamsChatsE2ESuite struct {
|
||||||
|
tester.Suite
|
||||||
|
dpnd dependencies
|
||||||
|
backupOps map[path.CategoryType]string
|
||||||
|
its intgTesterSetup
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreparedBackupTeamsChatsE2ESuite(t *testing.T) {
|
||||||
|
suite.Run(t, &PreparedBackupTeamsChatsE2ESuite{
|
||||||
|
Suite: tester.NewE2ESuite(
|
||||||
|
t,
|
||||||
|
[][]string{storeTD.AWSStorageCredEnvs, tconfig.M365AcctCredEnvs}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PreparedBackupTeamsChatsE2ESuite) SetupSuite() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
suite.its = newIntegrationTesterSetup(t)
|
||||||
|
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
||||||
|
suite.backupOps = make(map[path.CategoryType]string)
|
||||||
|
|
||||||
|
var (
|
||||||
|
teamschats = []string{suite.its.user.ID}
|
||||||
|
ins = idname.NewCache(map[string]string{suite.its.user.ID: suite.its.user.ID})
|
||||||
|
cats = []path.CategoryType{
|
||||||
|
path.ChatsCategory,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, set := range cats {
|
||||||
|
var (
|
||||||
|
sel = selectors.NewTeamsChatsBackup(teamschats)
|
||||||
|
scopes []selectors.TeamsChatsScope
|
||||||
|
)
|
||||||
|
|
||||||
|
switch set {
|
||||||
|
case path.ChatsCategory:
|
||||||
|
scopes = selTD.TeamsChatsBackupChatScope(sel)
|
||||||
|
}
|
||||||
|
|
||||||
|
sel.Include(scopes)
|
||||||
|
|
||||||
|
bop, err := suite.dpnd.repo.NewBackupWithLookup(ctx, sel.Selector, ins)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
err = bop.Run(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
bIDs := string(bop.Results.BackupID)
|
||||||
|
|
||||||
|
// sanity check, ensure we can find the backup and its details immediately
|
||||||
|
b, err := suite.dpnd.repo.Backup(ctx, string(bop.Results.BackupID))
|
||||||
|
require.NoError(t, err, "retrieving recent backup by ID")
|
||||||
|
require.Equal(t, bIDs, string(b.ID), "repo backup matches results id")
|
||||||
|
|
||||||
|
_, b, errs := suite.dpnd.repo.GetBackupDetails(ctx, bIDs)
|
||||||
|
require.NoError(t, errs.Failure(), "retrieving recent backup details by ID")
|
||||||
|
require.Empty(t, errs.Recovered(), "retrieving recent backup details by ID")
|
||||||
|
require.Equal(t, bIDs, string(b.ID), "repo details matches results id")
|
||||||
|
|
||||||
|
suite.backupOps[set] = string(b.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PreparedBackupTeamsChatsE2ESuite) TestTeamsChatsListCmd_chats() {
|
||||||
|
runTeamsChatsListCmdTest(suite, path.ChatsCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTeamsChatsListCmdTest(suite *PreparedBackupTeamsChatsE2ESuite, category path.CategoryType) {
|
||||||
|
suite.dpnd.recorder.Reset()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "list", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
cmd.SetOut(&suite.dpnd.recorder)
|
||||||
|
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
// compare the output
|
||||||
|
result := suite.dpnd.recorder.String()
|
||||||
|
assert.Contains(t, result, suite.backupOps[category])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PreparedBackupTeamsChatsE2ESuite) TestTeamsChatsListCmd_singleID_chats() {
|
||||||
|
runTeamsChatsListSingleCmdTest(suite, path.ChatsCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTeamsChatsListSingleCmdTest(suite *PreparedBackupTeamsChatsE2ESuite, category path.CategoryType) {
|
||||||
|
suite.dpnd.recorder.Reset()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
bID := suite.backupOps[category]
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "list", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath,
|
||||||
|
"--backup", string(bID))
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
cmd.SetOut(&suite.dpnd.recorder)
|
||||||
|
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
// compare the output
|
||||||
|
result := suite.dpnd.recorder.String()
|
||||||
|
assert.Contains(t, result, bID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PreparedBackupTeamsChatsE2ESuite) TestTeamsChatsListCmd_badID() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "list", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath,
|
||||||
|
"--backup", "smarfs")
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.Error(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PreparedBackupTeamsChatsE2ESuite) TestTeamsChatsDetailsCmd_chats() {
|
||||||
|
runTeamsChatsDetailsCmdTest(suite, path.ChatsCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTeamsChatsDetailsCmdTest(suite *PreparedBackupTeamsChatsE2ESuite, category path.CategoryType) {
|
||||||
|
suite.dpnd.recorder.Reset()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
bID := suite.backupOps[category]
|
||||||
|
|
||||||
|
// fetch the details from the repo first
|
||||||
|
deets, _, errs := suite.dpnd.repo.GetBackupDetails(ctx, string(bID))
|
||||||
|
require.NoError(t, errs.Failure(), clues.ToCore(errs.Failure()))
|
||||||
|
require.Empty(t, errs.Recovered())
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "details", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath,
|
||||||
|
"--"+flags.BackupFN, string(bID))
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
cmd.SetOut(&suite.dpnd.recorder)
|
||||||
|
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
// compare the output
|
||||||
|
result := suite.dpnd.recorder.String()
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
foundFolders := 0
|
||||||
|
|
||||||
|
for _, ent := range deets.Entries {
|
||||||
|
// Skip folders as they don't mean anything to the end teamschat.
|
||||||
|
if ent.Folder != nil {
|
||||||
|
foundFolders++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.Run(fmt.Sprintf("detail %d", i), func() {
|
||||||
|
assert.Contains(suite.T(), result, ent.ShortRef)
|
||||||
|
})
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only backup the default folder for each category so there should be at
|
||||||
|
// least that folder (we don't make details entries for prefix folders).
|
||||||
|
assert.GreaterOrEqual(t, foundFolders, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// tests for deleting backups
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type BackupDeleteTeamsChatsE2ESuite struct {
|
||||||
|
tester.Suite
|
||||||
|
dpnd dependencies
|
||||||
|
backupOps [3]operations.BackupOperation
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupDeleteTeamsChatsE2ESuite(t *testing.T) {
|
||||||
|
suite.Run(t, &BackupDeleteTeamsChatsE2ESuite{
|
||||||
|
Suite: tester.NewE2ESuite(
|
||||||
|
t,
|
||||||
|
[][]string{storeTD.AWSStorageCredEnvs, tconfig.M365AcctCredEnvs}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupDeleteTeamsChatsE2ESuite) SetupSuite() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
||||||
|
|
||||||
|
m365TeamsChatID := tconfig.M365TeamID(t)
|
||||||
|
teamschats := []string{m365TeamsChatID}
|
||||||
|
|
||||||
|
// some tests require an existing backup
|
||||||
|
sel := selectors.NewTeamsChatsBackup(teamschats)
|
||||||
|
sel.Include(selTD.TeamsChatsBackupChatScope(sel))
|
||||||
|
|
||||||
|
for i := 0; i < cap(suite.backupOps); i++ {
|
||||||
|
backupOp, err := suite.dpnd.repo.NewBackup(ctx, sel.Selector)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
suite.backupOps[i] = backupOp
|
||||||
|
|
||||||
|
err = suite.backupOps[i].Run(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupDeleteTeamsChatsE2ESuite) TestTeamsChatsBackupDeleteCmd() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "delete", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath,
|
||||||
|
"--"+flags.BackupIDsFN,
|
||||||
|
fmt.Sprintf("%s,%s",
|
||||||
|
string(suite.backupOps[0].Results.BackupID),
|
||||||
|
string(suite.backupOps[1].Results.BackupID)))
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
// a follow-up details call should fail, due to the backup ID being deleted
|
||||||
|
cmd = cliTD.StubRootCmd(
|
||||||
|
"backup", "details", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath,
|
||||||
|
"--backups", string(suite.backupOps[0].Results.BackupID))
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
err = cmd.ExecuteContext(ctx)
|
||||||
|
require.Error(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupDeleteTeamsChatsE2ESuite) TestTeamsChatsBackupDeleteCmd_SingleID() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "delete", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath,
|
||||||
|
"--"+flags.BackupFN,
|
||||||
|
string(suite.backupOps[2].Results.BackupID))
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
// a follow-up details call should fail, due to the backup ID being deleted
|
||||||
|
cmd = cliTD.StubRootCmd(
|
||||||
|
"backup", "details", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath,
|
||||||
|
"--backup", string(suite.backupOps[2].Results.BackupID))
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
err = cmd.ExecuteContext(ctx)
|
||||||
|
require.Error(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupDeleteTeamsChatsE2ESuite) TestTeamsChatsBackupDeleteCmd_UnknownID() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "delete", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath,
|
||||||
|
"--"+flags.BackupIDsFN, uuid.NewString())
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
// unknown backupIDs should error since the modelStore can't find the backup
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.Error(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupDeleteTeamsChatsE2ESuite) TestTeamsChatsBackupDeleteCmd_NoBackupID() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
|
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "delete", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
// empty backupIDs should error since no data provided
|
||||||
|
err := cmd.ExecuteContext(ctx)
|
||||||
|
require.Error(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func buildTeamsChatsBackupCmd(
|
||||||
|
ctx context.Context,
|
||||||
|
configFile, resource, category string,
|
||||||
|
recorder *strings.Builder,
|
||||||
|
) (*cobra.Command, context.Context) {
|
||||||
|
cmd := cliTD.StubRootCmd(
|
||||||
|
"backup", "create", "chats",
|
||||||
|
"--"+flags.ConfigFileFN, configFile,
|
||||||
|
"--"+flags.UserFN, resource,
|
||||||
|
"--"+flags.CategoryDataFN, category)
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
cmd.SetOut(recorder)
|
||||||
|
|
||||||
|
return cmd, print.SetRootCmd(ctx, cmd)
|
||||||
|
}
|
||||||
248
src/cli/backup/teamschats_test.go
Normal file
248
src/cli/backup/teamschats_test.go
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
|
flagsTD "github.com/alcionai/corso/src/cli/flags/testdata"
|
||||||
|
cliTD "github.com/alcionai/corso/src/cli/testdata"
|
||||||
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TeamsChatsUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamsChatsUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &TeamsChatsUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TeamsChatsUnitSuite) TestAddTeamsChatsCommands() {
|
||||||
|
expectUse := teamschatsServiceCommand
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
use string
|
||||||
|
expectUse string
|
||||||
|
expectShort string
|
||||||
|
expectRunE func(*cobra.Command, []string) error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "create teamschats",
|
||||||
|
use: createCommand,
|
||||||
|
expectUse: expectUse + " " + teamschatsServiceCommandCreateUseSuffix,
|
||||||
|
expectShort: teamschatsCreateCmd().Short,
|
||||||
|
expectRunE: createTeamsChatsCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list teamschats",
|
||||||
|
use: listCommand,
|
||||||
|
expectUse: expectUse,
|
||||||
|
expectShort: teamschatsListCmd().Short,
|
||||||
|
expectRunE: listTeamsChatsCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "details teamschats",
|
||||||
|
use: detailsCommand,
|
||||||
|
expectUse: expectUse + " " + teamschatsServiceCommandDetailsUseSuffix,
|
||||||
|
expectShort: teamschatsDetailsCmd().Short,
|
||||||
|
expectRunE: detailsTeamsChatsCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete teamschats",
|
||||||
|
use: deleteCommand,
|
||||||
|
expectUse: expectUse + " " + teamschatsServiceCommandDeleteUseSuffix,
|
||||||
|
expectShort: teamschatsDeleteCmd().Short,
|
||||||
|
expectRunE: deleteTeamsChatsCmd,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
cmd := &cobra.Command{Use: test.use}
|
||||||
|
|
||||||
|
c := addTeamsChatsCommands(cmd)
|
||||||
|
require.NotNil(t, c)
|
||||||
|
|
||||||
|
cmds := cmd.Commands()
|
||||||
|
require.Len(t, cmds, 1)
|
||||||
|
|
||||||
|
child := cmds[0]
|
||||||
|
assert.Equal(t, test.expectUse, child.Use)
|
||||||
|
assert.Equal(t, test.expectShort, child.Short)
|
||||||
|
tester.AreSameFunc(t, test.expectRunE, child.RunE)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TeamsChatsUnitSuite) TestValidateTeamsChatsBackupCreateFlags() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
cats []string
|
||||||
|
expect assert.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "none",
|
||||||
|
cats: []string{},
|
||||||
|
expect: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "chats",
|
||||||
|
cats: []string{flags.DataChats},
|
||||||
|
expect: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all allowed",
|
||||||
|
cats: []string{
|
||||||
|
flags.DataChats,
|
||||||
|
},
|
||||||
|
expect: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad inputs",
|
||||||
|
cats: []string{"foo"},
|
||||||
|
expect: assert.Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
err := validateTeamsChatsBackupCreateFlags([]string{"*"}, test.cats)
|
||||||
|
test.expect(suite.T(), err, clues.ToCore(err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TeamsChatsUnitSuite) TestBackupCreateFlags() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
cmd := cliTD.SetUpCmdHasFlags(
|
||||||
|
t,
|
||||||
|
&cobra.Command{Use: createCommand},
|
||||||
|
addTeamsChatsCommands,
|
||||||
|
[]cliTD.UseCobraCommandFn{
|
||||||
|
flags.AddAllProviderFlags,
|
||||||
|
flags.AddAllStorageFlags,
|
||||||
|
},
|
||||||
|
flagsTD.WithFlags(
|
||||||
|
teamschatsServiceCommand,
|
||||||
|
[]string{
|
||||||
|
"--" + flags.RunModeFN, flags.RunModeFlagTest,
|
||||||
|
"--" + flags.UserFN, flagsTD.FlgInputs(flagsTD.UsersInput),
|
||||||
|
"--" + flags.CategoryDataFN, flagsTD.FlgInputs(flagsTD.TeamsChatsCategoryDataInput),
|
||||||
|
},
|
||||||
|
flagsTD.PreparedGenericBackupFlags(),
|
||||||
|
flagsTD.PreparedProviderFlags(),
|
||||||
|
flagsTD.PreparedStorageFlags()))
|
||||||
|
|
||||||
|
opts := utils.MakeTeamsChatsOpts(cmd)
|
||||||
|
co := utils.Control()
|
||||||
|
backupOpts := utils.ParseBackupOptions()
|
||||||
|
|
||||||
|
// TODO(ashmrtn): Remove flag checks on control.Options to control.Backup once
|
||||||
|
// restore flags are switched over too and we no longer parse flags beyond
|
||||||
|
// connection info into control.Options.
|
||||||
|
assert.Equal(t, control.FailFast, backupOpts.FailureHandling)
|
||||||
|
assert.True(t, backupOpts.Incrementals.ForceFullEnumeration)
|
||||||
|
assert.True(t, backupOpts.Incrementals.ForceItemDataRefresh)
|
||||||
|
|
||||||
|
assert.Equal(t, control.FailFast, co.FailureHandling)
|
||||||
|
assert.True(t, co.ToggleFeatures.DisableIncrementals)
|
||||||
|
assert.True(t, co.ToggleFeatures.ForceItemDataDownload)
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, flagsTD.UsersInput, opts.Users)
|
||||||
|
flagsTD.AssertGenericBackupFlags(t, cmd)
|
||||||
|
flagsTD.AssertProviderFlags(t, cmd)
|
||||||
|
flagsTD.AssertStorageFlags(t, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TeamsChatsUnitSuite) TestBackupListFlags() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
cmd := cliTD.SetUpCmdHasFlags(
|
||||||
|
t,
|
||||||
|
&cobra.Command{Use: listCommand},
|
||||||
|
addTeamsChatsCommands,
|
||||||
|
[]cliTD.UseCobraCommandFn{
|
||||||
|
flags.AddAllProviderFlags,
|
||||||
|
flags.AddAllStorageFlags,
|
||||||
|
},
|
||||||
|
flagsTD.WithFlags(
|
||||||
|
teamschatsServiceCommand,
|
||||||
|
[]string{
|
||||||
|
"--" + flags.RunModeFN, flags.RunModeFlagTest,
|
||||||
|
"--" + flags.BackupFN, flagsTD.BackupInput,
|
||||||
|
},
|
||||||
|
flagsTD.PreparedBackupListFlags(),
|
||||||
|
flagsTD.PreparedProviderFlags(),
|
||||||
|
flagsTD.PreparedStorageFlags()))
|
||||||
|
|
||||||
|
assert.Equal(t, flagsTD.BackupInput, flags.BackupIDFV)
|
||||||
|
flagsTD.AssertBackupListFlags(t, cmd)
|
||||||
|
flagsTD.AssertProviderFlags(t, cmd)
|
||||||
|
flagsTD.AssertStorageFlags(t, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TeamsChatsUnitSuite) TestBackupDetailsFlags() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
cmd := cliTD.SetUpCmdHasFlags(
|
||||||
|
t,
|
||||||
|
&cobra.Command{Use: detailsCommand},
|
||||||
|
addTeamsChatsCommands,
|
||||||
|
[]cliTD.UseCobraCommandFn{
|
||||||
|
flags.AddAllProviderFlags,
|
||||||
|
flags.AddAllStorageFlags,
|
||||||
|
},
|
||||||
|
flagsTD.WithFlags(
|
||||||
|
teamschatsServiceCommand,
|
||||||
|
[]string{
|
||||||
|
"--" + flags.RunModeFN, flags.RunModeFlagTest,
|
||||||
|
"--" + flags.BackupFN, flagsTD.BackupInput,
|
||||||
|
"--" + flags.SkipReduceFN,
|
||||||
|
},
|
||||||
|
flagsTD.PreparedTeamsChatsFlags(),
|
||||||
|
flagsTD.PreparedProviderFlags(),
|
||||||
|
flagsTD.PreparedStorageFlags()))
|
||||||
|
|
||||||
|
co := utils.Control()
|
||||||
|
|
||||||
|
assert.Equal(t, flagsTD.BackupInput, flags.BackupIDFV)
|
||||||
|
assert.True(t, co.SkipReduce)
|
||||||
|
flagsTD.AssertProviderFlags(t, cmd)
|
||||||
|
flagsTD.AssertStorageFlags(t, cmd)
|
||||||
|
flagsTD.AssertTeamsChatsFlags(t, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TeamsChatsUnitSuite) TestBackupDeleteFlags() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
cmd := cliTD.SetUpCmdHasFlags(
|
||||||
|
t,
|
||||||
|
&cobra.Command{Use: deleteCommand},
|
||||||
|
addTeamsChatsCommands,
|
||||||
|
[]cliTD.UseCobraCommandFn{
|
||||||
|
flags.AddAllProviderFlags,
|
||||||
|
flags.AddAllStorageFlags,
|
||||||
|
},
|
||||||
|
flagsTD.WithFlags(
|
||||||
|
teamschatsServiceCommand,
|
||||||
|
[]string{
|
||||||
|
"--" + flags.RunModeFN, flags.RunModeFlagTest,
|
||||||
|
"--" + flags.BackupFN, flagsTD.BackupInput,
|
||||||
|
},
|
||||||
|
flagsTD.PreparedProviderFlags(),
|
||||||
|
flagsTD.PreparedStorageFlags()))
|
||||||
|
|
||||||
|
assert.Equal(t, flagsTD.BackupInput, flags.BackupIDFV)
|
||||||
|
flagsTD.AssertProviderFlags(t, cmd)
|
||||||
|
flagsTD.AssertStorageFlags(t, cmd)
|
||||||
|
}
|
||||||
13
src/cli/flags/teamschats.go
Normal file
13
src/cli/flags/teamschats.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DataChats = "chats"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddTeamsChatsDetailsAndRestoreFlags(cmd *cobra.Command) {
|
||||||
|
// TODO: add details flags
|
||||||
|
}
|
||||||
1
src/cli/flags/testdata/flags.go
vendored
1
src/cli/flags/testdata/flags.go
vendored
@ -21,6 +21,7 @@ var (
|
|||||||
ExchangeCategoryDataInput = []string{"email", "events", "contacts"}
|
ExchangeCategoryDataInput = []string{"email", "events", "contacts"}
|
||||||
SharepointCategoryDataInput = []string{"files", "lists", "pages"}
|
SharepointCategoryDataInput = []string{"files", "lists", "pages"}
|
||||||
GroupsCategoryDataInput = []string{"files", "lists", "pages", "messages"}
|
GroupsCategoryDataInput = []string{"files", "lists", "pages", "messages"}
|
||||||
|
TeamsChatsCategoryDataInput = []string{"chats"}
|
||||||
|
|
||||||
ChannelInput = []string{"channel1", "channel2"}
|
ChannelInput = []string{"channel1", "channel2"}
|
||||||
MessageInput = []string{"message1", "message2"}
|
MessageInput = []string{"message1", "message2"}
|
||||||
|
|||||||
25
src/cli/flags/testdata/teamschats.go
vendored
Normal file
25
src/cli/flags/testdata/teamschats.go
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package testdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PreparedTeamsChatsFlags() []string {
|
||||||
|
return []string{
|
||||||
|
// FIXME: populate when adding filters
|
||||||
|
// "--" + flags.ChatCreatedAfterFN, ChatCreatedAfterInput,
|
||||||
|
// "--" + flags.ChatCreatedBeforeFN, ChatCreatedBeforeInput,
|
||||||
|
// "--" + flags.ChatLastMessageAfterFN, ChatLastMessageAfterInput,
|
||||||
|
// "--" + flags.ChatLastMessageBeforeFN, ChatLastMessageBeforeInput,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertTeamsChatsFlags(t *testing.T, cmd *cobra.Command) {
|
||||||
|
// FIXME: populate when adding filters
|
||||||
|
// assert.Equal(t, ChatCreatedAfterInput, flags.ChatCreatedAfterFV)
|
||||||
|
// assert.Equal(t, ChatCreatedBeforeInput, flags.ChatCreatedBeforeFV)
|
||||||
|
// assert.Equal(t, ChatLastMessageAfterInput, flags.ChatLastMessageAfterFV)
|
||||||
|
// assert.Equal(t, ChatLastMessageBeforeInput, flags.ChatLastMessageBeforeFV)
|
||||||
|
}
|
||||||
101
src/cli/utils/teamschats.go
Normal file
101
src/cli/utils/teamschats.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TeamsChatsOpts struct {
|
||||||
|
Users []string
|
||||||
|
|
||||||
|
ExportCfg ExportCfgOpts
|
||||||
|
|
||||||
|
Populated flags.PopulatedFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
func TeamsChatsAllowedCategories() map[string]struct{} {
|
||||||
|
return map[string]struct{}{
|
||||||
|
flags.DataChats: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTeamsChatsCategories(sel *selectors.TeamsChatsBackup, cats []string) *selectors.TeamsChatsBackup {
|
||||||
|
if len(cats) == 0 {
|
||||||
|
sel.Include(sel.AllData())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range cats {
|
||||||
|
switch d {
|
||||||
|
case flags.DataChats:
|
||||||
|
sel.Include(sel.Chats(selectors.Any()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sel
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeTeamsChatsOpts(cmd *cobra.Command) TeamsChatsOpts {
|
||||||
|
return TeamsChatsOpts{
|
||||||
|
Users: flags.UserFV,
|
||||||
|
|
||||||
|
ExportCfg: makeExportCfgOpts(cmd),
|
||||||
|
|
||||||
|
// populated contains the list of flags that appear in the
|
||||||
|
// command, according to pflags. Use this to differentiate
|
||||||
|
// between an "empty" and a "missing" value.
|
||||||
|
Populated: flags.GetPopulatedFlags(cmd),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateTeamsChatsRestoreFlags checks common flags for correctness and interdependencies
|
||||||
|
func ValidateTeamsChatsRestoreFlags(backupID string, opts TeamsChatsOpts, isRestore bool) error {
|
||||||
|
if len(backupID) == 0 {
|
||||||
|
return clues.New("a backup ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore isn't currently supported
|
||||||
|
if isRestore {
|
||||||
|
return clues.New("restore not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTeamsChatsFilter adds the scope of the provided values to the selector's
|
||||||
|
// filter set
|
||||||
|
func AddTeamsChatsFilter(
|
||||||
|
sel *selectors.TeamsChatsRestore,
|
||||||
|
v string,
|
||||||
|
f func(string) []selectors.TeamsChatsScope,
|
||||||
|
) {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sel.Filter(f(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncludeTeamsChatsRestoreDataSelectors builds the common data-selector
|
||||||
|
// inclusions for teamschats commands.
|
||||||
|
func IncludeTeamsChatsRestoreDataSelectors(ctx context.Context, opts TeamsChatsOpts) *selectors.TeamsChatsRestore {
|
||||||
|
users := opts.Users
|
||||||
|
|
||||||
|
if len(opts.Users) == 0 {
|
||||||
|
users = selectors.Any()
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectors.NewTeamsChatsRestore(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterTeamsChatsRestoreInfoSelectors builds the common info-selector filters.
|
||||||
|
func FilterTeamsChatsRestoreInfoSelectors(
|
||||||
|
sel *selectors.TeamsChatsRestore,
|
||||||
|
opts TeamsChatsOpts,
|
||||||
|
) {
|
||||||
|
// TODO: populate when adding filters
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user