github.com/andrewhsu/cli/v2@v2.0.1-0.20210910131313-d4b4061f5b89/pkg/cmd/pr/review/review_test.go (about)

     1  package review
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/MakeNowJust/heredoc"
    12  	"github.com/andrewhsu/cli/v2/api"
    13  	"github.com/andrewhsu/cli/v2/context"
    14  	"github.com/andrewhsu/cli/v2/internal/config"
    15  	"github.com/andrewhsu/cli/v2/internal/ghrepo"
    16  	"github.com/andrewhsu/cli/v2/pkg/cmd/pr/shared"
    17  	"github.com/andrewhsu/cli/v2/pkg/cmdutil"
    18  	"github.com/andrewhsu/cli/v2/pkg/httpmock"
    19  	"github.com/andrewhsu/cli/v2/pkg/iostreams"
    20  	"github.com/andrewhsu/cli/v2/pkg/prompt"
    21  	"github.com/andrewhsu/cli/v2/test"
    22  	"github.com/google/shlex"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func Test_NewCmdReview(t *testing.T) {
    28  	tmpFile := filepath.Join(t.TempDir(), "my-body.md")
    29  	err := ioutil.WriteFile(tmpFile, []byte("a body from file"), 0600)
    30  	require.NoError(t, err)
    31  
    32  	tests := []struct {
    33  		name    string
    34  		args    string
    35  		stdin   string
    36  		isTTY   bool
    37  		want    ReviewOptions
    38  		wantErr string
    39  	}{
    40  		{
    41  			name:  "number argument",
    42  			args:  "123",
    43  			isTTY: true,
    44  			want: ReviewOptions{
    45  				SelectorArg: "123",
    46  				ReviewType:  0,
    47  				Body:        "",
    48  			},
    49  		},
    50  		{
    51  			name:  "no argument",
    52  			args:  "",
    53  			isTTY: true,
    54  			want: ReviewOptions{
    55  				SelectorArg: "",
    56  				ReviewType:  0,
    57  				Body:        "",
    58  			},
    59  		},
    60  		{
    61  			name:  "body from stdin",
    62  			args:  "123 --request-changes --body-file -",
    63  			stdin: "this is on standard input",
    64  			isTTY: true,
    65  			want: ReviewOptions{
    66  				SelectorArg: "123",
    67  				ReviewType:  1,
    68  				Body:        "this is on standard input",
    69  			},
    70  		},
    71  		{
    72  			name:  "body from file",
    73  			args:  fmt.Sprintf("123 --request-changes --body-file '%s'", tmpFile),
    74  			isTTY: true,
    75  			want: ReviewOptions{
    76  				SelectorArg: "123",
    77  				ReviewType:  1,
    78  				Body:        "a body from file",
    79  			},
    80  		},
    81  		{
    82  			name:    "no argument with --repo override",
    83  			args:    "-R owner/repo",
    84  			isTTY:   true,
    85  			wantErr: "argument required when using the --repo flag",
    86  		},
    87  		{
    88  			name:    "no arguments in non-interactive mode",
    89  			args:    "",
    90  			isTTY:   false,
    91  			wantErr: "--approve, --request-changes, or --comment required when not running interactively",
    92  		},
    93  		{
    94  			name:    "mutually exclusive review types",
    95  			args:    `--approve --comment -b hello`,
    96  			isTTY:   true,
    97  			wantErr: "need exactly one of --approve, --request-changes, or --comment",
    98  		},
    99  		{
   100  			name:    "comment without body",
   101  			args:    `--comment`,
   102  			isTTY:   true,
   103  			wantErr: "body cannot be blank for comment review",
   104  		},
   105  		{
   106  			name:    "request changes without body",
   107  			args:    `--request-changes`,
   108  			isTTY:   true,
   109  			wantErr: "body cannot be blank for request-changes review",
   110  		},
   111  		{
   112  			name:    "only body argument",
   113  			args:    `-b hello`,
   114  			isTTY:   true,
   115  			wantErr: "--body unsupported without --approve, --request-changes, or --comment",
   116  		},
   117  		{
   118  			name:    "body and body-file flags",
   119  			args:    "--body 'test' --body-file 'test-file.txt'",
   120  			isTTY:   true,
   121  			wantErr: "specify only one of `--body` or `--body-file`",
   122  		},
   123  	}
   124  	for _, tt := range tests {
   125  		t.Run(tt.name, func(t *testing.T) {
   126  			io, stdin, _, _ := iostreams.Test()
   127  			io.SetStdoutTTY(tt.isTTY)
   128  			io.SetStdinTTY(tt.isTTY)
   129  			io.SetStderrTTY(tt.isTTY)
   130  
   131  			if tt.stdin != "" {
   132  				_, _ = stdin.WriteString(tt.stdin)
   133  			}
   134  
   135  			f := &cmdutil.Factory{
   136  				IOStreams: io,
   137  			}
   138  
   139  			var opts *ReviewOptions
   140  			cmd := NewCmdReview(f, func(o *ReviewOptions) error {
   141  				opts = o
   142  				return nil
   143  			})
   144  			cmd.PersistentFlags().StringP("repo", "R", "", "")
   145  
   146  			argv, err := shlex.Split(tt.args)
   147  			require.NoError(t, err)
   148  			cmd.SetArgs(argv)
   149  
   150  			cmd.SetIn(&bytes.Buffer{})
   151  			cmd.SetOut(ioutil.Discard)
   152  			cmd.SetErr(ioutil.Discard)
   153  
   154  			_, err = cmd.ExecuteC()
   155  			if tt.wantErr != "" {
   156  				require.EqualError(t, err, tt.wantErr)
   157  				return
   158  			} else {
   159  				require.NoError(t, err)
   160  			}
   161  
   162  			assert.Equal(t, tt.want.SelectorArg, opts.SelectorArg)
   163  			assert.Equal(t, tt.want.Body, opts.Body)
   164  		})
   165  	}
   166  }
   167  
   168  func runCommand(rt http.RoundTripper, remotes context.Remotes, isTTY bool, cli string) (*test.CmdOut, error) {
   169  	io, _, stdout, stderr := iostreams.Test()
   170  	io.SetStdoutTTY(isTTY)
   171  	io.SetStdinTTY(isTTY)
   172  	io.SetStderrTTY(isTTY)
   173  
   174  	factory := &cmdutil.Factory{
   175  		IOStreams: io,
   176  		HttpClient: func() (*http.Client, error) {
   177  			return &http.Client{Transport: rt}, nil
   178  		},
   179  		Config: func() (config.Config, error) {
   180  			return config.NewBlankConfig(), nil
   181  		},
   182  	}
   183  
   184  	cmd := NewCmdReview(factory, nil)
   185  
   186  	argv, err := shlex.Split(cli)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	cmd.SetArgs(argv)
   191  
   192  	cmd.SetIn(&bytes.Buffer{})
   193  	cmd.SetOut(ioutil.Discard)
   194  	cmd.SetErr(ioutil.Discard)
   195  
   196  	_, err = cmd.ExecuteC()
   197  	return &test.CmdOut{
   198  		OutBuf: stdout,
   199  		ErrBuf: stderr,
   200  	}, err
   201  }
   202  
   203  func TestPRReview(t *testing.T) {
   204  	tests := []struct {
   205  		args      string
   206  		wantEvent string
   207  		wantBody  string
   208  	}{
   209  		{
   210  			args:      `--request-changes -b"bad"`,
   211  			wantEvent: "REQUEST_CHANGES",
   212  			wantBody:  "bad",
   213  		},
   214  		{
   215  			args:      `--approve`,
   216  			wantEvent: "APPROVE",
   217  			wantBody:  "",
   218  		},
   219  		{
   220  			args:      `--approve -b"hot damn"`,
   221  			wantEvent: "APPROVE",
   222  			wantBody:  "hot damn",
   223  		},
   224  		{
   225  			args:      `--comment --body "i dunno"`,
   226  			wantEvent: "COMMENT",
   227  			wantBody:  "i dunno",
   228  		},
   229  	}
   230  
   231  	for _, tt := range tests {
   232  		t.Run(tt.args, func(t *testing.T) {
   233  			http := &httpmock.Registry{}
   234  			defer http.Verify(t)
   235  
   236  			shared.RunCommandFinder("", &api.PullRequest{ID: "THE-ID"}, ghrepo.New("OWNER", "REPO"))
   237  
   238  			http.Register(
   239  				httpmock.GraphQL(`mutation PullRequestReviewAdd\b`),
   240  				httpmock.GraphQLMutation(`{"data": {} }`,
   241  					func(inputs map[string]interface{}) {
   242  						assert.Equal(t, map[string]interface{}{
   243  							"pullRequestId": "THE-ID",
   244  							"event":         tt.wantEvent,
   245  							"body":          tt.wantBody,
   246  						}, inputs)
   247  					}),
   248  			)
   249  
   250  			output, err := runCommand(http, nil, false, tt.args)
   251  			assert.NoError(t, err)
   252  			assert.Equal(t, "", output.String())
   253  			assert.Equal(t, "", output.Stderr())
   254  		})
   255  	}
   256  }
   257  
   258  func TestPRReview_interactive(t *testing.T) {
   259  	http := &httpmock.Registry{}
   260  	defer http.Verify(t)
   261  
   262  	shared.RunCommandFinder("", &api.PullRequest{ID: "THE-ID", Number: 123}, ghrepo.New("OWNER", "REPO"))
   263  
   264  	http.Register(
   265  		httpmock.GraphQL(`mutation PullRequestReviewAdd\b`),
   266  		httpmock.GraphQLMutation(`{"data": {} }`,
   267  			func(inputs map[string]interface{}) {
   268  				assert.Equal(t, inputs["event"], "APPROVE")
   269  				assert.Equal(t, inputs["body"], "cool story")
   270  			}),
   271  	)
   272  
   273  	as, teardown := prompt.InitAskStubber()
   274  	defer teardown()
   275  
   276  	as.Stub([]*prompt.QuestionStub{
   277  		{
   278  			Name:  "reviewType",
   279  			Value: "Approve",
   280  		},
   281  	})
   282  	as.Stub([]*prompt.QuestionStub{
   283  		{
   284  			Name:  "body",
   285  			Value: "cool story",
   286  		},
   287  	})
   288  	as.Stub([]*prompt.QuestionStub{
   289  		{
   290  			Name:  "confirm",
   291  			Value: true,
   292  		},
   293  	})
   294  
   295  	output, err := runCommand(http, nil, true, "")
   296  	assert.NoError(t, err)
   297  	assert.Equal(t, heredoc.Doc(`
   298  		Got:
   299  
   300  		  cool story                                                                  
   301  		
   302  	`), output.String())
   303  	assert.Equal(t, "✓ Approved pull request #123\n", output.Stderr())
   304  }
   305  
   306  func TestPRReview_interactive_no_body(t *testing.T) {
   307  	http := &httpmock.Registry{}
   308  	defer http.Verify(t)
   309  
   310  	shared.RunCommandFinder("", &api.PullRequest{ID: "THE-ID", Number: 123}, ghrepo.New("OWNER", "REPO"))
   311  
   312  	as, teardown := prompt.InitAskStubber()
   313  	defer teardown()
   314  
   315  	as.Stub([]*prompt.QuestionStub{
   316  		{
   317  			Name:  "reviewType",
   318  			Value: "Request changes",
   319  		},
   320  	})
   321  	as.Stub([]*prompt.QuestionStub{
   322  		{
   323  			Name:    "body",
   324  			Default: true,
   325  		},
   326  	})
   327  	as.Stub([]*prompt.QuestionStub{
   328  		{
   329  			Name:  "confirm",
   330  			Value: true,
   331  		},
   332  	})
   333  
   334  	_, err := runCommand(http, nil, true, "")
   335  	assert.EqualError(t, err, "this type of review cannot be blank")
   336  }
   337  
   338  func TestPRReview_interactive_blank_approve(t *testing.T) {
   339  	http := &httpmock.Registry{}
   340  	defer http.Verify(t)
   341  
   342  	shared.RunCommandFinder("", &api.PullRequest{ID: "THE-ID", Number: 123}, ghrepo.New("OWNER", "REPO"))
   343  
   344  	http.Register(
   345  		httpmock.GraphQL(`mutation PullRequestReviewAdd\b`),
   346  		httpmock.GraphQLMutation(`{"data": {} }`,
   347  			func(inputs map[string]interface{}) {
   348  				assert.Equal(t, inputs["event"], "APPROVE")
   349  				assert.Equal(t, inputs["body"], "")
   350  			}),
   351  	)
   352  
   353  	as, teardown := prompt.InitAskStubber()
   354  	defer teardown()
   355  
   356  	as.Stub([]*prompt.QuestionStub{
   357  		{
   358  			Name:  "reviewType",
   359  			Value: "Approve",
   360  		},
   361  	})
   362  	as.Stub([]*prompt.QuestionStub{
   363  		{
   364  			Name:    "body",
   365  			Default: true,
   366  		},
   367  	})
   368  	as.Stub([]*prompt.QuestionStub{
   369  		{
   370  			Name:  "confirm",
   371  			Value: true,
   372  		},
   373  	})
   374  
   375  	output, err := runCommand(http, nil, true, "")
   376  	assert.NoError(t, err)
   377  	assert.Equal(t, "", output.String())
   378  	assert.Equal(t, "✓ Approved pull request #123\n", output.Stderr())
   379  }