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 }