github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/pr/checks/checks_test.go (about) 1 package checks 2 3 import ( 4 "bytes" 5 "net/http" 6 "reflect" 7 "testing" 8 "time" 9 10 "github.com/ungtb10d/cli/v2/api" 11 "github.com/ungtb10d/cli/v2/internal/browser" 12 "github.com/ungtb10d/cli/v2/internal/ghrepo" 13 "github.com/ungtb10d/cli/v2/internal/run" 14 "github.com/ungtb10d/cli/v2/pkg/cmd/pr/shared" 15 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 16 "github.com/ungtb10d/cli/v2/pkg/httpmock" 17 "github.com/ungtb10d/cli/v2/pkg/iostreams" 18 "github.com/google/shlex" 19 "github.com/stretchr/testify/assert" 20 ) 21 22 func TestNewCmdChecks(t *testing.T) { 23 tests := []struct { 24 name string 25 cli string 26 wants ChecksOptions 27 wantsError string 28 }{ 29 { 30 name: "no arguments", 31 cli: "", 32 wants: ChecksOptions{ 33 Interval: time.Duration(10000000000), 34 }, 35 }, 36 { 37 name: "pr argument", 38 cli: "1234", 39 wants: ChecksOptions{ 40 SelectorArg: "1234", 41 Interval: time.Duration(10000000000), 42 }, 43 }, 44 { 45 name: "watch flag", 46 cli: "--watch", 47 wants: ChecksOptions{ 48 Watch: true, 49 Interval: time.Duration(10000000000), 50 }, 51 }, 52 { 53 name: "watch flag and interval flag", 54 cli: "--watch --interval 5", 55 wants: ChecksOptions{ 56 Watch: true, 57 Interval: time.Duration(5000000000), 58 }, 59 }, 60 { 61 name: "interval flag without watch flag", 62 cli: "--interval 5", 63 wantsError: "cannot use `--interval` flag without `--watch` flag", 64 }, 65 { 66 name: "required flag", 67 cli: "--required", 68 wants: ChecksOptions{ 69 Required: true, 70 Interval: time.Duration(10000000000), 71 }, 72 }, 73 } 74 75 for _, tt := range tests { 76 t.Run(tt.name, func(t *testing.T) { 77 ios, _, _, _ := iostreams.Test() 78 f := &cmdutil.Factory{ 79 IOStreams: ios, 80 } 81 82 argv, err := shlex.Split(tt.cli) 83 assert.NoError(t, err) 84 85 var gotOpts *ChecksOptions 86 cmd := NewCmdChecks(f, func(opts *ChecksOptions) error { 87 gotOpts = opts 88 return nil 89 }) 90 cmd.SetArgs(argv) 91 cmd.SetIn(&bytes.Buffer{}) 92 cmd.SetOut(&bytes.Buffer{}) 93 cmd.SetErr(&bytes.Buffer{}) 94 95 _, err = cmd.ExecuteC() 96 if tt.wantsError != "" { 97 assert.EqualError(t, err, tt.wantsError) 98 return 99 } 100 assert.NoError(t, err) 101 assert.Equal(t, tt.wants.SelectorArg, gotOpts.SelectorArg) 102 assert.Equal(t, tt.wants.Watch, gotOpts.Watch) 103 assert.Equal(t, tt.wants.Interval, gotOpts.Interval) 104 assert.Equal(t, tt.wants.Required, gotOpts.Required) 105 }) 106 } 107 } 108 109 func Test_checksRun(t *testing.T) { 110 tests := []struct { 111 name string 112 tty bool 113 watch bool 114 required bool 115 httpStubs func(*httpmock.Registry) 116 wantOut string 117 wantErr string 118 }{ 119 { 120 name: "no commits", 121 tty: true, 122 httpStubs: func(reg *httpmock.Registry) { 123 reg.Register( 124 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 125 httpmock.StringResponse(`{"data":{"node":{}}}`), 126 ) 127 }, 128 wantOut: "", 129 wantErr: "no commit found on the pull request", 130 }, 131 { 132 name: "no checks", 133 tty: true, 134 httpStubs: func(reg *httpmock.Registry) { 135 reg.Register( 136 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 137 httpmock.StringResponse(`{"data":{"node":{"statusCheckRollup":{"nodes":[{"commit":{"oid": "abc"}}]}}}}`), 138 ) 139 }, 140 wantOut: "", 141 wantErr: "no checks reported on the 'trunk' branch", 142 }, 143 { 144 name: "some failing", 145 tty: true, 146 httpStubs: func(reg *httpmock.Registry) { 147 reg.Register( 148 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 149 httpmock.FileResponse("./fixtures/someFailing.json"), 150 ) 151 }, 152 wantOut: "Some checks were not successful\n1 failing, 1 successful, 0 skipped, and 1 pending checks\n\nX sad tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n* slow tests 1m26s sweet link\n", 153 wantErr: "SilentError", 154 }, 155 { 156 name: "some pending", 157 tty: true, 158 httpStubs: func(reg *httpmock.Registry) { 159 reg.Register( 160 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 161 httpmock.FileResponse("./fixtures/somePending.json"), 162 ) 163 }, 164 wantOut: "Some checks are still pending\n0 failing, 2 successful, 0 skipped, and 1 pending checks\n\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n* slow tests 1m26s sweet link\n", 165 wantErr: "SilentError", 166 }, 167 { 168 name: "all passing", 169 tty: true, 170 httpStubs: func(reg *httpmock.Registry) { 171 reg.Register( 172 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 173 httpmock.FileResponse("./fixtures/allPassing.json"), 174 ) 175 }, 176 wantOut: "All checks were successful\n0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n", 177 wantErr: "", 178 }, 179 { 180 name: "watch all passing", 181 tty: true, 182 watch: true, 183 httpStubs: func(reg *httpmock.Registry) { 184 reg.Register( 185 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 186 httpmock.FileResponse("./fixtures/allPassing.json"), 187 ) 188 }, 189 wantOut: "\x1b[?1049hAll checks were successful\n0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n\x1b[?1049lAll checks were successful\n0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n", 190 wantErr: "", 191 }, 192 { 193 name: "with statuses", 194 tty: true, 195 httpStubs: func(reg *httpmock.Registry) { 196 reg.Register( 197 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 198 httpmock.FileResponse("./fixtures/withStatuses.json"), 199 ) 200 }, 201 wantOut: "Some checks were not successful\n1 failing, 2 successful, 0 skipped, and 0 pending checks\n\nX a status sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n", 202 wantErr: "SilentError", 203 }, 204 { 205 name: "no checks", 206 httpStubs: func(reg *httpmock.Registry) { 207 reg.Register( 208 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 209 httpmock.StringResponse(`{"data":{"node":{"statusCheckRollup":{"nodes":[{"commit":{"oid": "abc"}}]}}}}`), 210 ) 211 }, 212 wantOut: "", 213 wantErr: "no checks reported on the 'trunk' branch", 214 }, 215 { 216 name: "some failing", 217 httpStubs: func(reg *httpmock.Registry) { 218 reg.Register( 219 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 220 httpmock.FileResponse("./fixtures/someFailing.json"), 221 ) 222 }, 223 wantOut: "sad tests\tfail\t1m26s\tsweet link\ncool tests\tpass\t1m26s\tsweet link\nslow tests\tpending\t1m26s\tsweet link\n", 224 wantErr: "SilentError", 225 }, 226 { 227 name: "some pending", 228 httpStubs: func(reg *httpmock.Registry) { 229 reg.Register( 230 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 231 httpmock.FileResponse("./fixtures/somePending.json"), 232 ) 233 }, 234 wantOut: "cool tests\tpass\t1m26s\tsweet link\nrad tests\tpass\t1m26s\tsweet link\nslow tests\tpending\t1m26s\tsweet link\n", 235 wantErr: "SilentError", 236 }, 237 { 238 name: "all passing", 239 httpStubs: func(reg *httpmock.Registry) { 240 reg.Register( 241 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 242 httpmock.FileResponse("./fixtures/allPassing.json"), 243 ) 244 }, 245 wantOut: "awesome tests\tpass\t1m26s\tsweet link\ncool tests\tpass\t1m26s\tsweet link\nrad tests\tpass\t1m26s\tsweet link\n", 246 wantErr: "", 247 }, 248 { 249 name: "with statuses", 250 httpStubs: func(reg *httpmock.Registry) { 251 reg.Register( 252 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 253 httpmock.FileResponse("./fixtures/withStatuses.json"), 254 ) 255 }, 256 wantOut: "a status\tfail\t0\tsweet link\ncool tests\tpass\t1m26s\tsweet link\nrad tests\tpass\t1m26s\tsweet link\n", 257 wantErr: "SilentError", 258 }, 259 { 260 name: "some skipped", 261 httpStubs: func(reg *httpmock.Registry) { 262 reg.Register( 263 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 264 httpmock.FileResponse("./fixtures/someSkipping.json"), 265 ) 266 }, 267 tty: true, 268 wantOut: "All checks were successful\n0 failing, 1 successful, 2 skipped, and 0 pending checks\n\n✓ cool tests 1m26s sweet link\n- rad tests 1m26s sweet link\n- skip tests 1m26s sweet link\n", 269 wantErr: "", 270 }, 271 { 272 name: "only required", 273 httpStubs: func(reg *httpmock.Registry) { 274 reg.Register( 275 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 276 httpmock.FileResponse("./fixtures/onlyRequired.json"), 277 ) 278 }, 279 tty: true, 280 wantOut: "All checks were successful\n0 failing, 1 successful, 0 skipped, and 0 pending checks\n\n✓ cool tests 1m26s sweet link\n", 281 wantErr: "", 282 required: true, 283 }, 284 { 285 name: "no required checks", 286 httpStubs: func(reg *httpmock.Registry) { 287 reg.Register( 288 httpmock.GraphQL(`query PullRequestStatusChecks\b`), 289 httpmock.FileResponse("./fixtures/someSkipping.json"), 290 ) 291 }, 292 wantOut: "", 293 wantErr: "no required checks reported on the 'trunk' branch", 294 required: true, 295 }, 296 } 297 298 for _, tt := range tests { 299 t.Run(tt.name, func(t *testing.T) { 300 ios, _, stdout, _ := iostreams.Test() 301 ios.SetStdoutTTY(tt.tty) 302 ios.SetAlternateScreenBufferEnabled(tt.tty) 303 304 reg := &httpmock.Registry{} 305 defer reg.Verify(t) 306 if tt.httpStubs != nil { 307 tt.httpStubs(reg) 308 } 309 310 response := &api.PullRequest{Number: 123, HeadRefName: "trunk"} 311 opts := &ChecksOptions{ 312 HttpClient: func() (*http.Client, error) { 313 return &http.Client{Transport: reg}, nil 314 }, 315 IO: ios, 316 SelectorArg: "123", 317 Finder: shared.NewMockFinder("123", response, ghrepo.New("OWNER", "REPO")), 318 Watch: tt.watch, 319 Required: tt.required, 320 } 321 322 err := checksRun(opts) 323 if tt.wantErr != "" { 324 assert.EqualError(t, err, tt.wantErr) 325 } else { 326 assert.NoError(t, err) 327 } 328 329 assert.Equal(t, tt.wantOut, stdout.String()) 330 }) 331 } 332 } 333 334 func TestChecksRun_web(t *testing.T) { 335 tests := []struct { 336 name string 337 isTTY bool 338 wantStderr string 339 wantStdout string 340 wantBrowse string 341 }{ 342 { 343 name: "tty", 344 isTTY: true, 345 wantStderr: "Opening github.com/OWNER/REPO/pull/123/checks in your browser.\n", 346 wantStdout: "", 347 wantBrowse: "https://github.com/OWNER/REPO/pull/123/checks", 348 }, 349 { 350 name: "nontty", 351 isTTY: false, 352 wantStderr: "", 353 wantStdout: "", 354 wantBrowse: "https://github.com/OWNER/REPO/pull/123/checks", 355 }, 356 } 357 for _, tc := range tests { 358 t.Run(tc.name, func(t *testing.T) { 359 browser := &browser.Stub{} 360 361 ios, _, stdout, stderr := iostreams.Test() 362 ios.SetStdoutTTY(tc.isTTY) 363 ios.SetStdinTTY(tc.isTTY) 364 ios.SetStderrTTY(tc.isTTY) 365 366 _, teardown := run.Stub() 367 defer teardown(t) 368 369 err := checksRunWebMode(&ChecksOptions{ 370 IO: ios, 371 Browser: browser, 372 WebMode: true, 373 SelectorArg: "123", 374 Finder: shared.NewMockFinder("123", &api.PullRequest{Number: 123}, ghrepo.New("OWNER", "REPO")), 375 }) 376 assert.NoError(t, err) 377 assert.Equal(t, tc.wantStdout, stdout.String()) 378 assert.Equal(t, tc.wantStderr, stderr.String()) 379 browser.Verify(t, tc.wantBrowse) 380 }) 381 } 382 } 383 384 func TestEliminateDupulicates(t *testing.T) { 385 tests := []struct { 386 name string 387 checkContexts []api.CheckContext 388 want []api.CheckContext 389 }{ 390 { 391 name: "duplicate CheckRun (lint)", 392 checkContexts: []api.CheckContext{ 393 { 394 TypeName: "CheckRun", 395 Name: "build (ubuntu-latest)", 396 Status: "COMPLETED", 397 Conclusion: "SUCCESS", 398 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 399 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 400 DetailsURL: "https://github.com/ungtb10d/cli/runs/1", 401 }, 402 { 403 TypeName: "CheckRun", 404 Name: "lint", 405 Status: "COMPLETED", 406 Conclusion: "FAILURE", 407 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 408 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 409 DetailsURL: "https://github.com/ungtb10d/cli/runs/2", 410 }, 411 { 412 TypeName: "CheckRun", 413 Name: "lint", 414 Status: "COMPLETED", 415 Conclusion: "SUCCESS", 416 StartedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC), 417 CompletedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC), 418 DetailsURL: "https://github.com/ungtb10d/cli/runs/3", 419 }, 420 }, 421 want: []api.CheckContext{ 422 { 423 TypeName: "CheckRun", 424 Name: "lint", 425 Status: "COMPLETED", 426 Conclusion: "SUCCESS", 427 StartedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC), 428 CompletedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC), 429 DetailsURL: "https://github.com/ungtb10d/cli/runs/3", 430 }, 431 { 432 TypeName: "CheckRun", 433 Name: "build (ubuntu-latest)", 434 Status: "COMPLETED", 435 Conclusion: "SUCCESS", 436 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 437 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 438 DetailsURL: "https://github.com/ungtb10d/cli/runs/1", 439 }, 440 }, 441 }, 442 { 443 name: "duplicate StatusContext (Windows GPU)", 444 checkContexts: []api.CheckContext{ 445 { 446 TypeName: "StatusContext", 447 Name: "", 448 Context: "Windows GPU", 449 State: "FAILURE", 450 Status: "", 451 Conclusion: "", 452 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 453 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 454 DetailsURL: "", 455 TargetURL: "https://github.com/ungtb10d/cli/2", 456 }, 457 { 458 TypeName: "StatusContext", 459 Name: "", 460 Context: "Windows GPU", 461 State: "SUCCESS", 462 Status: "", 463 Conclusion: "", 464 StartedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC), 465 CompletedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC), 466 DetailsURL: "", 467 TargetURL: "https://github.com/ungtb10d/cli/3", 468 }, 469 { 470 TypeName: "StatusContext", 471 Name: "", 472 Context: "Linux GPU", 473 State: "SUCCESS", 474 Status: "", 475 Conclusion: "", 476 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 477 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 478 DetailsURL: "", 479 TargetURL: "https://github.com/ungtb10d/cli/1", 480 }, 481 }, 482 want: []api.CheckContext{ 483 { 484 TypeName: "StatusContext", 485 Name: "", 486 Context: "Windows GPU", 487 State: "SUCCESS", 488 Status: "", 489 Conclusion: "", 490 StartedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC), 491 CompletedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC), 492 DetailsURL: "", 493 TargetURL: "https://github.com/ungtb10d/cli/3", 494 }, 495 { 496 TypeName: "StatusContext", 497 Name: "", 498 Context: "Linux GPU", 499 State: "SUCCESS", 500 Status: "", 501 Conclusion: "", 502 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 503 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 504 DetailsURL: "", 505 TargetURL: "https://github.com/ungtb10d/cli/1", 506 }, 507 }, 508 }, 509 { 510 name: "unique CheckContext", 511 checkContexts: []api.CheckContext{ 512 { 513 TypeName: "CheckRun", 514 Name: "build (ubuntu-latest)", 515 Status: "COMPLETED", 516 Conclusion: "SUCCESS", 517 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 518 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 519 DetailsURL: "https://github.com/ungtb10d/cli/runs/1", 520 }, 521 { 522 TypeName: "StatusContext", 523 Name: "", 524 Context: "Windows GPU", 525 State: "SUCCESS", 526 Status: "", 527 Conclusion: "", 528 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 529 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 530 DetailsURL: "", 531 TargetURL: "https://github.com/ungtb10d/cli/2", 532 }, 533 { 534 TypeName: "StatusContext", 535 Name: "", 536 Context: "Linux GPU", 537 State: "SUCCESS", 538 Status: "", 539 Conclusion: "", 540 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 541 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 542 DetailsURL: "", 543 TargetURL: "https://github.com/ungtb10d/cli/3", 544 }, 545 }, 546 want: []api.CheckContext{ 547 { 548 TypeName: "CheckRun", 549 Name: "build (ubuntu-latest)", 550 Status: "COMPLETED", 551 Conclusion: "SUCCESS", 552 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 553 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 554 DetailsURL: "https://github.com/ungtb10d/cli/runs/1", 555 }, 556 { 557 TypeName: "StatusContext", 558 Name: "", 559 Context: "Windows GPU", 560 State: "SUCCESS", 561 Status: "", 562 Conclusion: "", 563 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 564 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 565 DetailsURL: "", 566 TargetURL: "https://github.com/ungtb10d/cli/2", 567 }, 568 { 569 TypeName: "StatusContext", 570 Name: "", 571 Context: "Linux GPU", 572 State: "SUCCESS", 573 Status: "", 574 Conclusion: "", 575 StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 576 CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), 577 DetailsURL: "", 578 TargetURL: "https://github.com/ungtb10d/cli/3", 579 }, 580 }, 581 }, 582 } 583 584 for _, tt := range tests { 585 t.Run(tt.name, func(t *testing.T) { 586 got := eliminateDuplicates(tt.checkContexts) 587 if !reflect.DeepEqual(tt.want, got) { 588 t.Errorf("got eliminateDuplicates %+v, want %+v\n", got, tt.want) 589 } 590 }) 591 } 592 }