diff --git a/src/cli/backup/backup.go b/src/cli/backup/backup.go index 64022e295..efc8ea6c6 100644 --- a/src/cli/backup/backup.go +++ b/src/cli/backup/backup.go @@ -4,8 +4,8 @@ import ( "github.com/spf13/cobra" ) -var backupApplications = []func(parent *cobra.Command) *cobra.Command{ - addExchangeApp, +var backupCommands = []func(parent *cobra.Command) *cobra.Command{ + addExchangeCommands, } // AddCommands attaches all `corso backup * *` commands to the parent. @@ -14,7 +14,7 @@ func AddCommands(parent *cobra.Command) { backupCmd.AddCommand(createCmd) backupCmd.AddCommand(listCmd) - for _, addBackupTo := range backupApplications { + for _, addBackupTo := range backupCommands { addBackupTo(createCmd) addBackupTo(listCmd) } diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index 897aa54e1..5201f62e6 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -19,7 +19,7 @@ var ( ) // called by backup.go to map parent subcommands to provider-specific handling. -func addExchangeApp(parent *cobra.Command) *cobra.Command { +func addExchangeCommands(parent *cobra.Command) *cobra.Command { var ( c *cobra.Command fs *pflag.FlagSet @@ -44,7 +44,7 @@ var exchangeCreateCmd = &cobra.Command{ Args: cobra.NoArgs, } -// initializes a s3 repo. +// processes an exchange service backup. func createExchangeCmd(cmd *cobra.Command, args []string) error { ctx := cmd.Context() diff --git a/src/cli/backup/exchange_test.go b/src/cli/backup/exchange_test.go new file mode 100644 index 000000000..90fb37ad8 --- /dev/null +++ b/src/cli/backup/exchange_test.go @@ -0,0 +1,50 @@ +package backup + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + ctesting "github.com/alcionai/corso/internal/testing" +) + +type ExchangeSuite struct { + suite.Suite +} + +func TestExchangeSuite(t *testing.T) { + suite.Run(t, new(ExchangeSuite)) +} + +func (suite *ExchangeSuite) TestAddExchangeCommands() { + expectUse := exchangeServiceCommand + table := []struct { + name string + use string + expectUse string + expectShort string + expectRunE func(*cobra.Command, []string) error + }{ + {"create exchange", createCommand, expectUse, exchangeCreateCmd.Short, createExchangeCmd}, + {"list exchange", listCommand, expectUse, exchangeListCmd.Short, listExchangeCmd}, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + cmd := &cobra.Command{Use: test.use} + + c := addExchangeCommands(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) + ctesting.AreSameFunc(t, test.expectRunE, child.RunE) + }) + } +} diff --git a/src/cli/cli.go b/src/cli/cli.go index d0d4c709f..f2a47249f 100644 --- a/src/cli/cli.go +++ b/src/cli/cli.go @@ -21,7 +21,7 @@ var corsoCmd = &cobra.Command{ Use: "corso", Short: "Protect your Microsoft 365 data.", Long: `Reliable, secure, and efficient data protection for Microsoft 365.`, - Run: handleCorsoCmd, + RunE: handleCorsoCmd, } // the root-level flags @@ -45,12 +45,12 @@ func initConfig() { // Handler for flat calls to `corso`. // Produces the same output as `corso --help`. -func handleCorsoCmd(cmd *cobra.Command, args []string) { +func handleCorsoCmd(cmd *cobra.Command, args []string) error { if version { fmt.Printf("Corso\nversion:\tpre-alpha\n") - } else { - cmd.Help() + return nil } + return cmd.Help() } // Handle builds and executes the cli processor. diff --git a/src/cli/cli_test.go b/src/cli/cli_test.go new file mode 100644 index 000000000..ed1641d43 --- /dev/null +++ b/src/cli/cli_test.go @@ -0,0 +1,40 @@ +package cli_test + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/alcionai/corso/cli/backup" + "github.com/alcionai/corso/cli/repo" + "github.com/alcionai/corso/cli/restore" +) + +type CliSuite struct { + suite.Suite +} + +type CLISuite struct { + suite.Suite +} + +func TestCLISuite(t *testing.T) { + suite.Run(t, new(CLISuite)) +} + +func (suite *CLISuite) TestAddCommands_noPanics() { + t := suite.T() + + var test = &cobra.Command{ + Use: "test", + Short: "Protect your Microsoft 365 data.", + Long: `Reliable, secure, and efficient data protection for Microsoft 365.`, + RunE: func(c *cobra.Command, args []string) error { return nil }, + } + + assert.NotPanics(t, func() { repo.AddCommands(test) }) + assert.NotPanics(t, func() { backup.AddCommands(test) }) + assert.NotPanics(t, func() { restore.AddCommands(test) }) +} diff --git a/src/cli/repo/s3_test.go b/src/cli/repo/s3_test.go new file mode 100644 index 000000000..be7341aad --- /dev/null +++ b/src/cli/repo/s3_test.go @@ -0,0 +1,50 @@ +package repo + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + ctesting "github.com/alcionai/corso/internal/testing" +) + +type S3Suite struct { + suite.Suite +} + +func TestS3Suite(t *testing.T) { + suite.Run(t, new(S3Suite)) +} + +func (suite *S3Suite) TestAddS3Commands() { + expectUse := s3ProviderCommand + table := []struct { + name string + use string + expectUse string + expectShort string + expectRunE func(*cobra.Command, []string) error + }{ + {"init s3", initCommand, expectUse, s3InitCmd.Short, initS3Cmd}, + {"connect s3", connectCommand, expectUse, s3ConnectCmd.Short, connectS3Cmd}, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + cmd := &cobra.Command{Use: test.use} + + c := addS3Commands(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) + ctesting.AreSameFunc(t, test.expectRunE, child.RunE) + }) + } +} diff --git a/src/cli/restore/exchange.go b/src/cli/restore/exchange.go index 206b8e863..808812f5f 100644 --- a/src/cli/restore/exchange.go +++ b/src/cli/restore/exchange.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/alcionai/corso/cli/config" "github.com/alcionai/corso/cli/utils" @@ -21,29 +22,36 @@ var ( ) // called by restore.go to map parent subcommands to provider-specific handling. -func addExchangeApp(parent *cobra.Command) *cobra.Command { - parent.AddCommand(exchangeCmd) +func addExchangeCommands(parent *cobra.Command) *cobra.Command { + var ( + c *cobra.Command + fs *pflag.FlagSet + ) - fs := exchangeCmd.Flags() - fs.StringVar(&folder, "folder", "", "Name of the mail folder being restored") - fs.StringVar(&mail, "mail", "", "ID of the mail message being restored") - fs.StringVar(&restorePointID, "restore-point", "", "ID of the backup restore point") - exchangeCmd.MarkFlagRequired("restore-point") - fs.StringVar(&user, "user", "", "ID of the user whose exchange data will get restored") - - return exchangeCmd + switch parent.Use { + case restoreCommand: + c, fs = utils.AddCommand(parent, exchangeRestoreCmd) + fs.StringVar(&folder, "folder", "", "Name of the mail folder being restored") + fs.StringVar(&mail, "mail", "", "ID of the mail message being restored") + fs.StringVar(&restorePointID, "restore-point", "", "ID of the backup restore point") + c.MarkFlagRequired("restore-point") + fs.StringVar(&user, "user", "", "ID of the user whose exchange data will get restored") + } + return c } -// `corso restore create exchange [...]` -var exchangeCmd = &cobra.Command{ - Use: "exchange", +const exchangeServiceCommand = "exchange" + +// `corso restore exchange [...]` +var exchangeRestoreCmd = &cobra.Command{ + Use: exchangeServiceCommand, Short: "Restore M365 Exchange service data", - RunE: createExchangeCmd, + RunE: restoreExchangeCmd, Args: cobra.NoArgs, } -// initializes a s3 repo. -func createExchangeCmd(cmd *cobra.Command, args []string) error { +// processes an exchange service restore. +func restoreExchangeCmd(cmd *cobra.Command, args []string) error { ctx := cmd.Context() if utils.HasNoFlagsAndShownHelp(cmd) { diff --git a/src/cli/restore/exchange_test.go b/src/cli/restore/exchange_test.go index 15e399676..5a1a49878 100644 --- a/src/cli/restore/exchange_test.go +++ b/src/cli/restore/exchange_test.go @@ -3,19 +3,23 @@ package restore import ( "testing" + "github.com/spf13/cobra" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + + ctesting "github.com/alcionai/corso/internal/testing" ) -type RestoreSuite struct { +type ExchangeSuite struct { suite.Suite } -func TestRestoreSuite(t *testing.T) { - suite.Run(t, new(RestoreSuite)) +func TestExchangeSuite(t *testing.T) { + suite.Run(t, new(ExchangeSuite)) } -func (suite *RestoreSuite) TestValidateRestoreFlags() { +func (suite *ExchangeSuite) TestValidateRestoreFlags() { table := []struct { name string u, f, m, rpid string @@ -39,3 +43,32 @@ func (suite *RestoreSuite) TestValidateRestoreFlags() { }) } } + +func (suite *ExchangeSuite) TestAddExchangeCommands() { + expectUse := exchangeServiceCommand + table := []struct { + name string + use string + expectUse string + expectShort string + expectRunE func(*cobra.Command, []string) error + }{ + {"restore exchange", restoreCommand, expectUse, exchangeRestoreCmd.Short, restoreExchangeCmd}, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + cmd := &cobra.Command{Use: test.use} + + c := addExchangeCommands(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) + ctesting.AreSameFunc(t, test.expectRunE, child.RunE) + }) + } +} diff --git a/src/cli/restore/restore.go b/src/cli/restore/restore.go index 3c5364d95..6d5b6d583 100644 --- a/src/cli/restore/restore.go +++ b/src/cli/restore/restore.go @@ -4,23 +4,25 @@ import ( "github.com/spf13/cobra" ) -var restoreApplications = []func(parent *cobra.Command) *cobra.Command{ - addExchangeApp, +var restoreCommands = []func(parent *cobra.Command) *cobra.Command{ + addExchangeCommands, } // AddCommands attaches all `corso restore * *` commands to the parent. func AddCommands(parent *cobra.Command) { parent.AddCommand(restoreCmd) - for _, addRestoreTo := range restoreApplications { + for _, addRestoreTo := range restoreCommands { addRestoreTo(restoreCmd) } } +const restoreCommand = "restore" + // The restore category of commands. // `corso restore [] [...]` var restoreCmd = &cobra.Command{ - Use: "restore", + Use: restoreCommand, Short: "Restore your service data", Long: `Restore the data stored in one of your M365 services.`, Run: handleRestoreCmd, diff --git a/src/internal/testing/testing.go b/src/internal/testing/testing.go new file mode 100644 index 000000000..390ee9e52 --- /dev/null +++ b/src/internal/testing/testing.go @@ -0,0 +1,26 @@ +package testing + +import ( + "reflect" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" +) + +// AreSameFunc asserts whether the two funcs are the same func. +func AreSameFunc(t *testing.T, expect, have any) { + assert.Equal( + t, + runtime.FuncForPC( + reflect. + ValueOf(expect). + Pointer(), + ).Name(), + runtime.FuncForPC( + reflect. + ValueOf(have). + Pointer(), + ).Name(), + ) +}