github.com/secman-team/gh-api@v1.8.2/pkg/cmd/repo/fork/fork_test.go (about) 1 package fork 2 3 import ( 4 "bytes" 5 "net/http" 6 "net/url" 7 "regexp" 8 "testing" 9 "time" 10 11 "github.com/secman-team/gh-api/context" 12 "github.com/secman-team/gh-api/git" 13 "github.com/secman-team/gh-api/core/config" 14 "github.com/secman-team/gh-api/core/ghrepo" 15 "github.com/secman-team/gh-api/core/run" 16 "github.com/secman-team/gh-api/pkg/cmdutil" 17 "github.com/secman-team/gh-api/pkg/httpmock" 18 "github.com/secman-team/gh-api/pkg/iostreams" 19 "github.com/secman-team/gh-api/pkg/prompt" 20 "github.com/secman-team/gh-api/test" 21 "github.com/google/shlex" 22 "github.com/stretchr/testify/assert" 23 ) 24 25 func TestNewCmdFork(t *testing.T) { 26 tests := []struct { 27 name string 28 cli string 29 tty bool 30 wants ForkOptions 31 wantErr bool 32 }{ 33 { 34 name: "repo with git args", 35 cli: "foo/bar -- --foo=bar", 36 wants: ForkOptions{ 37 Repository: "foo/bar", 38 GitArgs: []string{"TODO"}, 39 RemoteName: "origin", 40 Rename: true, 41 }, 42 }, 43 { 44 name: "git args without repo", 45 cli: "-- --foo bar", 46 wantErr: true, 47 }, 48 { 49 name: "repo", 50 cli: "foo/bar", 51 wants: ForkOptions{ 52 Repository: "foo/bar", 53 RemoteName: "origin", 54 Rename: true, 55 }, 56 }, 57 { 58 name: "blank remote name", 59 cli: "--remote --remote-name=''", 60 wantErr: true, 61 }, 62 { 63 name: "remote name", 64 cli: "--remote --remote-name=foo", 65 wants: ForkOptions{ 66 RemoteName: "foo", 67 Rename: false, 68 Remote: true, 69 }, 70 }, 71 { 72 name: "blank nontty", 73 cli: "", 74 wants: ForkOptions{ 75 RemoteName: "origin", 76 Rename: true, 77 }, 78 }, 79 { 80 name: "blank tty", 81 cli: "", 82 tty: true, 83 wants: ForkOptions{ 84 RemoteName: "origin", 85 PromptClone: true, 86 PromptRemote: true, 87 Rename: true, 88 }, 89 }, 90 { 91 name: "clone", 92 cli: "--clone", 93 wants: ForkOptions{ 94 RemoteName: "origin", 95 Rename: true, 96 }, 97 }, 98 { 99 name: "remote", 100 cli: "--remote", 101 wants: ForkOptions{ 102 RemoteName: "origin", 103 Remote: true, 104 Rename: true, 105 }, 106 }, 107 } 108 109 for _, tt := range tests { 110 t.Run(tt.name, func(t *testing.T) { 111 io, _, _, _ := iostreams.Test() 112 113 f := &cmdutil.Factory{ 114 IOStreams: io, 115 } 116 117 io.SetStdoutTTY(tt.tty) 118 io.SetStdinTTY(tt.tty) 119 120 argv, err := shlex.Split(tt.cli) 121 assert.NoError(t, err) 122 123 var gotOpts *ForkOptions 124 cmd := NewCmdFork(f, func(opts *ForkOptions) error { 125 gotOpts = opts 126 return nil 127 }) 128 cmd.SetArgs(argv) 129 cmd.SetIn(&bytes.Buffer{}) 130 cmd.SetOut(&bytes.Buffer{}) 131 cmd.SetErr(&bytes.Buffer{}) 132 133 _, err = cmd.ExecuteC() 134 if tt.wantErr { 135 assert.Error(t, err) 136 return 137 } 138 assert.NoError(t, err) 139 140 assert.Equal(t, tt.wants.RemoteName, gotOpts.RemoteName) 141 assert.Equal(t, tt.wants.Remote, gotOpts.Remote) 142 assert.Equal(t, tt.wants.PromptRemote, gotOpts.PromptRemote) 143 assert.Equal(t, tt.wants.PromptClone, gotOpts.PromptClone) 144 }) 145 } 146 } 147 148 func runCommand(httpClient *http.Client, remotes []*context.Remote, isTTY bool, cli string) (*test.CmdOut, error) { 149 io, stdin, stdout, stderr := iostreams.Test() 150 io.SetStdoutTTY(isTTY) 151 io.SetStdinTTY(isTTY) 152 io.SetStderrTTY(isTTY) 153 fac := &cmdutil.Factory{ 154 IOStreams: io, 155 HttpClient: func() (*http.Client, error) { 156 return httpClient, nil 157 }, 158 Config: func() (config.Config, error) { 159 return config.NewBlankConfig(), nil 160 }, 161 BaseRepo: func() (ghrepo.Interface, error) { 162 return ghrepo.New("OWNER", "REPO"), nil 163 }, 164 Remotes: func() (context.Remotes, error) { 165 if remotes == nil { 166 return []*context.Remote{ 167 { 168 Remote: &git.Remote{ 169 Name: "origin", 170 FetchURL: &url.URL{}, 171 }, 172 Repo: ghrepo.New("OWNER", "REPO"), 173 }, 174 }, nil 175 } 176 177 return remotes, nil 178 }, 179 } 180 181 cmd := NewCmdFork(fac, nil) 182 183 argv, err := shlex.Split(cli) 184 cmd.SetArgs(argv) 185 186 cmd.SetIn(stdin) 187 cmd.SetOut(stdout) 188 cmd.SetErr(stderr) 189 190 if err != nil { 191 panic(err) 192 } 193 194 _, err = cmd.ExecuteC() 195 196 if err != nil { 197 return nil, err 198 } 199 200 return &test.CmdOut{ 201 OutBuf: stdout, 202 ErrBuf: stderr}, nil 203 } 204 205 func TestRepoFork_nontty(t *testing.T) { 206 defer stubSince(2 * time.Second)() 207 reg := &httpmock.Registry{} 208 defer reg.Verify(t) 209 defer reg.StubWithFixturePath(200, "./forkResult.json")() 210 httpClient := &http.Client{Transport: reg} 211 212 _, restore := run.Stub() 213 defer restore(t) 214 215 output, err := runCommand(httpClient, nil, false, "") 216 if err != nil { 217 t.Fatalf("error running command `repo fork`: %v", err) 218 } 219 220 assert.Equal(t, "", output.String()) 221 assert.Equal(t, "", output.Stderr()) 222 223 } 224 225 func TestRepoFork_no_conflicting_remote(t *testing.T) { 226 remotes := []*context.Remote{ 227 { 228 Remote: &git.Remote{ 229 Name: "upstream", 230 FetchURL: &url.URL{}, 231 }, 232 Repo: ghrepo.New("OWNER", "REPO"), 233 }, 234 } 235 defer stubSince(2 * time.Second)() 236 reg := &httpmock.Registry{} 237 defer reg.Verify(t) 238 defer reg.StubWithFixturePath(200, "./forkResult.json")() 239 httpClient := &http.Client{Transport: reg} 240 241 cs, restore := run.Stub() 242 defer restore(t) 243 244 cs.Register(`git remote add -f origin https://github\.com/someone/REPO\.git`, 0, "") 245 246 output, err := runCommand(httpClient, remotes, false, "--remote") 247 if err != nil { 248 t.Fatalf("error running command `repo fork`: %v", err) 249 } 250 251 assert.Equal(t, "", output.String()) 252 assert.Equal(t, "", output.Stderr()) 253 } 254 255 func TestRepoFork_existing_remote_error(t *testing.T) { 256 defer stubSince(2 * time.Second)() 257 reg := &httpmock.Registry{} 258 defer reg.StubWithFixturePath(200, "./forkResult.json")() 259 httpClient := &http.Client{Transport: reg} 260 261 _, err := runCommand(httpClient, nil, true, "--remote --remote-name='origin'") 262 if err == nil { 263 t.Fatal("expected error running command `repo fork`") 264 } 265 266 assert.Equal(t, "a git remote named 'origin' already exists", err.Error()) 267 268 reg.Verify(t) 269 } 270 271 func TestRepoFork_in_parent_tty(t *testing.T) { 272 defer stubSince(2 * time.Second)() 273 reg := &httpmock.Registry{} 274 defer reg.StubWithFixturePath(200, "./forkResult.json")() 275 httpClient := &http.Client{Transport: reg} 276 277 cs, restore := run.Stub() 278 defer restore(t) 279 280 cs.Register("git remote rename origin upstream", 0, "") 281 cs.Register(`git remote add -f origin https://github\.com/someone/REPO\.git`, 0, "") 282 283 output, err := runCommand(httpClient, nil, true, "--remote") 284 if err != nil { 285 t.Fatalf("error running command `repo fork`: %v", err) 286 } 287 288 assert.Equal(t, "", output.String()) 289 assert.Equal(t, "ā Created fork someone/REPO\nā Added remote origin\n", output.Stderr()) 290 reg.Verify(t) 291 } 292 func TestRepoFork_in_parent_nontty(t *testing.T) { 293 defer stubSince(2 * time.Second)() 294 reg := &httpmock.Registry{} 295 defer reg.StubWithFixturePath(200, "./forkResult.json")() 296 httpClient := &http.Client{Transport: reg} 297 298 cs, restore := run.Stub() 299 defer restore(t) 300 301 cs.Register("git remote rename origin upstream", 0, "") 302 cs.Register(`git remote add -f origin https://github\.com/someone/REPO\.git`, 0, "") 303 304 output, err := runCommand(httpClient, nil, false, "--remote") 305 if err != nil { 306 t.Fatalf("error running command `repo fork`: %v", err) 307 } 308 309 assert.Equal(t, "", output.String()) 310 assert.Equal(t, "", output.Stderr()) 311 reg.Verify(t) 312 } 313 314 func TestRepoFork_outside_parent_nontty(t *testing.T) { 315 defer stubSince(2 * time.Second)() 316 reg := &httpmock.Registry{} 317 reg.Verify(t) 318 defer reg.StubWithFixturePath(200, "./forkResult.json")() 319 httpClient := &http.Client{Transport: reg} 320 321 cs, restore := run.Stub() 322 defer restore(t) 323 324 cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "") 325 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 326 327 output, err := runCommand(httpClient, nil, false, "--clone OWNER/REPO") 328 if err != nil { 329 t.Errorf("error running command `repo fork`: %v", err) 330 } 331 332 assert.Equal(t, "", output.String()) 333 assert.Equal(t, output.Stderr(), "") 334 335 } 336 337 func TestRepoFork_already_forked(t *testing.T) { 338 reg := &httpmock.Registry{} 339 defer reg.StubWithFixturePath(200, "./forkResult.json")() 340 httpClient := &http.Client{Transport: reg} 341 342 _, restore := run.Stub() 343 defer restore(t) 344 345 output, err := runCommand(httpClient, nil, true, "--remote=false") 346 if err != nil { 347 t.Errorf("got unexpected error: %v", err) 348 } 349 350 r := regexp.MustCompile(`someone/REPO.*already exists`) 351 if !r.MatchString(output.Stderr()) { 352 t.Errorf("output did not match regexp /%s/\n> output\n%s\n", r, output.Stderr()) 353 return 354 } 355 356 reg.Verify(t) 357 } 358 359 func TestRepoFork_reuseRemote(t *testing.T) { 360 remotes := []*context.Remote{ 361 { 362 Remote: &git.Remote{Name: "origin", FetchURL: &url.URL{}}, 363 Repo: ghrepo.New("someone", "REPO"), 364 }, 365 { 366 Remote: &git.Remote{Name: "upstream", FetchURL: &url.URL{}}, 367 Repo: ghrepo.New("OWNER", "REPO"), 368 }, 369 } 370 reg := &httpmock.Registry{} 371 defer reg.StubWithFixturePath(200, "./forkResult.json")() 372 httpClient := &http.Client{Transport: reg} 373 374 output, err := runCommand(httpClient, remotes, true, "--remote") 375 if err != nil { 376 t.Errorf("got unexpected error: %v", err) 377 } 378 r := regexp.MustCompile(`Using existing remote.*origin`) 379 if !r.MatchString(output.Stderr()) { 380 t.Errorf("output did not match: %q", output.Stderr()) 381 return 382 } 383 reg.Verify(t) 384 } 385 386 func TestRepoFork_in_parent(t *testing.T) { 387 reg := &httpmock.Registry{} 388 defer reg.StubWithFixturePath(200, "./forkResult.json")() 389 httpClient := &http.Client{Transport: reg} 390 391 _, restore := run.Stub() 392 defer restore(t) 393 defer stubSince(2 * time.Second)() 394 395 output, err := runCommand(httpClient, nil, true, "--remote=false") 396 if err != nil { 397 t.Errorf("error running command `repo fork`: %v", err) 398 } 399 400 assert.Equal(t, "", output.String()) 401 402 r := regexp.MustCompile(`Created fork.*someone/REPO`) 403 if !r.MatchString(output.Stderr()) { 404 t.Errorf("output did not match regexp /%s/\n> output\n%s\n", r, output) 405 return 406 } 407 reg.Verify(t) 408 } 409 410 func TestRepoFork_outside(t *testing.T) { 411 tests := []struct { 412 name string 413 args string 414 }{ 415 { 416 name: "url arg", 417 args: "--clone=false http://github.com/OWNER/REPO.git", 418 }, 419 { 420 name: "full name arg", 421 args: "--clone=false OWNER/REPO", 422 }, 423 } 424 for _, tt := range tests { 425 t.Run(tt.name, func(t *testing.T) { 426 defer stubSince(2 * time.Second)() 427 reg := &httpmock.Registry{} 428 defer reg.StubWithFixturePath(200, "./forkResult.json")() 429 httpClient := &http.Client{Transport: reg} 430 431 output, err := runCommand(httpClient, nil, true, tt.args) 432 if err != nil { 433 t.Errorf("error running command `repo fork`: %v", err) 434 } 435 436 assert.Equal(t, "", output.String()) 437 438 r := regexp.MustCompile(`Created fork.*someone/REPO`) 439 if !r.MatchString(output.Stderr()) { 440 t.Errorf("output did not match regexp /%s/\n> output\n%s\n", r, output) 441 return 442 } 443 reg.Verify(t) 444 }) 445 } 446 } 447 448 func TestRepoFork_in_parent_yes(t *testing.T) { 449 defer stubSince(2 * time.Second)() 450 reg := &httpmock.Registry{} 451 defer reg.StubWithFixturePath(200, "./forkResult.json")() 452 httpClient := &http.Client{Transport: reg} 453 454 cs, restore := run.Stub() 455 defer restore(t) 456 457 cs.Register(`git remote add -f fork https://github\.com/someone/REPO\.git`, 0, "") 458 459 output, err := runCommand(httpClient, nil, true, "--remote --remote-name=fork") 460 if err != nil { 461 t.Errorf("error running command `repo fork`: %v", err) 462 } 463 464 assert.Equal(t, "", output.String()) 465 //nolint:staticcheck // prefer exact matchers over ExpectLines 466 test.ExpectLines(t, output.Stderr(), 467 "Created fork.*someone/REPO", 468 "Added remote.*fork") 469 reg.Verify(t) 470 } 471 472 func TestRepoFork_outside_yes(t *testing.T) { 473 defer stubSince(2 * time.Second)() 474 reg := &httpmock.Registry{} 475 defer reg.StubWithFixturePath(200, "./forkResult.json")() 476 httpClient := &http.Client{Transport: reg} 477 478 cs, restore := run.Stub() 479 defer restore(t) 480 481 cs.Register(`git clone https://github\.com/someone/REPO\.git`, 0, "") 482 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 483 484 output, err := runCommand(httpClient, nil, true, "--clone OWNER/REPO") 485 if err != nil { 486 t.Errorf("error running command `repo fork`: %v", err) 487 } 488 489 assert.Equal(t, "", output.String()) 490 //nolint:staticcheck // prefer exact matchers over ExpectLines 491 test.ExpectLines(t, output.Stderr(), 492 "Created fork.*someone/REPO", 493 "Cloned fork") 494 reg.Verify(t) 495 } 496 497 func TestRepoFork_ForkAlreadyExistsAndCloneNonTty(t *testing.T) { 498 defer stubSince(2 * time.Minute)() 499 reg := &httpmock.Registry{} 500 defer reg.StubWithFixturePath(200, "./forkResult.json")() 501 httpClient := &http.Client{Transport: reg} 502 503 cs, restore := run.Stub() 504 defer restore(t) 505 506 cs.Register(`git clone https://github\.com/someone/REPO\.git`, 0, "") 507 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 508 509 output, err := runCommand(httpClient, nil, false, "--clone OWNER/REPO") 510 if err != nil { 511 t.Errorf("error running command `repo fork`: %v", err) 512 } 513 514 assert.Equal(t, "", output.String()) 515 //nolint:staticcheck // prefer exact matchers over ExpectLines 516 test.ExpectLines(t, output.Stderr(), 517 "someone/REPO.*already exists") 518 reg.Verify(t) 519 } 520 521 func TestRepoFork_outside_survey_yes(t *testing.T) { 522 defer stubSince(2 * time.Second)() 523 reg := &httpmock.Registry{} 524 defer reg.StubWithFixturePath(200, "./forkResult.json")() 525 httpClient := &http.Client{Transport: reg} 526 527 cs, restore := run.Stub() 528 defer restore(t) 529 530 cs.Register(`git clone https://github\.com/someone/REPO\.git`, 0, "") 531 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 532 533 defer prompt.StubConfirm(true)() 534 535 output, err := runCommand(httpClient, nil, true, "OWNER/REPO") 536 if err != nil { 537 t.Errorf("error running command `repo fork`: %v", err) 538 } 539 540 assert.Equal(t, "", output.String()) 541 //nolint:staticcheck // prefer exact matchers over ExpectLines 542 test.ExpectLines(t, output.Stderr(), 543 "Created fork.*someone/REPO", 544 "Cloned fork") 545 reg.Verify(t) 546 } 547 548 func TestRepoFork_outside_survey_no(t *testing.T) { 549 defer stubSince(2 * time.Second)() 550 reg := &httpmock.Registry{} 551 defer reg.StubWithFixturePath(200, "./forkResult.json")() 552 httpClient := &http.Client{Transport: reg} 553 554 _, restore := run.Stub() 555 defer restore(t) 556 557 defer prompt.StubConfirm(false)() 558 559 output, err := runCommand(httpClient, nil, true, "OWNER/REPO") 560 if err != nil { 561 t.Errorf("error running command `repo fork`: %v", err) 562 } 563 564 assert.Equal(t, "", output.String()) 565 566 r := regexp.MustCompile(`Created fork.*someone/REPO`) 567 if !r.MatchString(output.Stderr()) { 568 t.Errorf("output did not match regexp /%s/\n> output\n%s\n", r, output) 569 return 570 } 571 reg.Verify(t) 572 } 573 574 func TestRepoFork_in_parent_survey_yes(t *testing.T) { 575 reg := &httpmock.Registry{} 576 defer reg.StubWithFixturePath(200, "./forkResult.json")() 577 httpClient := &http.Client{Transport: reg} 578 defer stubSince(2 * time.Second)() 579 580 cs, restore := run.Stub() 581 defer restore(t) 582 583 cs.Register(`git remote add -f fork https://github\.com/someone/REPO\.git`, 0, "") 584 585 defer prompt.StubConfirm(true)() 586 587 output, err := runCommand(httpClient, nil, true, "--remote-name=fork") 588 if err != nil { 589 t.Errorf("error running command `repo fork`: %v", err) 590 } 591 592 assert.Equal(t, "", output.String()) 593 594 //nolint:staticcheck // prefer exact matchers over ExpectLines 595 test.ExpectLines(t, output.Stderr(), 596 "Created fork.*someone/REPO", 597 "Added remote.*fork") 598 reg.Verify(t) 599 } 600 601 func TestRepoFork_in_parent_survey_no(t *testing.T) { 602 reg := &httpmock.Registry{} 603 defer reg.Verify(t) 604 defer reg.StubWithFixturePath(200, "./forkResult.json")() 605 httpClient := &http.Client{Transport: reg} 606 defer stubSince(2 * time.Second)() 607 608 _, restore := run.Stub() 609 defer restore(t) 610 611 defer prompt.StubConfirm(false)() 612 613 output, err := runCommand(httpClient, nil, true, "") 614 if err != nil { 615 t.Errorf("error running command `repo fork`: %v", err) 616 } 617 618 assert.Equal(t, "", output.String()) 619 620 r := regexp.MustCompile(`Created fork.*someone/REPO`) 621 if !r.MatchString(output.Stderr()) { 622 t.Errorf("output did not match regexp /%s/\n> output\n%s\n", r, output) 623 return 624 } 625 } 626 627 func Test_RepoFork_gitFlags(t *testing.T) { 628 defer stubSince(2 * time.Second)() 629 reg := &httpmock.Registry{} 630 defer reg.StubWithFixturePath(200, "./forkResult.json")() 631 httpClient := &http.Client{Transport: reg} 632 633 cs, cmdTeardown := run.Stub() 634 defer cmdTeardown(t) 635 636 cs.Register(`git clone --depth 1 https://github.com/someone/REPO.git`, 0, "") 637 cs.Register(`git -C REPO remote add -f upstream https://github.com/OWNER/REPO.git`, 0, "") 638 639 output, err := runCommand(httpClient, nil, false, "--clone OWNER/REPO -- --depth 1") 640 if err != nil { 641 t.Errorf("error running command `repo fork`: %v", err) 642 } 643 644 assert.Equal(t, "", output.String()) 645 assert.Equal(t, output.Stderr(), "") 646 reg.Verify(t) 647 } 648 649 func Test_RepoFork_flagError(t *testing.T) { 650 _, err := runCommand(nil, nil, true, "--depth 1 OWNER/REPO") 651 if err == nil || err.Error() != "unknown flag: --depth\nSeparate git clone flags with '--'." { 652 t.Errorf("unexpected error %v", err) 653 } 654 } 655 656 func TestRepoFork_in_parent_match_protocol(t *testing.T) { 657 defer stubSince(2 * time.Second)() 658 reg := &httpmock.Registry{} 659 defer reg.Verify(t) 660 defer reg.StubWithFixturePath(200, "./forkResult.json")() 661 httpClient := &http.Client{Transport: reg} 662 663 cs, restore := run.Stub() 664 defer restore(t) 665 666 cs.Register(`git remote add -f fork git@github\.com:someone/REPO\.git`, 0, "") 667 668 remotes := []*context.Remote{ 669 { 670 Remote: &git.Remote{Name: "origin", PushURL: &url.URL{ 671 Scheme: "ssh", 672 }}, 673 Repo: ghrepo.New("OWNER", "REPO"), 674 }, 675 } 676 677 output, err := runCommand(httpClient, remotes, true, "--remote --remote-name=fork") 678 if err != nil { 679 t.Errorf("error running command `repo fork`: %v", err) 680 } 681 682 assert.Equal(t, "", output.String()) 683 684 //nolint:staticcheck // prefer exact matchers over ExpectLines 685 test.ExpectLines(t, output.Stderr(), 686 "Created fork.*someone/REPO", 687 "Added remote.*fork") 688 } 689 690 func stubSince(d time.Duration) func() { 691 originalSince := Since 692 Since = func(t time.Time) time.Duration { 693 return d 694 } 695 return func() { 696 Since = originalSince 697 } 698 }