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