package view import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "regexp" "testing" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/browser" fd "github.com/cli/cli/v2/internal/featuredetection" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/internal/run" "github.com/cli/cli/v2/pkg/cmd/pr/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/jsonfieldstest" "github.com/cli/cli/v2/test" "github.com/google/shlex" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestJSONFields(t *testing.T) { jsonfieldstest.ExpectCommandToSupportJSONFields(t, NewCmdView, []string{ "additions", "assignees", "author", "autoMergeRequest", "baseRefName", "baseRefOid", "body", "changedFiles", "closed", "closedAt", "closingIssuesReferences", "comments", "commits", "createdAt", "deletions", "files", "fullDatabaseId", "headRefName", "headRefOid", "headRepository", "headRepositoryOwner", "id", "isCrossRepository", "isDraft", "labels", "latestReviews", "maintainerCanModify", "mergeCommit", "mergeStateStatus", "mergeable", "mergedAt", "mergedBy", "milestone", "number", "potentialMergeCommit", "projectCards", "projectItems", "reactionGroups", "reviewDecision", "reviewRequests", "reviews", "state", "statusCheckRollup", "title", "updatedAt", "url", }) } func Test_NewCmdView(t *testing.T) { tests := []struct { name string args string isTTY bool want ViewOptions wantErr string }{ { name: "number argument", args: "123", isTTY: true, want: ViewOptions{ SelectorArg: "123", BrowserMode: false, }, }, { name: "no argument", args: "", isTTY: true, want: ViewOptions{ SelectorArg: "", BrowserMode: false, }, }, { name: "web mode", args: "123 -w", isTTY: true, want: ViewOptions{ SelectorArg: "123", BrowserMode: true, }, }, { name: "no argument with --repo override", args: "-R owner/repo", isTTY: true, wantErr: "argument required when using the --repo flag", }, { name: "comments", args: "123 -c", isTTY: true, want: ViewOptions{ SelectorArg: "123", Comments: true, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ios, _, _, _ := iostreams.Test() ios.SetStdoutTTY(tt.isTTY) ios.SetStdinTTY(tt.isTTY) ios.SetStderrTTY(tt.isTTY) f := &cmdutil.Factory{ IOStreams: ios, } var opts *ViewOptions cmd := NewCmdView(f, func(o *ViewOptions) error { opts = o return nil }) cmd.PersistentFlags().StringP("repo", "R", "", "") argv, err := shlex.Split(tt.args) require.NoError(t, err) cmd.SetArgs(argv) cmd.SetIn(&bytes.Buffer{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) _, err = cmd.ExecuteC() if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) return } else { require.NoError(t, err) } assert.Equal(t, tt.want.SelectorArg, opts.SelectorArg) }) } } func runCommand(rt http.RoundTripper, branch string, isTTY bool, cli string) (*test.CmdOut, error) { ios, _, stdout, stderr := iostreams.Test() ios.SetStdoutTTY(isTTY) ios.SetStdinTTY(isTTY) ios.SetStderrTTY(isTTY) browser := &browser.Stub{} factory := &cmdutil.Factory{ IOStreams: ios, Browser: browser, HttpClient: func() (*http.Client, error) { return &http.Client{Transport: rt}, nil }, } cmd := NewCmdView(factory, nil) argv, err := shlex.Split(cli) if err != nil { return nil, err } cmd.SetArgs(argv) cmd.SetIn(&bytes.Buffer{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) _, err = cmd.ExecuteC() return &test.CmdOut{ OutBuf: stdout, ErrBuf: stderr, BrowsedURL: browser.BrowsedURL(), }, err } // hack for compatibility with old JSON fixture files func prFromFixtures(fixtures map[string]string) (*api.PullRequest, error) { var response struct { Data struct { Repository struct { PullRequest *api.PullRequest } } } ff, err := os.Open(fixtures["PullRequestByNumber"]) if err != nil { return nil, err } defer ff.Close() dec := json.NewDecoder(ff) err = dec.Decode(&response) if err != nil { return nil, err } for name := range fixtures { switch name { case "PullRequestByNumber": case "ReviewsForPullRequest", "CommentsForPullRequest": ff, err := os.Open(fixtures[name]) if err != nil { return nil, err } defer ff.Close() dec := json.NewDecoder(ff) err = dec.Decode(&response) if err != nil { return nil, err } default: return nil, fmt.Errorf("unrecognized fixture type: %q", name) } } return response.Data.Repository.PullRequest, nil } func TestPRView_Preview_nontty(t *testing.T) { tests := map[string]struct { branch string args string fixtures map[string]string expectedOutputs []string }{ "Open PR without metadata": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreview.json", }, expectedOutputs: []string{ `title:\tBlueberries are from a fork\n`, `state:\tOPEN\n`, `author:\tnobody\n`, `labels:\t\n`, `assignees:\t\n`, `reviewers:\t\n`, `projects:\t\n`, `milestone:\t\n`, `url:\thttps://github.com/OWNER/REPO/pull/12\n`, `additions:\t100\n`, `deletions:\t10\n`, `number:\t12\n`, `blueberries taste good`, }, }, "Open PR with metadata by number": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithMetadataByNumber.json", }, expectedOutputs: []string{ `title:\tBlueberries are from a fork\n`, `reviewers:\t1 \(Requested\)\n`, `assignees:\tmarseilles, monaco\n`, `labels:\tClosed: Duplicate, Closed: Won't Fix, help wanted, Status: In Progress, Type: Bug\n`, `projects:\tv2 Project 1 \(No Status\), v2 Project 2 \(Done\), Project 1 \(column A\), Project 2 \(column B\), Project 3 \(column C\), Project 4 \(Awaiting triage\)\n`, `milestone:\tuluru\n`, `\*\*blueberries taste good\*\*`, }, }, "Open PR with reviewers by number": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithReviewersByNumber.json", "ReviewsForPullRequest": "./fixtures/prViewPreviewManyReviews.json", }, expectedOutputs: []string{ `title:\tBlueberries are from a fork\n`, `state:\tOPEN\n`, `author:\tnobody\n`, `labels:\t\n`, `assignees:\t\n`, `projects:\t\n`, `milestone:\t\n`, `additions:\t100\n`, `deletions:\t10\n`, `reviewers:\tDEF \(Commented\), def \(Changes requested\), ghost \(Approved\), hubot \(Commented\), xyz \(Approved\), 123 \(Requested\), abc \(Requested\), my-org\/team-1 \(Requested\)\n`, `\*\*blueberries taste good\*\*`, }, }, "Closed PR": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewClosedState.json", }, expectedOutputs: []string{ `state:\tCLOSED\n`, `author:\tnobody\n`, `labels:\t\n`, `assignees:\t\n`, `reviewers:\t\n`, `projects:\t\n`, `milestone:\t\n`, `additions:\t100\n`, `deletions:\t10\n`, `\*\*blueberries taste good\*\*`, }, }, "Merged PR": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewMergedState.json", }, expectedOutputs: []string{ `state:\tMERGED\n`, `author:\tnobody\n`, `labels:\t\n`, `assignees:\t\n`, `reviewers:\t\n`, `projects:\t\n`, `milestone:\t\n`, `additions:\t100\n`, `deletions:\t10\n`, `\*\*blueberries taste good\*\*`, }, }, "Draft PR": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewDraftState.json", }, expectedOutputs: []string{ `title:\tBlueberries are from a fork\n`, `state:\tDRAFT\n`, `author:\tnobody\n`, `labels:`, `assignees:`, `reviewers:`, `projects:`, `milestone:`, `additions:\t100\n`, `deletions:\t10\n`, `\*\*blueberries taste good\*\*`, }, }, "PR with auto-merge enabled": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithAutoMergeEnabled.json", }, expectedOutputs: []string{ `title:\tBlueberries are from a fork\n`, `state:\tOPEN\n`, `author:\tnobody\n`, `labels:\t\n`, `assignees:\t\n`, `projects:\t\n`, `milestone:\t\n`, `additions:\t100\n`, `deletions:\t10\n`, `auto-merge:\tenabled\thubot\tsquash\n`, }, }, "PR with nil project": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithNilProject.json", }, expectedOutputs: []string{ `projects:\t\n`, }, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { http := &httpmock.Registry{} defer http.Verify(t) pr, err := prFromFixtures(tc.fixtures) require.NoError(t, err) shared.StubFinderForRunCommandStyleTests(t, "12", pr, ghrepo.New("OWNER", "REPO")) output, err := runCommand(http, tc.branch, false, tc.args) if err != nil { t.Errorf("error running command `%v`: %v", tc.args, err) } assert.Equal(t, "", output.Stderr()) //nolint:staticcheck // prefer exact matchers over ExpectLines test.ExpectLines(t, output.String(), tc.expectedOutputs...) }) } } func TestPRView_Preview(t *testing.T) { tests := map[string]struct { branch string args string fixtures map[string]string expectedOutputs []string }{ "Open PR without metadata": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreview.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Open PR with metadata by number": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithMetadataByNumber.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10`, `Reviewers:.*1 \(.*Requested.*\)\n`, `Assignees:.*marseilles, monaco\n`, `Labels:.*Closed: Duplicate, Closed: Won't Fix, help wanted, Status: In Progress, Type: Bug\n`, `Projects:.*v2 Project 1 \(No Status\), v2 Project 2 \(Done\), Project 1 \(column A\), Project 2 \(column B\), Project 3 \(column C\), Project 4 \(Awaiting triage\)\n`, `Milestone:.*uluru\n`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Open PR with reviewers by number": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithReviewersByNumber.json", "ReviewsForPullRequest": "./fixtures/prViewPreviewManyReviews.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Reviewers: DEF \(Commented\), def \(Changes requested\), ghost \(Approved\), hubot \(Commented\), xyz \(Approved\), 123 \(Requested\), abc \(Requested\), my-org\/team-1 \(Requested\)`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Closed PR": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewClosedState.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Closed.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Merged PR": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewMergedState.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Merged.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Draft PR": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewDraftState.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Draft.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Open PR with all checks passing": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithAllChecksPassing.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10 • ✓ Checks passing`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Open PR with all checks failing": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithAllChecksFailing.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10 • × All checks failing`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Open PR with some checks failing": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithSomeChecksFailing.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10 • × 1/2 checks failing`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Open PR with some checks pending": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithSomeChecksPending.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10 • - Checks pending`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "Open PR with no checks": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithNoChecks.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12`, `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `.+100.-10 • No checks`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "PR with auto-merge enabled": { branch: "master", args: "12", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewWithAutoMergeEnabled.json", }, expectedOutputs: []string{ `Blueberries are from a fork OWNER/REPO#12\n`, `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, `Auto-merge:.*enabled.* by hubot, using squash and merge`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { http := &httpmock.Registry{} defer http.Verify(t) pr, err := prFromFixtures(tc.fixtures) require.NoError(t, err) shared.StubFinderForRunCommandStyleTests(t, "12", pr, ghrepo.New("OWNER", "REPO")) output, err := runCommand(http, tc.branch, true, tc.args) if err != nil { t.Errorf("error running command `%v`: %v", tc.args, err) } assert.Equal(t, "", output.Stderr()) out := output.String() timeRE := regexp.MustCompile(`\d+ years`) out = timeRE.ReplaceAllString(out, "X years") //nolint:staticcheck // prefer exact matchers over ExpectLines test.ExpectLines(t, out, tc.expectedOutputs...) }) } } func TestPRView_web_currentBranch(t *testing.T) { http := &httpmock.Registry{} defer http.Verify(t) shared.StubFinderForRunCommandStyleTests(t, "", &api.PullRequest{URL: "https://github.com/OWNER/REPO/pull/10"}, ghrepo.New("OWNER", "REPO")) _, cmdTeardown := run.Stub() defer cmdTeardown(t) output, err := runCommand(http, "blueberries", true, "-w") if err != nil { t.Errorf("error running command `pr view`: %v", err) } assert.Equal(t, "", output.String()) assert.Equal(t, "Opening https://github.com/OWNER/REPO/pull/10 in your browser.\n", output.Stderr()) assert.Equal(t, "https://github.com/OWNER/REPO/pull/10", output.BrowsedURL) } func TestPRView_web_noResultsForBranch(t *testing.T) { http := &httpmock.Registry{} defer http.Verify(t) shared.StubFinderForRunCommandStyleTests(t, "", nil, nil) _, cmdTeardown := run.Stub() defer cmdTeardown(t) _, err := runCommand(http, "blueberries", true, "-w") if err == nil || err.Error() != `no pull requests found` { t.Errorf("error running command `pr view`: %v", err) } } func TestPRView_tty_Comments(t *testing.T) { tests := map[string]struct { branch string cli string fixtures map[string]string expectedOutputs []string wantsErr bool }{ "without comments flag": { branch: "master", cli: "123", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewSingleComment.json", "ReviewsForPullRequest": "./fixtures/prViewPreviewReviews.json", }, expectedOutputs: []string{ `some title OWNER/REPO#12`, `1 \x{1f615} • 2 \x{1f440} • 3 \x{2764}\x{fe0f}`, `some body`, `———————— Not showing 9 comments ————————`, `marseilles \(Collaborator\) • Jan 9, 2020 • Newest comment`, `4 \x{1f389} • 5 \x{1f604} • 6 \x{1f680}`, `Comment 5`, `Use --comments to view the full conversation`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "with comments flag": { branch: "master", cli: "123 --comments", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewSingleComment.json", "ReviewsForPullRequest": "./fixtures/prViewPreviewReviews.json", "CommentsForPullRequest": "./fixtures/prViewPreviewFullComments.json", }, expectedOutputs: []string{ `some title OWNER/REPO#12`, `some body`, `monalisa • Jan 1, 2020 • Edited`, `1 \x{1f615} • 2 \x{1f440} • 3 \x{2764}\x{fe0f} • 4 \x{1f389} • 5 \x{1f604} • 6 \x{1f680} • 7 \x{1f44e} • 8 \x{1f44d}`, `Comment 1`, `sam commented • Jan 2, 2020`, `1 \x{1f44e} • 1 \x{1f44d}`, `Review 1`, `View the full review: https://github.com/OWNER/REPO/pull/12#pullrequestreview-1`, `johnnytest \(Contributor\) • Jan 3, 2020`, `Comment 2`, `matt requested changes \(Owner\) • Jan 4, 2020`, `1 \x{1f615} • 1 \x{1f440}`, `Review 2`, `View the full review: https://github.com/OWNER/REPO/pull/12#pullrequestreview-2`, `elvisp \(Member\) • Jan 5, 2020`, `Comment 3`, `leah approved \(Member\) • Jan 6, 2020 • Edited`, `Review 3`, `View the full review: https://github.com/OWNER/REPO/pull/12#pullrequestreview-3`, `loislane \(Owner\) • Jan 7, 2020`, `Comment 4`, `louise dismissed • Jan 8, 2020`, `Review 4`, `View the full review: https://github.com/OWNER/REPO/pull/12#pullrequestreview-4`, `sam-spam • This comment has been marked as spam`, `marseilles \(Collaborator\) • Jan 9, 2020 • Newest comment`, `Comment 5`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, }, "with invalid comments flag": { branch: "master", cli: "123 --comments 3", wantsErr: true, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { http := &httpmock.Registry{} defer http.Verify(t) if len(tt.fixtures) > 0 { pr, err := prFromFixtures(tt.fixtures) require.NoError(t, err) shared.StubFinderForRunCommandStyleTests(t, "123", pr, ghrepo.New("OWNER", "REPO")) } else { shared.StubFinderForRunCommandStyleTests(t, "123", nil, nil) } output, err := runCommand(http, tt.branch, true, tt.cli) if tt.wantsErr { assert.Error(t, err) return } assert.NoError(t, err) assert.Equal(t, "", output.Stderr()) //nolint:staticcheck // prefer exact matchers over ExpectLines test.ExpectLines(t, output.String(), tt.expectedOutputs...) }) } } func TestPRView_nontty_Comments(t *testing.T) { tests := map[string]struct { branch string cli string fixtures map[string]string expectedOutputs []string wantsErr bool }{ "without comments flag": { branch: "master", cli: "123", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewSingleComment.json", "ReviewsForPullRequest": "./fixtures/prViewPreviewReviews.json", }, expectedOutputs: []string{ `title:\tsome title`, `state:\tOPEN`, `author:\tnobody`, `url:\thttps://github.com/OWNER/REPO/pull/12`, `some body`, }, }, "with comments flag": { branch: "master", cli: "123 --comments", fixtures: map[string]string{ "PullRequestByNumber": "./fixtures/prViewPreviewSingleComment.json", "ReviewsForPullRequest": "./fixtures/prViewPreviewReviews.json", "CommentsForPullRequest": "./fixtures/prViewPreviewFullComments.json", }, expectedOutputs: []string{ `author:\tmonalisa`, `association:\tnone`, `edited:\ttrue`, `status:\tnone`, `Comment 1`, `author:\tsam`, `association:\tnone`, `edited:\tfalse`, `status:\tcommented`, `Review 1`, `author:\tjohnnytest`, `association:\tcontributor`, `edited:\tfalse`, `status:\tnone`, `Comment 2`, `author:\tmatt`, `association:\towner`, `edited:\tfalse`, `status:\tchanges requested`, `Review 2`, `author:\telvisp`, `association:\tmember`, `edited:\tfalse`, `status:\tnone`, `Comment 3`, `author:\tleah`, `association:\tmember`, `edited:\ttrue`, `status:\tapproved`, `Review 3`, `author:\tloislane`, `association:\towner`, `edited:\tfalse`, `status:\tnone`, `Comment 4`, `author:\tlouise`, `association:\tnone`, `edited:\tfalse`, `status:\tdismissed`, `Review 4`, `author:\tmarseilles`, `association:\tcollaborator`, `edited:\tfalse`, `status:\tnone`, `Comment 5`, }, }, "with invalid comments flag": { branch: "master", cli: "123 --comments 3", wantsErr: true, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { http := &httpmock.Registry{} defer http.Verify(t) if len(tt.fixtures) > 0 { pr, err := prFromFixtures(tt.fixtures) require.NoError(t, err) shared.StubFinderForRunCommandStyleTests(t, "123", pr, ghrepo.New("OWNER", "REPO")) } else { shared.StubFinderForRunCommandStyleTests(t, "123", nil, nil) } output, err := runCommand(http, tt.branch, false, tt.cli) if tt.wantsErr { assert.Error(t, err) return } assert.NoError(t, err) assert.Equal(t, "", output.Stderr()) //nolint:staticcheck // prefer exact matchers over ExpectLines test.ExpectLines(t, output.String(), tt.expectedOutputs...) }) } } // TODO projectsV1Deprecation // Remove this test. func TestProjectsV1Deprecation(t *testing.T) { t.Run("when projects v1 is supported, is included in query", func(t *testing.T) { ios, _, _, _ := iostreams.Test() reg := &httpmock.Registry{} reg.Register( httpmock.GraphQL(`projectCards`), // Simulate a GraphQL error to early exit the test. httpmock.StatusStringResponse(500, ""), ) f := &cmdutil.Factory{ IOStreams: ios, HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil }, } _, cmdTeardown := run.Stub() defer cmdTeardown(t) // Ignore the error because we have no way to really stub it without // fully stubbing a GQL error structure in the request body. _ = viewRun(&ViewOptions{ IO: ios, Finder: shared.NewFinder(f), Detector: &fd.EnabledDetectorMock{}, SelectorArg: "https://github.com/cli/cli/pull/123", }) // Verify that our request contained projectCards reg.Verify(t) }) t.Run("when projects v1 is not supported, is not included in query", func(t *testing.T) { ios, _, _, _ := iostreams.Test() reg := &httpmock.Registry{} reg.Exclude( t, httpmock.GraphQL(`projectCards`), ) f := &cmdutil.Factory{ IOStreams: ios, HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil }, } _, cmdTeardown := run.Stub() defer cmdTeardown(t) // Ignore the error because we have no way to really stub it without // fully stubbing a GQL error structure in the request body. _ = viewRun(&ViewOptions{ IO: ios, Finder: shared.NewFinder(f), Detector: &fd.DisabledDetectorMock{}, SelectorArg: "https://github.com/cli/cli/pull/123", }) // Verify that our request contained projectCards reg.Verify(t) }) }