Skip to content
This repository has been archived by the owner on Oct 11, 2024. It is now read-only.

Commit

Permalink
add chats export support to the CLI (#5128)
Browse files Browse the repository at this point in the history
more boilerplate adaptation. commands are marked as `pre-release` and
hidden until release.
  • Loading branch information
ryanfkeepers authored Feb 15, 2024
1 parent d7ca5e4 commit 36fd6b9
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 244 deletions.
8 changes: 4 additions & 4 deletions src/cli/backup/teamschats.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func teamschatsCreateCmd() *cobra.Command {
return &cobra.Command{
Use: teamschatsServiceCommand,
Aliases: []string{teamsServiceCommand},
Short: "Backup M365 Chats service data",
Short: "Backup M365 Chats data",
RunE: createTeamsChatsCmd,
Args: cobra.NoArgs,
}
Expand Down Expand Up @@ -170,7 +170,7 @@ func createTeamsChatsCmd(cmd *cobra.Command, args []string) error {
func teamschatsListCmd() *cobra.Command {
return &cobra.Command{
Use: teamschatsServiceCommand,
Short: "List the history of M365 TeamsChats service backups",
Short: "List the history of M365 Chats backups",
RunE: listTeamsChatsCmd,
Args: cobra.NoArgs,
}
Expand All @@ -189,7 +189,7 @@ func listTeamsChatsCmd(cmd *cobra.Command, args []string) error {
func teamschatsDetailsCmd() *cobra.Command {
return &cobra.Command{
Use: teamschatsServiceCommand,
Short: "Shows the details of a M365 TeamsChats service backup",
Short: "Shows the details of a M365 Chats backup",
RunE: detailsTeamsChatsCmd,
Args: cobra.NoArgs,
}
Expand Down Expand Up @@ -237,7 +237,7 @@ func runDetailsTeamsChatsCmd(cmd *cobra.Command) error {
func teamschatsDeleteCmd() *cobra.Command {
return &cobra.Command{
Use: teamschatsServiceCommand,
Short: "Delete backed-up M365 TeamsChats service data",
Short: "Delete backed-up M365 Chats data",
RunE: deleteTeamsChatsCmd,
Args: cobra.NoArgs,
}
Expand Down
1 change: 1 addition & 0 deletions src/cli/export/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var exportCommands = []func(cmd *cobra.Command) *cobra.Command{
addSharePointCommands,
addGroupsCommands,
addExchangeCommands,
addTeamsChatsCommands,
}

var defaultAcceptedFormatTypes = []string{string(control.DefaultFormat)}
Expand Down
101 changes: 101 additions & 0 deletions src/cli/export/teamschats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package export

import (
"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/alcionai/corso/src/cli/flags"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/pkg/control"
)

// called by export.go to map subcommands to provider-specific handling.
func addTeamsChatsCommands(cmd *cobra.Command) *cobra.Command {
var c *cobra.Command

switch cmd.Use {
case exportCommand:
c, _ = utils.AddCommand(cmd, teamschatsExportCmd(), utils.MarkPreviewCommand())

c.Use = c.Use + " " + teamschatsServiceCommandUseSuffix

flags.AddBackupIDFlag(c, true)
flags.AddTeamsChatsDetailsAndRestoreFlags(c)
flags.AddExportConfigFlags(c)
flags.AddFailFastFlag(c)
}

return c
}

const (
teamschatsServiceCommand = "chats"
teamschatsServiceCommandUseSuffix = "<destination> --backup <backupId>"

//nolint:lll
teamschatsServiceCommandExportExamples = `# Export a specific chat from the last backup (1234abcd...) to /my-exports
corso export chats my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd --chat 98765abcdef
# Export all of Bob's chats to the current directory
corso export chats . --backup 1234abcd-12ab-cd34-56de-1234abcd \
--chat '*'
# Export all chats that were created before 2020 to /my-exports
corso export chats my-exports --backup 1234abcd-12ab-cd34-56de-1234abcd
--chat-created-before 2020-01-01T00:00:00`
)

// `corso export chats [<flag>...] <destination>`
func teamschatsExportCmd() *cobra.Command {
return &cobra.Command{
Use: teamschatsServiceCommand,
Aliases: []string{teamsServiceCommand},
Short: "Export M365 Chats data",
RunE: exportTeamsChatsCmd,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("missing export destination")
}

return nil
},
Example: teamschatsServiceCommandExportExamples,
}
}

// processes an teamschats service export.
func exportTeamsChatsCmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

if utils.HasNoFlagsAndShownHelp(cmd) {
return nil
}

opts := utils.MakeTeamsChatsOpts(cmd)

if flags.RunModeFV == flags.RunModeFlagTest {
return nil
}

if err := utils.ValidateTeamsChatsRestoreFlags(flags.BackupIDFV, opts, false); err != nil {
return err
}

sel := utils.IncludeTeamsChatsRestoreDataSelectors(ctx, opts)
utils.FilterTeamsChatsRestoreInfoSelectors(sel, opts)

acceptedTeamsChatsFormatTypes := []string{
string(control.DefaultFormat),
string(control.JSONFormat),
}

return runExport(
ctx,
cmd,
args,
opts.ExportCfg,
sel.Selector,
flags.BackupIDFV,
"Chats",
acceptedTeamsChatsFormatTypes)
}
78 changes: 78 additions & 0 deletions src/cli/export/teamschats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package export

import (
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"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"
)

type TeamsChatsUnitSuite struct {
tester.Suite
}

func TestTeamsChatsUnitSuite(t *testing.T) {
suite.Run(t, &TeamsChatsUnitSuite{Suite: tester.NewUnitSuite(t)})
}

func (suite *TeamsChatsUnitSuite) TestAddTeamsChatsCommands() {
expectUse := teamschatsServiceCommand + " " + teamschatsServiceCommandUseSuffix

table := []struct {
name string
use string
expectUse string
expectShort string
expectRunE func(*cobra.Command, []string) error
}{
{"export teamschats", exportCommand, expectUse, teamschatsExportCmd().Short, exportTeamsChatsCmd},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
parent := &cobra.Command{Use: exportCommand}

cmd := cliTD.SetUpCmdHasFlags(
t,
parent,
addTeamsChatsCommands,
[]cliTD.UseCobraCommandFn{
flags.AddAllProviderFlags,
flags.AddAllStorageFlags,
},
flagsTD.WithFlags(
teamschatsServiceCommand,
[]string{
flagsTD.RestoreDestination,
"--" + flags.RunModeFN, flags.RunModeFlagTest,
"--" + flags.BackupFN, flagsTD.BackupInput,
"--" + flags.FormatFN, flagsTD.FormatType,
"--" + flags.ArchiveFN,
},
flagsTD.PreparedProviderFlags(),
flagsTD.PreparedStorageFlags()))

cliTD.CheckCmdChild(
t,
parent,
3,
test.expectUse,
test.expectShort,
test.expectRunE)

opts := utils.MakeTeamsChatsOpts(cmd)

assert.Equal(t, flagsTD.BackupInput, flags.BackupIDFV)
assert.Equal(t, flagsTD.Archive, opts.ExportCfg.Archive)
assert.Equal(t, flagsTD.FormatType, opts.ExportCfg.Format)
flagsTD.AssertStorageFlags(t, cmd)
})
}
}
117 changes: 117 additions & 0 deletions src/internal/operations/test/m365/backup_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,120 @@ func RunMergeBaseGroupsUpdate(
"cached items")
})
}

