github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/pr/comment/comment_test.go (about)

     1  package comment
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/ungtb10d/cli/v2/api"
    12  	"github.com/ungtb10d/cli/v2/internal/browser"
    13  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
    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  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  func TestNewCmdComment(t *testing.T) {
    24  	tmpFile := filepath.Join(t.TempDir(), "my-body.md")
    25  	err := os.WriteFile(tmpFile, []byte("a body from file"), 0600)
    26  	require.NoError(t, err)
    27  
    28  	tests := []struct {
    29  		name     string
    30  		input    string
    31  		stdin    string
    32  		output   shared.CommentableOptions
    33  		wantsErr bool
    34  	}{
    35  		{
    36  			name:  "no arguments",
    37  			input: "",
    38  			output: shared.CommentableOptions{
    39  				Interactive: true,
    40  				InputType:   0,
    41  				Body:        "",
    42  			},
    43  			wantsErr: false,
    44  		},
    45  		{
    46  			name:     "two arguments",
    47  			input:    "1 2",
    48  			output:   shared.CommentableOptions{},
    49  			wantsErr: true,
    50  		},
    51  		{
    52  			name:  "pr number",
    53  			input: "1",
    54  			output: shared.CommentableOptions{
    55  				Interactive: true,
    56  				InputType:   0,
    57  				Body:        "",
    58  			},
    59  			wantsErr: false,
    60  		},
    61  		{
    62  			name:  "pr url",
    63  			input: "https://github.com/OWNER/REPO/pull/12",
    64  			output: shared.CommentableOptions{
    65  				Interactive: true,
    66  				InputType:   0,
    67  				Body:        "",
    68  			},
    69  			wantsErr: false,
    70  		},
    71  		{
    72  			name:  "pr branch",
    73  			input: "branch-name",
    74  			output: shared.CommentableOptions{
    75  				Interactive: true,
    76  				InputType:   0,
    77  				Body:        "",
    78  			},
    79  			wantsErr: false,
    80  		},
    81  		{
    82  			name:  "body flag",
    83  			input: "1 --body test",
    84  			output: shared.CommentableOptions{
    85  				Interactive: false,
    86  				InputType:   shared.InputTypeInline,
    87  				Body:        "test",
    88  			},
    89  			wantsErr: false,
    90  		},
    91  		{
    92  			name:  "body from stdin",
    93  			input: "1 --body-file -",
    94  			stdin: "this is on standard input",
    95  			output: shared.CommentableOptions{
    96  				Interactive: false,
    97  				InputType:   shared.InputTypeInline,
    98  				Body:        "this is on standard input",
    99  			},
   100  			wantsErr: false,
   101  		},
   102  		{
   103  			name:  "body from file",
   104  			input: fmt.Sprintf("1 --body-file '%s'", tmpFile),
   105  			output: shared.CommentableOptions{
   106  				Interactive: false,
   107  				InputType:   shared.InputTypeInline,
   108  				Body:        "a body from file",
   109  			},
   110  			wantsErr: false,
   111  		},
   112  		{
   113  			name:  "editor flag",
   114  			input: "1 --editor",
   115  			output: shared.CommentableOptions{
   116  				Interactive: false,
   117  				InputType:   shared.InputTypeEditor,
   118  				Body:        "",
   119  			},
   120  			wantsErr: false,
   121  		},
   122  		{
   123  			name:  "web flag",
   124  			input: "1 --web",
   125  			output: shared.CommentableOptions{
   126  				Interactive: false,
   127  				InputType:   shared.InputTypeWeb,
   128  				Body:        "",
   129  			},
   130  			wantsErr: false,
   131  		},
   132  		{
   133  			name:     "body and body-file flags",
   134  			input:    "1 --body 'test' --body-file 'test-file.txt'",
   135  			output:   shared.CommentableOptions{},
   136  			wantsErr: true,
   137  		},
   138  		{
   139  			name:     "editor and web flags",
   140  			input:    "1 --editor --web",
   141  			output:   shared.CommentableOptions{},
   142  			wantsErr: true,
   143  		},
   144  		{
   145  			name:     "editor and body flags",
   146  			input:    "1 --editor --body test",
   147  			output:   shared.CommentableOptions{},
   148  			wantsErr: true,
   149  		},
   150  		{
   151  			name:     "web and body flags",
   152  			input:    "1 --web --body test",
   153  			output:   shared.CommentableOptions{},
   154  			wantsErr: true,
   155  		},
   156  		{
   157  			name:     "editor, web, and body flags",
   158  			input:    "1 --editor --web --body test",
   159  			output:   shared.CommentableOptions{},
   160  			wantsErr: true,
   161  		},
   162  	}
   163  
   164  	for _, tt := range tests {
   165  		t.Run(tt.name, func(t *testing.T) {
   166  			ios, stdin, _, _ := iostreams.Test()
   167  			ios.SetStdoutTTY(true)
   168  			ios.SetStdinTTY(true)
   169  			ios.SetStderrTTY(true)
   170  
   171  			if tt.stdin != "" {
   172  				_, _ = stdin.WriteString(tt.stdin)
   173  			}
   174  
   175  			f := &cmdutil.Factory{
   176  				IOStreams: ios,
   177  				Browser:   &browser.Stub{},
   178  			}
   179  
   180  			argv, err := shlex.Split(tt.input)
   181  			assert.NoError(t, err)
   182  
   183  			var gotOpts *shared.CommentableOptions
   184  			cmd := NewCmdComment(f, func(opts *shared.CommentableOptions) error {
   185  				gotOpts = opts
   186  				return nil
   187  			})
   188  			cmd.Flags().BoolP("help", "x", false, "")
   189  
   190  			cmd.SetArgs(argv)
   191  			cmd.SetIn(&bytes.Buffer{})
   192  			cmd.SetOut(&bytes.Buffer{})
   193  			cmd.SetErr(&bytes.Buffer{})
   194  
   195  			_, err = cmd.ExecuteC()
   196  			if tt.wantsErr {
   197  				assert.Error(t, err)
   198  				return
   199  			}
   200  
   201  			assert.NoError(t, err)
   202  			assert.Equal(t, tt.output.Interactive, gotOpts.Interactive)
   203  			assert.Equal(t, tt.output.InputType, gotOpts.InputType)
   204  			assert.Equal(t, tt.output.Body, gotOpts.Body)
   205  		})
   206  	}
   207  }
   208  
   209  func Test_commentRun(t *testing.T) {
   210  	tests := []struct {
   211  		name      string
   212  		input     *shared.CommentableOptions
   213  		httpStubs func(*testing.T, *httpmock.Registry)
   214  		stdout    string
   215  		stderr    string
   216  	}{
   217  		{
   218  			name: "interactive editor",
   219  			input: &shared.CommentableOptions{
   220  				Interactive: true,
   221  				InputType:   0,
   222  				Body:        "",
   223  
   224  				InteractiveEditSurvey: func(string) (string, error) { return "comment body", nil },
   225  				ConfirmSubmitSurvey:   func() (bool, error) { return true, nil },
   226  			},
   227  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   228  				mockCommentCreate(t, reg)
   229  			},
   230  			stdout: "https://github.com/OWNER/REPO/pull/123#issuecomment-456\n",
   231  		},
   232  		{
   233  			name: "interactive editor with edit last",
   234  			input: &shared.CommentableOptions{
   235  				Interactive: true,
   236  				InputType:   0,
   237  				Body:        "",
   238  				EditLast:    true,
   239  
   240  				InteractiveEditSurvey: func(string) (string, error) { return "comment body", nil },
   241  				ConfirmSubmitSurvey:   func() (bool, error) { return true, nil },
   242  			},
   243  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   244  				mockCommentUpdate(t, reg)
   245  			},
   246  			stdout: "https://github.com/OWNER/REPO/pull/123#issuecomment-111\n",
   247  		},
   248  		{
   249  			name: "non-interactive web",
   250  			input: &shared.CommentableOptions{
   251  				Interactive: false,
   252  				InputType:   shared.InputTypeWeb,
   253  				Body:        "",
   254  
   255  				OpenInBrowser: func(string) error { return nil },
   256  			},
   257  			stderr: "Opening github.com/OWNER/REPO/pull/123 in your browser.\n",
   258  		},
   259  		{
   260  			name: "non-interactive web with edit last",
   261  			input: &shared.CommentableOptions{
   262  				Interactive: false,
   263  				InputType:   shared.InputTypeWeb,
   264  				Body:        "",
   265  				EditLast:    true,
   266  
   267  				OpenInBrowser: func(string) error { return nil },
   268  			},
   269  			stderr: "Opening github.com/OWNER/REPO/pull/123 in your browser.\n",
   270  		},
   271  		{
   272  			name: "non-interactive editor",
   273  			input: &shared.CommentableOptions{
   274  				Interactive: false,
   275  				InputType:   shared.InputTypeEditor,
   276  				Body:        "",
   277  
   278  				EditSurvey: func(string) (string, error) { return "comment body", nil },
   279  			},
   280  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   281  				mockCommentCreate(t, reg)
   282  			},
   283  			stdout: "https://github.com/OWNER/REPO/pull/123#issuecomment-456\n",
   284  		},
   285  		{
   286  			name: "non-interactive editor with edit last",
   287  			input: &shared.CommentableOptions{
   288  				Interactive: false,
   289  				InputType:   shared.InputTypeEditor,
   290  				Body:        "",
   291  				EditLast:    true,
   292  
   293  				EditSurvey: func(string) (string, error) { return "comment body", nil },
   294  			},
   295  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   296  				mockCommentUpdate(t, reg)
   297  			},
   298  			stdout: "https://github.com/OWNER/REPO/pull/123#issuecomment-111\n",
   299  		},
   300  		{
   301  			name: "non-interactive inline",
   302  			input: &shared.CommentableOptions{
   303  				Interactive: false,
   304  				InputType:   shared.InputTypeInline,
   305  				Body:        "comment body",
   306  			},
   307  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   308  				mockCommentCreate(t, reg)
   309  			},
   310  			stdout: "https://github.com/OWNER/REPO/pull/123#issuecomment-456\n",
   311  		},
   312  		{
   313  			name: "non-interactive inline with edit last",
   314  			input: &shared.CommentableOptions{
   315  				Interactive: false,
   316  				InputType:   shared.InputTypeInline,
   317  				Body:        "comment body",
   318  				EditLast:    true,
   319  			},
   320  			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
   321  				mockCommentUpdate(t, reg)
   322  			},
   323  			stdout: "https://github.com/OWNER/REPO/pull/123#issuecomment-111\n",
   324  		},
   325  	}
   326  	for _, tt := range tests {
   327  		ios, _, stdout, stderr := iostreams.Test()
   328  		ios.SetStdoutTTY(true)
   329  		ios.SetStdinTTY(true)
   330  		ios.SetStderrTTY(true)
   331  
   332  		reg := &httpmock.Registry{}
   333  		defer reg.Verify(t)
   334  		if tt.httpStubs != nil {
   335  			tt.httpStubs(t, reg)
   336  		}
   337  
   338  		httpClient := func() (*http.Client, error) { return &http.Client{Transport: reg}, nil }
   339  
   340  		tt.input.IO = ios
   341  		tt.input.HttpClient = httpClient
   342  		tt.input.RetrieveCommentable = func() (shared.Commentable, ghrepo.Interface, error) {
   343  			return &api.PullRequest{
   344  				Number: 123,
   345  				URL:    "https://github.com/OWNER/REPO/pull/123",
   346  				Comments: api.Comments{Nodes: []api.Comment{
   347  					{ID: "id1", Author: api.Author{Login: "octocat"}, URL: "https://github.com/OWNER/REPO/pull/123#issuecomment-111", ViewerDidAuthor: true},
   348  					{ID: "id2", Author: api.Author{Login: "monalisa"}, URL: "https://github.com/OWNER/REPO/pull/123#issuecomment-222"},
   349  				}},
   350  			}, ghrepo.New("OWNER", "REPO"), nil
   351  		}
   352  
   353  		t.Run(tt.name, func(t *testing.T) {
   354  			err := shared.CommentableRun(tt.input)
   355  			assert.NoError(t, err)
   356  			assert.Equal(t, tt.stdout, stdout.String())
   357  			assert.Equal(t, tt.stderr, stderr.String())
   358  		})
   359  	}
   360  }
   361  
   362  func mockCommentCreate(t *testing.T, reg *httpmock.Registry) {
   363  	reg.Register(
   364  		httpmock.GraphQL(`mutation CommentCreate\b`),
   365  		httpmock.GraphQLMutation(`
   366  		{ "data": { "addComment": { "commentEdge": { "node": {
   367  			"url": "https://github.com/OWNER/REPO/pull/123#issuecomment-456"
   368  		} } } } }`,
   369  			func(inputs map[string]interface{}) {
   370  				assert.Equal(t, "comment body", inputs["body"])
   371  			}),
   372  	)
   373  }
   374  
   375  func mockCommentUpdate(t *testing.T, reg *httpmock.Registry) {
   376  	reg.Register(
   377  		httpmock.GraphQL(`mutation CommentUpdate\b`),
   378  		httpmock.GraphQLMutation(`
   379  		{ "data": { "updateIssueComment": { "issueComment": {
   380  			"url": "https://github.com/OWNER/REPO/pull/123#issuecomment-111"
   381  		} } } }`,
   382  			func(inputs map[string]interface{}) {
   383  				assert.Equal(t, "id1", inputs["id"])
   384  				assert.Equal(t, "comment body", inputs["body"])
   385  			}),
   386  	)
   387  }