This completely rewrites the PR lookup mechanism so that the caller must specify the GraphQL fields to query for each PR. Additionally, this fixes some export problems with `pr view --json`. Features: - Each pr command now gets assigned a concept of a Finder. This makes it easier to stub the PR in tests without having to stub the underlying HTTP calls or git invocations. - `pr view --web` is much faster since it only fetches the "url" field. - `pr diff 123` now skips a whole API call where a whole PR was unnecessarily preloaded just to access its diff in a subsequent call. - PullRequestGraphQL query builder is now used to construct queries. - A bunch of individual commands are now freed of having to know about concepts such as BaseRepo, Branch, Config, or Remotes.
312 lines
7.3 KiB
Go
312 lines
7.3 KiB
Go
package comment
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/cli/cli/api"
|
|
"github.com/cli/cli/internal/ghrepo"
|
|
"github.com/cli/cli/pkg/cmd/pr/shared"
|
|
"github.com/cli/cli/pkg/cmdutil"
|
|
"github.com/cli/cli/pkg/httpmock"
|
|
"github.com/cli/cli/pkg/iostreams"
|
|
"github.com/google/shlex"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewCmdComment(t *testing.T) {
|
|
tmpFile := filepath.Join(t.TempDir(), "my-body.md")
|
|
err := ioutil.WriteFile(tmpFile, []byte("a body from file"), 0600)
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
stdin string
|
|
output shared.CommentableOptions
|
|
wantsErr bool
|
|
}{
|
|
{
|
|
name: "no arguments",
|
|
input: "",
|
|
output: shared.CommentableOptions{
|
|
Interactive: true,
|
|
InputType: 0,
|
|
Body: "",
|
|
},
|
|
wantsErr: false,
|
|
},
|
|
{
|
|
name: "two arguments",
|
|
input: "1 2",
|
|
output: shared.CommentableOptions{},
|
|
wantsErr: true,
|
|
},
|
|
{
|
|
name: "pr number",
|
|
input: "1",
|
|
output: shared.CommentableOptions{
|
|
Interactive: true,
|
|
InputType: 0,
|
|
Body: "",
|
|
},
|
|
wantsErr: false,
|
|
},
|
|
{
|
|
name: "pr url",
|
|
input: "https://github.com/OWNER/REPO/pull/12",
|
|
output: shared.CommentableOptions{
|
|
Interactive: true,
|
|
InputType: 0,
|
|
Body: "",
|
|
},
|
|
wantsErr: false,
|
|
},
|
|
{
|
|
name: "pr branch",
|
|
input: "branch-name",
|
|
output: shared.CommentableOptions{
|
|
Interactive: true,
|
|
InputType: 0,
|
|
Body: "",
|
|
},
|
|
wantsErr: false,
|
|
},
|
|
{
|
|
name: "body flag",
|
|
input: "1 --body test",
|
|
output: shared.CommentableOptions{
|
|
Interactive: false,
|
|
InputType: shared.InputTypeInline,
|
|
Body: "test",
|
|
},
|
|
wantsErr: false,
|
|
},
|
|
{
|
|
name: "body from stdin",
|
|
input: "1 --body-file -",
|
|
stdin: "this is on standard input",
|
|
output: shared.CommentableOptions{
|
|
Interactive: false,
|
|
InputType: shared.InputTypeInline,
|
|
Body: "this is on standard input",
|
|
},
|
|
wantsErr: false,
|
|
},
|
|
{
|
|
name: "body from file",
|
|
input: fmt.Sprintf("1 --body-file '%s'", tmpFile),
|
|
output: shared.CommentableOptions{
|
|
Interactive: false,
|
|
InputType: shared.InputTypeInline,
|
|
Body: "a body from file",
|
|
},
|
|
wantsErr: false,
|
|
},
|
|
{
|
|
name: "editor flag",
|
|
input: "1 --editor",
|
|
output: shared.CommentableOptions{
|
|
Interactive: false,
|
|
InputType: shared.InputTypeEditor,
|
|
Body: "",
|
|
},
|
|
wantsErr: false,
|
|
},
|
|
{
|
|
name: "web flag",
|
|
input: "1 --web",
|
|
output: shared.CommentableOptions{
|
|
Interactive: false,
|
|
InputType: shared.InputTypeWeb,
|
|
Body: "",
|
|
},
|
|
wantsErr: false,
|
|
},
|
|
{
|
|
name: "body and body-file flags",
|
|
input: "1 --body 'test' --body-file 'test-file.txt'",
|
|
output: shared.CommentableOptions{},
|
|
wantsErr: true,
|
|
},
|
|
{
|
|
name: "editor and web flags",
|
|
input: "1 --editor --web",
|
|
output: shared.CommentableOptions{},
|
|
wantsErr: true,
|
|
},
|
|
{
|
|
name: "editor and body flags",
|
|
input: "1 --editor --body test",
|
|
output: shared.CommentableOptions{},
|
|
wantsErr: true,
|
|
},
|
|
{
|
|
name: "web and body flags",
|
|
input: "1 --web --body test",
|
|
output: shared.CommentableOptions{},
|
|
wantsErr: true,
|
|
},
|
|
{
|
|
name: "editor, web, and body flags",
|
|
input: "1 --editor --web --body test",
|
|
output: shared.CommentableOptions{},
|
|
wantsErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
io, stdin, _, _ := iostreams.Test()
|
|
io.SetStdoutTTY(true)
|
|
io.SetStdinTTY(true)
|
|
io.SetStderrTTY(true)
|
|
|
|
if tt.stdin != "" {
|
|
_, _ = stdin.WriteString(tt.stdin)
|
|
}
|
|
|
|
f := &cmdutil.Factory{
|
|
IOStreams: io,
|
|
Browser: &cmdutil.TestBrowser{},
|
|
}
|
|
|
|
argv, err := shlex.Split(tt.input)
|
|
assert.NoError(t, err)
|
|
|
|
var gotOpts *shared.CommentableOptions
|
|
cmd := NewCmdComment(f, func(opts *shared.CommentableOptions) error {
|
|
gotOpts = opts
|
|
return nil
|
|
})
|
|
cmd.Flags().BoolP("help", "x", false, "")
|
|
|
|
cmd.SetArgs(argv)
|
|
cmd.SetIn(&bytes.Buffer{})
|
|
cmd.SetOut(&bytes.Buffer{})
|
|
cmd.SetErr(&bytes.Buffer{})
|
|
|
|
_, err = cmd.ExecuteC()
|
|
if tt.wantsErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.output.Interactive, gotOpts.Interactive)
|
|
assert.Equal(t, tt.output.InputType, gotOpts.InputType)
|
|
assert.Equal(t, tt.output.Body, gotOpts.Body)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_commentRun(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input *shared.CommentableOptions
|
|
httpStubs func(*testing.T, *httpmock.Registry)
|
|
stdout string
|
|
stderr string
|
|
}{
|
|
{
|
|
name: "interactive editor",
|
|
input: &shared.CommentableOptions{
|
|
Interactive: true,
|
|
InputType: 0,
|
|
Body: "",
|
|
|
|
InteractiveEditSurvey: func() (string, error) { return "comment body", nil },
|
|
ConfirmSubmitSurvey: func() (bool, error) { return true, nil },
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
mockCommentCreate(t, reg)
|
|
},
|
|
stdout: "https://github.com/OWNER/REPO/pull/123#issuecomment-456\n",
|
|
},
|
|
{
|
|
name: "non-interactive web",
|
|
input: &shared.CommentableOptions{
|
|
Interactive: false,
|
|
InputType: shared.InputTypeWeb,
|
|
Body: "",
|
|
|
|
OpenInBrowser: func(string) error { return nil },
|
|
},
|
|
stderr: "Opening github.com/OWNER/REPO/pull/123 in your browser.\n",
|
|
},
|
|
{
|
|
name: "non-interactive editor",
|
|
input: &shared.CommentableOptions{
|
|
Interactive: false,
|
|
InputType: shared.InputTypeEditor,
|
|
Body: "",
|
|
|
|
EditSurvey: func() (string, error) { return "comment body", nil },
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
mockCommentCreate(t, reg)
|
|
},
|
|
stdout: "https://github.com/OWNER/REPO/pull/123#issuecomment-456\n",
|
|
},
|
|
{
|
|
name: "non-interactive inline",
|
|
input: &shared.CommentableOptions{
|
|
Interactive: false,
|
|
InputType: shared.InputTypeInline,
|
|
Body: "comment body",
|
|
},
|
|
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
|
mockCommentCreate(t, reg)
|
|
},
|
|
stdout: "https://github.com/OWNER/REPO/pull/123#issuecomment-456\n",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
io, _, stdout, stderr := iostreams.Test()
|
|
io.SetStdoutTTY(true)
|
|
io.SetStdinTTY(true)
|
|
io.SetStderrTTY(true)
|
|
|
|
reg := &httpmock.Registry{}
|
|
defer reg.Verify(t)
|
|
if tt.httpStubs != nil {
|
|
tt.httpStubs(t, reg)
|
|
}
|
|
|
|
httpClient := func() (*http.Client, error) { return &http.Client{Transport: reg}, nil }
|
|
|
|
tt.input.IO = io
|
|
tt.input.HttpClient = httpClient
|
|
tt.input.RetrieveCommentable = func() (shared.Commentable, ghrepo.Interface, error) {
|
|
return &api.PullRequest{
|
|
Number: 123,
|
|
URL: "https://github.com/OWNER/REPO/pull/123",
|
|
}, ghrepo.New("OWNER", "REPO"), nil
|
|
}
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := shared.CommentableRun(tt.input)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.stdout, stdout.String())
|
|
assert.Equal(t, tt.stderr, stderr.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func mockCommentCreate(t *testing.T, reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation CommentCreate\b`),
|
|
httpmock.GraphQLMutation(`
|
|
{ "data": { "addComment": { "commentEdge": { "node": {
|
|
"url": "https://github.com/OWNER/REPO/pull/123#issuecomment-456"
|
|
} } } } }`,
|
|
func(inputs map[string]interface{}) {
|
|
assert.Equal(t, "comment body", inputs["body"])
|
|
}),
|
|
)
|
|
}
|