func RunBasicBackupTest(
suite tester.Suite,
sel selectors.Selector,
) {
t := suite.T()

ctx, flush := tester.NewContext(t)
defer flush()

var (
mb = evmock.NewBus()
counter = count.New()
opts = control.DefaultOptions()
whatSet = deeTD.CategoryFromRepoRef
)

bo, bod := PrepNewTestBackupOp(t, ctx, mb, sel, opts, version.Backup, counter)
defer bod.Close(t, ctx)

reasons, err := bod.Sel.Reasons(bod.Acct.ID(), false)
require.NoError(t, err, clues.ToCore(err))

RunAndCheckBackup(t, ctx, &bo, mb, false)

for _, reason := range reasons {
CheckBackupIsInManifests(
t,
ctx,
bod.KW,
bod.SW,
&bo,
bod.Sel,
bod.Sel.ID(),
reason.Category())
}

_, expectDeets := deeTD.GetDeetsInBackup(
t,
ctx,
bo.Results.BackupID,
bod.Acct.ID(),
bod.Sel.ID(),
sel.PathService(),
whatSet,
bod.KMS,
bod.SSS)
deeTD.CheckBackupDetails(
t,
ctx,
bo.Results.BackupID,
whatSet,
bod.KMS,
bod.SSS,
expectDeets,
false)

// Basic, happy path incremental test. No changes are dictated or expected.
// This only tests that an incremental backup is runnable at all, and that it
// produces fewer results than the last backup.
//
// Incremental testing for conversations is limited because of API restrictions.
// Since graph doesn't provide us a way to programmatically delete conversations,
// or create new conversations without a delegated token, we can't do incremental
// testing with newly added items.
incMB := evmock.NewBus()
incBO := NewTestBackupOp(
t,
ctx,
bod,
incMB,
opts,
count.New())

RunAndCheckBackup(t, ctx, &incBO, incMB, true)

for _, reason := range reasons {
CheckBackupIsInManifests(
t,
ctx,
bod.KW,
bod.SW,
&incBO,
bod.Sel,
bod.Sel.ID(),
reason.Category())
}

_, expectDeets = deeTD.GetDeetsInBackup(
t,
ctx,
incBO.Results.BackupID,
bod.Acct.ID(),
bod.Sel.ID(),
bod.Sel.PathService(),
whatSet,
bod.KMS,
bod.SSS)
deeTD.CheckBackupDetails(
t,
ctx,
incBO.Results.BackupID,
whatSet,
bod.KMS,
bod.SSS,
expectDeets,
false)

assert.NotZero(
t,
incBO.Results.Counts[string(count.PersistedCachedFiles)],
"cached items")
assert.Greater(t, bo.Results.ItemsWritten, incBO.Results.ItemsWritten, "incremental items written")
assert.Greater(t, bo.Results.BytesRead, incBO.Results.BytesRead, "incremental bytes read")
assert.Greater(t, bo.Results.BytesUploaded, incBO.Results.BytesUploaded, "incremental bytes uploaded")
assert.Equal(t, 1, incMB.TimesCalled[events.BackupEnd], "incremental backup-end events")
}
Loading

0 comments on commit 36fd6b9

Please sign in to comment.