From 0db50460b1c88f99b0fb05b87d525a5bd91d140d Mon Sep 17 00:00:00 2001 From: phm07 <22707808+phm07@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:15:02 +0200 Subject: [PATCH] test(context): add unit tests (#755) This PR adds tests to the `context` subcommands using the new configuration system implemented in #736 --- internal/cmd/context/active.go | 2 +- internal/cmd/context/active_test.go | 92 +++++++++++++ internal/cmd/context/context.go | 10 +- internal/cmd/context/create.go | 11 +- internal/cmd/context/create_test.go | 121 ++++++++++++++++++ internal/cmd/context/delete.go | 3 +- internal/cmd/context/delete_test.go | 85 ++++++++++++ internal/cmd/context/list.go | 23 ++-- internal/cmd/context/list_test.go | 119 +++++++++++++++++ internal/cmd/context/use.go | 3 +- internal/cmd/context/use_test.go | 90 +++++++++++++ internal/state/state.go | 8 ++ internal/testutil/fixture.go | 9 ++ internal/testutil/terminal/terminal.go | 26 ++++ .../testutil/terminal/zz_terminal_mock.go | 63 +++++++++ 15 files changed, 639 insertions(+), 26 deletions(-) create mode 100644 internal/cmd/context/active_test.go create mode 100644 internal/cmd/context/create_test.go create mode 100644 internal/cmd/context/delete_test.go create mode 100644 internal/cmd/context/list_test.go create mode 100644 internal/cmd/context/use_test.go create mode 100644 internal/testutil/terminal/terminal.go create mode 100644 internal/testutil/terminal/zz_terminal_mock.go diff --git a/internal/cmd/context/active.go b/internal/cmd/context/active.go index 13a3fa3c..45f34f5e 100644 --- a/internal/cmd/context/active.go +++ b/internal/cmd/context/active.go @@ -10,7 +10,7 @@ import ( "github.com/hetznercloud/cli/internal/state" ) -func newActiveCommand(s state.State) *cobra.Command { +func NewActiveCommand(s state.State) *cobra.Command { cmd := &cobra.Command{ Use: "active", Short: "Show active context", diff --git a/internal/cmd/context/active_test.go b/internal/cmd/context/active_test.go new file mode 100644 index 00000000..7fc06c6d --- /dev/null +++ b/internal/cmd/context/active_test.go @@ -0,0 +1,92 @@ +package context_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/hetznercloud/cli/internal/cmd/context" + "github.com/hetznercloud/cli/internal/testutil" +) + +func TestActive(t *testing.T) { + + testConfig := ` +active_context = "my-context" + +[[contexts]] +name = "my-context" +token = "super secret token" +` + + type testCase struct { + name string + args []string + config string + err string + expOut string + expErr string + preRun func() + postRun func() + } + + testCases := []testCase{ + { + name: "no arguments", + args: []string{}, + config: testConfig, + expOut: "my-context\n", + }, + { + name: "no config", + args: []string{}, + }, + { + name: "from env", + args: []string{}, + config: testConfig, + preRun: func() { + _ = os.Setenv("HCLOUD_CONTEXT", "abcdef") + }, + postRun: func() { + _ = os.Unsetenv("HCLOUD_CONTEXT") + }, + // 'abcdef' does not exist, so there is nothing printed to stdout. + // The warning 'active context "abcdef" not found' should be printed to stderr during config loading, which + // is before stderr is captured. + }, + { + name: "invalid config", + args: []string{}, + config: `active_context = "invalid-context-name"`, + // if there is no context with the name of the active_context, there should be no output. See above + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + + if tt.preRun != nil { + tt.preRun() + } + if tt.postRun != nil { + defer tt.postRun() + } + + fx := testutil.NewFixtureWithConfigFile(t, []byte(tt.config)) + defer fx.Finish() + + cmd := context.NewActiveCommand(fx.State()) + out, errOut, err := fx.Run(cmd, tt.args) + + if tt.err == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tt.err) + } + assert.Equal(t, tt.expErr, errOut) + assert.Equal(t, tt.expOut, out) + }) + } +} diff --git a/internal/cmd/context/context.go b/internal/cmd/context/context.go index f5c020b4..964a6a05 100644 --- a/internal/cmd/context/context.go +++ b/internal/cmd/context/context.go @@ -16,11 +16,11 @@ func NewCommand(s state.State) *cobra.Command { DisableFlagsInUseLine: true, } cmd.AddCommand( - newCreateCommand(s), - newActiveCommand(s), - newUseCommand(s), - newDeleteCommand(s), - newListCommand(s), + NewCreateCommand(s), + NewActiveCommand(s), + NewUseCommand(s), + NewDeleteCommand(s), + NewListCommand(s), ) return cmd } diff --git a/internal/cmd/context/create.go b/internal/cmd/context/create.go index cfc25836..7f2048cc 100644 --- a/internal/cmd/context/create.go +++ b/internal/cmd/context/create.go @@ -9,21 +9,20 @@ import ( "syscall" "github.com/spf13/cobra" - "golang.org/x/term" "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/cli/internal/state/config" - "github.com/hetznercloud/cli/internal/ui" ) -func newCreateCommand(s state.State) *cobra.Command { +func NewCreateCommand(s state.State) *cobra.Command { cmd := &cobra.Command{ Use: "create ", Short: "Create a new context", Args: util.Validate, TraverseChildren: true, DisableFlagsInUseLine: true, + SilenceUsage: true, RunE: state.Wrap(s, runCreate), } return cmd @@ -31,7 +30,7 @@ func newCreateCommand(s state.State) *cobra.Command { func runCreate(s state.State, cmd *cobra.Command, args []string) error { cfg := s.Config() - if !ui.StdoutIsTerminal() { + if !s.Terminal().StdoutIsTerminal() { return errors.New("context create is an interactive command") } @@ -63,8 +62,8 @@ func runCreate(s state.State, cmd *cobra.Command, args []string) error { for { cmd.Printf("Token: ") // Conversion needed for compilation on Windows - // vvv - btoken, err := term.ReadPassword(int(syscall.Stdin)) + // vvv + btoken, err := s.Terminal().ReadPassword(int(syscall.Stdin)) cmd.Print("\n") if err != nil { return err diff --git a/internal/cmd/context/create_test.go b/internal/cmd/context/create_test.go new file mode 100644 index 00000000..53003b93 --- /dev/null +++ b/internal/cmd/context/create_test.go @@ -0,0 +1,121 @@ +package context_test + +import ( + "io" + "syscall" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/hetznercloud/cli/internal/cmd/context" + "github.com/hetznercloud/cli/internal/testutil" +) + +func TestCreate(t *testing.T) { + + testConfig := ` +active_context = "my-context" + +[[contexts]] +name = "my-context" +token = "super secret token" +` + + type testCase struct { + name string + args []string + config string + isTerm bool + token string + err string + expErr string + expOut string + } + + testCases := []testCase{ + { + name: "new context", + args: []string{"new-context"}, + isTerm: true, + config: testConfig, + token: "q4acIB6pq2CwsPqF+dNR2B6NTrv4yxmsspvDC1a02OqfMQeCz7nOk4A3pcJha8ix", + expOut: `Token: +active_context = "new-context" + +[[contexts]] + name = "my-context" + token = "super secret token" + +[[contexts]] + name = "new-context" + token = "q4acIB6pq2CwsPqF+dNR2B6NTrv4yxmsspvDC1a02OqfMQeCz7nOk4A3pcJha8ix" +Context new-context created and activated +`, + }, + { + name: "not terminal", + args: []string{"new-context"}, + isTerm: false, + config: testConfig, + err: "context create is an interactive command", + expErr: "Error: context create is an interactive command\n", + }, + { + name: "existing context", + args: []string{"my-context"}, + isTerm: true, + config: testConfig, + token: "q4acIB6pq2CwsPqF+dNR2B6NTrv4yxmsspvDC1a02OqfMQeCz7nOk4A3pcJha8ix", + err: "name already used", + expErr: "Error: name already used\n", + }, + { + name: "invalid name", + args: []string{""}, + isTerm: true, + config: testConfig, + err: "invalid name", + expErr: "Error: invalid name\n", + }, + { + name: "token too short", + args: []string{"new-context"}, + isTerm: true, + config: testConfig, + token: "abc", + err: "EOF", + expErr: "Error: EOF\n", + expOut: "Token: \nEntered token is invalid (must be exactly 64 characters long)\nToken: \n", + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + fx := testutil.NewFixtureWithConfigFile(t, []byte(tt.config)) + defer fx.Finish() + + fx.Terminal.EXPECT().StdoutIsTerminal().Return(tt.isTerm) + + isFirstCall := true + fx.Terminal.EXPECT().ReadPassword(int(syscall.Stdin)).DoAndReturn(func(_ int) ([]byte, error) { + if isFirstCall { + isFirstCall = false + return []byte(tt.token), nil + } + // return EOF after first call to prevent infinite loop + return nil, io.EOF + }).AnyTimes() + + cmd := context.NewCreateCommand(fx.State()) + out, errOut, err := fx.Run(cmd, tt.args) + + if tt.err == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tt.err) + } + assert.Equal(t, tt.expErr, errOut) + assert.Equal(t, tt.expOut, out) + }) + } +} diff --git a/internal/cmd/context/delete.go b/internal/cmd/context/delete.go index 0ee96a2a..4aabf479 100644 --- a/internal/cmd/context/delete.go +++ b/internal/cmd/context/delete.go @@ -12,7 +12,7 @@ import ( "github.com/hetznercloud/cli/internal/state/config" ) -func newDeleteCommand(s state.State) *cobra.Command { +func NewDeleteCommand(s state.State) *cobra.Command { cmd := &cobra.Command{ Use: "delete ", Short: "Delete a context", @@ -20,6 +20,7 @@ func newDeleteCommand(s state.State) *cobra.Command { ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidates(config.ContextNames(s.Config())...)), TraverseChildren: true, DisableFlagsInUseLine: true, + SilenceUsage: true, RunE: state.Wrap(s, runDelete), } return cmd diff --git a/internal/cmd/context/delete_test.go b/internal/cmd/context/delete_test.go new file mode 100644 index 00000000..1b9ddca5 --- /dev/null +++ b/internal/cmd/context/delete_test.go @@ -0,0 +1,85 @@ +package context_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/hetznercloud/cli/internal/cmd/context" + "github.com/hetznercloud/cli/internal/testutil" +) + +func TestDelete(t *testing.T) { + + testConfig := ` +active_context = "my-context" + +[[contexts]] +name = "my-context" +token = "super secret token" + +[[contexts]] +name = "my-other-context" +token = "super secret token" +` + + type testCase struct { + name string + args []string + config string + err string + expErr string + expOut string + } + + testCases := []testCase{ + { + name: "delete active context", + args: []string{"my-context"}, + config: testConfig, + expErr: "Warning: You are deleting the currently active context. Please select a new active context.\n", + expOut: `active_context = "" + +[[contexts]] + name = "my-other-context" + token = "super secret token" +`, + }, + { + name: "delete inactive context", + args: []string{"my-other-context"}, + config: testConfig, + expOut: `active_context = "my-context" + +[[contexts]] + name = "my-context" + token = "super secret token" +`, + }, + { + name: "delete non-existing context", + args: []string{"non-existing-context"}, + config: testConfig, + err: "context not found: non-existing-context", + expErr: "Error: context not found: non-existing-context\n", + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + fx := testutil.NewFixtureWithConfigFile(t, []byte(tt.config)) + defer fx.Finish() + + cmd := context.NewDeleteCommand(fx.State()) + out, errOut, err := fx.Run(cmd, tt.args) + + if tt.err == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tt.err) + } + assert.Equal(t, tt.expErr, errOut) + assert.Equal(t, tt.expOut, out) + }) + } +} diff --git a/internal/cmd/context/list.go b/internal/cmd/context/list.go index 7b325a5d..62dbab9a 100644 --- a/internal/cmd/context/list.go +++ b/internal/cmd/context/list.go @@ -8,34 +8,27 @@ import ( "github.com/hetznercloud/cli/internal/state" ) -var listTableOutput *output.Table - type ContextPresentation struct { Name string Token string Active string } -func init() { - listTableOutput = output.NewTable(). - AddAllowedFields(ContextPresentation{}). - RemoveAllowedField("token") -} - -func newListCommand(s state.State) *cobra.Command { +func NewListCommand(s state.State) *cobra.Command { + cols := newListOutputTable().Columns() cmd := &cobra.Command{ Use: "list [options]", Short: "List contexts", Long: util.ListLongDescription( "Displays a list of contexts.", - listTableOutput.Columns(), + cols, ), Args: util.Validate, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: state.Wrap(s, runList), } - output.AddFlag(cmd, output.OptionNoHeader(), output.OptionColumns(listTableOutput.Columns())) + output.AddFlag(cmd, output.OptionNoHeader(), output.OptionColumns(cols)) return cmd } @@ -47,7 +40,7 @@ func runList(s state.State, cmd *cobra.Command, _ []string) error { cols = outOpts["columns"] } - tw := listTableOutput + tw := newListOutputTable() if err := tw.ValidateColumns(cols); err != nil { return err } @@ -71,3 +64,9 @@ func runList(s state.State, cmd *cobra.Command, _ []string) error { tw.Flush() return nil } + +func newListOutputTable() *output.Table { + return output.NewTable(). + AddAllowedFields(ContextPresentation{}). + RemoveAllowedField("token") +} diff --git a/internal/cmd/context/list_test.go b/internal/cmd/context/list_test.go new file mode 100644 index 00000000..e7635d24 --- /dev/null +++ b/internal/cmd/context/list_test.go @@ -0,0 +1,119 @@ +package context_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/hetznercloud/cli/internal/cmd/context" + "github.com/hetznercloud/cli/internal/testutil" +) + +func TestList(t *testing.T) { + + testConfig := ` +active_context = "my-context" + +[[contexts]] +name = "my-context" +token = "super secret token" + +[[contexts]] +name = "my-other-context" +token = "another super secret token" + +[[contexts]] +name = "my-third-context" +token = "yet another super secret token" +` + + type testCase struct { + name string + args []string + config string + err string + expOut string + expErr string + preRun func() + postRun func() + } + + testCases := []testCase{ + { + name: "default", + args: []string{}, + config: testConfig, + expOut: `ACTIVE NAME +* my-context + my-other-context + my-third-context +`, + }, + { + name: "no config", + args: []string{}, + expOut: "ACTIVE NAME\n", + }, + { + name: "no header", + args: []string{"-o=noheader"}, + config: testConfig, + expOut: `* my-context + my-other-context + my-third-context +`, + }, + { + name: "no header only name", + args: []string{"-o=noheader", "-o=columns=name"}, + config: testConfig, + expOut: `my-context +my-other-context +my-third-context +`, + }, + { + name: "different context", + args: []string{}, + config: testConfig, + preRun: func() { + _ = os.Setenv("HCLOUD_CONTEXT", "my-other-context") + }, + postRun: func() { + _ = os.Unsetenv("HCLOUD_CONTEXT") + }, + expOut: `ACTIVE NAME + my-context +* my-other-context + my-third-context +`, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + + if tt.preRun != nil { + tt.preRun() + } + if tt.postRun != nil { + defer tt.postRun() + } + + fx := testutil.NewFixtureWithConfigFile(t, []byte(tt.config)) + defer fx.Finish() + + cmd := context.NewListCommand(fx.State()) + out, errOut, err := fx.Run(cmd, tt.args) + + if tt.err == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tt.err) + } + assert.Equal(t, tt.expErr, errOut) + assert.Equal(t, tt.expOut, out) + }) + } +} diff --git a/internal/cmd/context/use.go b/internal/cmd/context/use.go index f0a26e41..4a765349 100644 --- a/internal/cmd/context/use.go +++ b/internal/cmd/context/use.go @@ -12,7 +12,7 @@ import ( "github.com/hetznercloud/cli/internal/state/config" ) -func newUseCommand(s state.State) *cobra.Command { +func NewUseCommand(s state.State) *cobra.Command { cmd := &cobra.Command{ Use: "use ", Short: "Use a context", @@ -20,6 +20,7 @@ func newUseCommand(s state.State) *cobra.Command { ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidates(config.ContextNames(s.Config())...)), TraverseChildren: true, DisableFlagsInUseLine: true, + SilenceUsage: true, RunE: state.Wrap(s, runUse), } return cmd diff --git a/internal/cmd/context/use_test.go b/internal/cmd/context/use_test.go new file mode 100644 index 00000000..9de900d6 --- /dev/null +++ b/internal/cmd/context/use_test.go @@ -0,0 +1,90 @@ +package context_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/hetznercloud/cli/internal/cmd/context" + "github.com/hetznercloud/cli/internal/testutil" +) + +func TestUse(t *testing.T) { + + testConfig := `active_context = "my-context" + +[[contexts]] + name = "my-context" + token = "super secret token" + +[[contexts]] + name = "my-other-context" + token = "another super secret token" + +[[contexts]] + name = "my-third-context" + token = "yet another super secret token" +` + + type testCase struct { + name string + args []string + config string + err string + expErr string + expOut string + } + + testCases := []testCase{ + { + name: "use different context", + args: []string{"my-other-context"}, + config: testConfig, + expOut: `active_context = "my-other-context" + +[[contexts]] + name = "my-context" + token = "super secret token" + +[[contexts]] + name = "my-other-context" + token = "another super secret token" + +[[contexts]] + name = "my-third-context" + token = "yet another super secret token" +`, + }, + { + name: "use active context", + args: []string{"my-context"}, + config: testConfig, + expOut: testConfig, + }, + { + name: "use non-existing context", + args: []string{"non-existing-context"}, + config: testConfig, + err: "context not found: non-existing-context", + expErr: "Error: context not found: non-existing-context\n", + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + fx := testutil.NewFixtureWithConfigFile(t, []byte(tt.config)) + defer fx.Finish() + + cmd := context.NewUseCommand(fx.State()) + out, errOut, err := fx.Run(cmd, tt.args) + + if tt.err == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tt.err) + } + assert.Equal(t, tt.expErr, errOut) + assert.Equal(t, tt.expOut, out) + }) + } +} diff --git a/internal/state/state.go b/internal/state/state.go index 964f9461..b2918b82 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -6,6 +6,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state/config" + "github.com/hetznercloud/cli/internal/testutil/terminal" "github.com/hetznercloud/cli/internal/version" "github.com/hetznercloud/hcloud-go/v2/hcloud" ) @@ -18,6 +19,7 @@ type State interface { Client() hcapi2.Client Config() config.Config + Terminal() terminal.Terminal } type state struct { @@ -25,12 +27,14 @@ type state struct { client hcapi2.Client config config.Config + term terminal.Terminal } func New(cfg config.Config) (State, error) { s := &state{ Context: context.Background(), config: cfg, + term: terminal.DefaultTerminal{}, } s.client = s.newClient() @@ -45,6 +49,10 @@ func (c *state) Config() config.Config { return c.config } +func (c *state) Terminal() terminal.Terminal { + return c.term +} + func (c *state) newClient() hcapi2.Client { opts := []hcloud.ClientOption{ hcloud.WithToken(config.OptionToken.Get(c.config)), diff --git a/internal/testutil/fixture.go b/internal/testutil/fixture.go index 04a5813d..ebfaa546 100644 --- a/internal/testutil/fixture.go +++ b/internal/testutil/fixture.go @@ -13,6 +13,7 @@ import ( hcapi2_mock "github.com/hetznercloud/cli/internal/hcapi2/mock" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/cli/internal/state/config" + "github.com/hetznercloud/cli/internal/testutil/terminal" ) // Fixture provides affordances for testing CLI commands. @@ -22,6 +23,7 @@ type Fixture struct { ActionWaiter *state.MockActionWaiter TokenEnsurer *state.MockTokenEnsurer Config *config.MockConfig + Terminal *terminal.MockTerminal } // NewFixture creates a new Fixture with default config file. @@ -45,6 +47,7 @@ func NewFixtureWithConfigFile(t *testing.T, f any) *Fixture { ActionWaiter: state.NewMockActionWaiter(ctrl), TokenEnsurer: state.NewMockTokenEnsurer(ctrl), Config: &config.MockConfig{Config: cfg}, + Terminal: terminal.NewMockTerminal(ctrl), } } @@ -79,6 +82,7 @@ type fixtureState struct { client hcapi2.Client config config.Config + term terminal.Terminal } func (*fixtureState) WriteConfig() error { @@ -93,6 +97,10 @@ func (s *fixtureState) Config() config.Config { return s.config } +func (s *fixtureState) Terminal() terminal.Terminal { + return s.term +} + // State returns a state.State implementation for testing purposes. func (f *Fixture) State() state.State { return &fixtureState{ @@ -101,5 +109,6 @@ func (f *Fixture) State() state.State { ActionWaiter: f.ActionWaiter, client: f.Client, config: f.Config, + term: f.Terminal, } } diff --git a/internal/testutil/terminal/terminal.go b/internal/testutil/terminal/terminal.go new file mode 100644 index 00000000..6b8e2c2d --- /dev/null +++ b/internal/testutil/terminal/terminal.go @@ -0,0 +1,26 @@ +package terminal + +import ( + "golang.org/x/term" + + "github.com/hetznercloud/cli/internal/ui" +) + +//go:generate go run github.com/golang/mock/mockgen -package terminal -destination zz_terminal_mock.go . Terminal + +type Terminal interface { + StdoutIsTerminal() bool + ReadPassword(fd int) ([]byte, error) +} + +type DefaultTerminal struct{} + +func (DefaultTerminal) StdoutIsTerminal() bool { + return ui.StdoutIsTerminal() +} + +func (DefaultTerminal) ReadPassword(fd int) ([]byte, error) { + return term.ReadPassword(fd) +} + +var _ Terminal = DefaultTerminal{} diff --git a/internal/testutil/terminal/zz_terminal_mock.go b/internal/testutil/terminal/zz_terminal_mock.go new file mode 100644 index 00000000..4b97432a --- /dev/null +++ b/internal/testutil/terminal/zz_terminal_mock.go @@ -0,0 +1,63 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/hetznercloud/cli/internal/testutil/terminal (interfaces: Terminal) + +// Package terminal is a generated GoMock package. +package terminal + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockTerminal is a mock of Terminal interface. +type MockTerminal struct { + ctrl *gomock.Controller + recorder *MockTerminalMockRecorder +} + +// MockTerminalMockRecorder is the mock recorder for MockTerminal. +type MockTerminalMockRecorder struct { + mock *MockTerminal +} + +// NewMockTerminal creates a new mock instance. +func NewMockTerminal(ctrl *gomock.Controller) *MockTerminal { + mock := &MockTerminal{ctrl: ctrl} + mock.recorder = &MockTerminalMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTerminal) EXPECT() *MockTerminalMockRecorder { + return m.recorder +} + +// ReadPassword mocks base method. +func (m *MockTerminal) ReadPassword(arg0 int) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadPassword", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReadPassword indicates an expected call of ReadPassword. +func (mr *MockTerminalMockRecorder) ReadPassword(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadPassword", reflect.TypeOf((*MockTerminal)(nil).ReadPassword), arg0) +} + +// StdoutIsTerminal mocks base method. +func (m *MockTerminal) StdoutIsTerminal() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StdoutIsTerminal") + ret0, _ := ret[0].(bool) + return ret0 +} + +// StdoutIsTerminal indicates an expected call of StdoutIsTerminal. +func (mr *MockTerminalMockRecorder) StdoutIsTerminal() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StdoutIsTerminal", reflect.TypeOf((*MockTerminal)(nil).StdoutIsTerminal)) +}