diff --git a/pkg/cmd/issue/issue.go b/pkg/cmd/issue/issue.go index a02b7f7a3..03afd47f8 100644 --- a/pkg/cmd/issue/issue.go +++ b/pkg/cmd/issue/issue.go @@ -45,10 +45,10 @@ func NewCmdIssue(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(cmdClose.NewCmdClose(f, nil)) cmd.AddCommand(cmdCreate.NewCmdCreate(f, nil)) cmd.AddCommand(cmdList.NewCmdList(f, nil)) - cmd.AddCommand(cmdLock.NewCmdLock(f, cmd.Name())) + cmd.AddCommand(cmdLock.NewCmdLock(f, cmd.Name(), nil)) cmd.AddCommand(cmdReopen.NewCmdReopen(f, nil)) cmd.AddCommand(cmdStatus.NewCmdStatus(f, nil)) - cmd.AddCommand(cmdLock.NewCmdUnlock(f, cmd.Name())) + cmd.AddCommand(cmdLock.NewCmdUnlock(f, cmd.Name(), nil)) cmd.AddCommand(cmdView.NewCmdView(f, nil)) cmd.AddCommand(cmdComment.NewCmdComment(f, nil)) cmd.AddCommand(cmdDelete.NewCmdDelete(f, nil)) diff --git a/pkg/cmd/issue/lock/lock.go b/pkg/cmd/issue/lock/lock.go index c81cc8090..0b8e4c6ed 100644 --- a/pkg/cmd/issue/lock/lock.go +++ b/pkg/cmd/issue/lock/lock.go @@ -11,11 +11,11 @@ package lock import ( + "errors" "fmt" "net/http" "strings" - "github.com/AlecAivazis/survey/v2" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/ghrepo" @@ -26,6 +26,13 @@ import ( "github.com/spf13/cobra" ) +type iprompter interface { + Confirm(string, bool) (bool, error) +} + +// TODO cmd tests +// TODO run tests + // reasons contains all possible lock reasons allowed by GitHub. // // We don't directly construct a map so that we can maintain the reasons in @@ -84,6 +91,7 @@ type LockOptions struct { Config func() (config.Config, error) IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) + Prompter iprompter Fields []string ParentCmd string @@ -92,7 +100,6 @@ type LockOptions struct { } func (opts *LockOptions) setCommonOptions(f *cmdutil.Factory, cmd *cobra.Command, args []string) { - opts.IO = f.IOStreams opts.HttpClient = f.HttpClient opts.Config = f.Config @@ -107,10 +114,11 @@ func (opts *LockOptions) setCommonOptions(f *cmdutil.Factory, cmd *cobra.Command } -func NewCmdLock(f *cmdutil.Factory, parentName string) *cobra.Command { - +func NewCmdLock(f *cmdutil.Factory, parentName string, runF func(string, *LockOptions) error) *cobra.Command { opts := &LockOptions{ParentCmd: parentName} + opts.Prompter = f.Prompter + c := alias[opts.ParentCmd] short := fmt.Sprintf("Lock %s conversation", strings.ToLower(c.FullName)) @@ -119,21 +127,28 @@ func NewCmdLock(f *cmdutil.Factory, parentName string) *cobra.Command { Short: short, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.setCommonOptions(f, cmd, args) reasonProvided := cmd.Flags().Changed("message") if reasonProvided { _, ok := reasonsMap[opts.Reason] if !ok { - cs := opts.IO.ColorScheme() + if opts.IO.IsStdoutTTY() { + cs := opts.IO.ColorScheme() - return cmdutil.FlagErrorf("%s Invalid reason: %v\nAborting lock. See help for options.", - cs.FailureIconWithColor(cs.Red), opts.Reason) + return cmdutil.FlagErrorf("%s Invalid reason: %v\nAborting lock. See help for options.", + cs.FailureIconWithColor(cs.Red), opts.Reason) + + } else { + return fmt.Errorf("invalid reason %s", opts.Reason) + } } } - return padlock(Lock, opts) + if runF != nil { + return runF(Lock, opts) + } + return lock(Lock, opts) }, } @@ -143,8 +158,7 @@ func NewCmdLock(f *cmdutil.Factory, parentName string) *cobra.Command { return cmd } -func NewCmdUnlock(f *cmdutil.Factory, parentName string) *cobra.Command { - +func NewCmdUnlock(f *cmdutil.Factory, parentName string, runF func(string, *LockOptions) error) *cobra.Command { opts := &LockOptions{ParentCmd: parentName} c := alias[opts.ParentCmd] @@ -158,7 +172,10 @@ func NewCmdUnlock(f *cmdutil.Factory, parentName string) *cobra.Command { opts.setCommonOptions(f, cmd, args) - return padlock(Unlock, opts) + if runF != nil { + return runF(Unlock, opts) + } + return lock(Unlock, opts) }, } @@ -182,13 +199,12 @@ func reason(reason string) string { // // Example output: "Locked as RESOLVED: Issue #31 (Title of issue)" func status(state string, lockable *api.Issue, opts *LockOptions) string { - return fmt.Sprintf("%sed%s: %s #%d (%s)", state, reason(opts.Reason), alias[opts.ParentCmd].FullName, lockable.Number, lockable.Title) } -// padlock will lock or unlock a conversation. -func padlock(state string, opts *LockOptions) error { +// lock will lock or unlock a conversation. +func lock(state string, opts *LockOptions) error { cs := opts.IO.ColorScheme() httpClient, err := opts.HttpClient() @@ -243,14 +259,15 @@ func padlock(state string, opts *LockOptions) error { return err } - fmt.Fprint(opts.IO.ErrOut, successMsg) + if opts.IO.IsStdoutTTY() { + fmt.Fprint(opts.IO.ErrOut, successMsg) + } return nil } // lockLockable will lock an issue or pull request func lockLockable(httpClient *http.Client, repo ghrepo.Interface, lockable *api.Issue, opts *LockOptions) error { - var mutation struct { LockLockable struct { LockedRecord struct { @@ -298,17 +315,16 @@ func unlockLockable(httpClient *http.Client, repo ghrepo.Interface, lockable *ap // lockable item that is already locked; it will just ignore that request. You // need to first unlock then lock with a new reason. func relockLockable(httpClient *http.Client, repo ghrepo.Interface, lockable *api.Issue, opts *LockOptions) (bool, error) { - - var relocked bool - shouldRelock := &survey.Confirm{ - Message: fmt.Sprintf("%s #%d already locked%s. Unlock and lock again%s?", - alias[opts.ParentCmd].FullName, lockable.Number, reason(lockable.ActiveLockReason), reason(opts.Reason)), - Default: true, + if !opts.IO.CanPrompt() { + return false, errors.New("already locked") } - err := survey.AskOne(shouldRelock, &relocked) + prompt := fmt.Sprintf("%s #%d already locked%s. Unlock and lock again%s?", + alias[opts.ParentCmd].FullName, lockable.Number, reason(lockable.ActiveLockReason), reason(opts.Reason)) + + relocked, err := opts.Prompter.Confirm(prompt, true) if err != nil { - return relocked, err + return false, err } else if !relocked { return relocked, nil } diff --git a/pkg/cmd/issue/lock/lock_test.go b/pkg/cmd/issue/lock/lock_test.go index 1544f6293..ad9d8735d 100644 --- a/pkg/cmd/issue/lock/lock_test.go +++ b/pkg/cmd/issue/lock/lock_test.go @@ -1,12 +1,74 @@ package lock import ( + "bytes" + "io" "strings" "testing" + "github.com/cli/cli/v2/pkg/cmdutil" + "github.com/cli/cli/v2/pkg/iostreams" + "github.com/google/shlex" "github.com/stretchr/testify/assert" ) +func Test_NewCmdLock(t *testing.T) { + cases := []struct { + name string + args string + want LockOptions + wantErr string + tty bool + }{ + // TODO + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + ios, _, _, _ := iostreams.Test() + ios.SetStdoutTTY(tt.tty) + ios.SetStdinTTY(tt.tty) + ios.SetStderrTTY(tt.tty) + f := &cmdutil.Factory{ + IOStreams: ios, + } + var opts *LockOptions + cmd := NewCmdLock(f, "issue", func(_ string, o *LockOptions) error { + opts = o + return nil + }) + cmd.PersistentFlags().StringP("repo", "R", "", "") + + argv, err := shlex.Split(tt.args) + assert.NoError(t, err) + + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + + _, err = cmd.ExecuteC() + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) + return + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tt.want.Reason, opts.Reason) + assert.Equal(t, tt.want.SelectorArg, opts.SelectorArg) + }) + } +} + +func Test_lock(t *testing.T) { + // TODO +} + +func Test_NewCmdUnlock(t *testing.T) { + // TODO +} + func TestReasons(t *testing.T) { assert.Equal(t, len(reasons), len(reasonsApi)) diff --git a/pkg/cmd/pr/pr.go b/pkg/cmd/pr/pr.go index d5265f405..eea93d4e9 100644 --- a/pkg/cmd/pr/pr.go +++ b/pkg/cmd/pr/pr.go @@ -49,13 +49,13 @@ func NewCmdPR(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(cmdCreate.NewCmdCreate(f, nil)) cmd.AddCommand(cmdDiff.NewCmdDiff(f, nil)) cmd.AddCommand(cmdList.NewCmdList(f, nil)) - cmd.AddCommand(cmdLock.NewCmdLock(f, cmd.Name())) + cmd.AddCommand(cmdLock.NewCmdLock(f, cmd.Name(), nil)) cmd.AddCommand(cmdMerge.NewCmdMerge(f, nil)) cmd.AddCommand(cmdReady.NewCmdReady(f, nil)) cmd.AddCommand(cmdReopen.NewCmdReopen(f, nil)) cmd.AddCommand(cmdReview.NewCmdReview(f, nil)) cmd.AddCommand(cmdStatus.NewCmdStatus(f, nil)) - cmd.AddCommand(cmdLock.NewCmdUnlock(f, cmd.Name())) + cmd.AddCommand(cmdLock.NewCmdUnlock(f, cmd.Name(), nil)) cmd.AddCommand(cmdView.NewCmdView(f, nil)) cmd.AddCommand(cmdChecks.NewCmdChecks(f, nil)) cmd.AddCommand(cmdComment.NewCmdComment(f, nil))