github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/issue/list/list_test.go (about)

     1  package list
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"regexp"
     8  	"testing"
     9  
    10  	"github.com/MakeNowJust/heredoc"
    11  	"github.com/cli/cli/internal/config"
    12  	"github.com/cli/cli/internal/ghrepo"
    13  	"github.com/cli/cli/internal/run"
    14  	prShared "github.com/cli/cli/pkg/cmd/pr/shared"
    15  	"github.com/cli/cli/pkg/cmdutil"
    16  	"github.com/cli/cli/pkg/httpmock"
    17  	"github.com/cli/cli/pkg/iostreams"
    18  	"github.com/cli/cli/test"
    19  	"github.com/google/shlex"
    20  	"github.com/stretchr/testify/assert"
    21  )
    22  
    23  func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) {
    24  	io, _, stdout, stderr := iostreams.Test()
    25  	io.SetStdoutTTY(isTTY)
    26  	io.SetStdinTTY(isTTY)
    27  	io.SetStderrTTY(isTTY)
    28  
    29  	factory := &cmdutil.Factory{
    30  		IOStreams: io,
    31  		HttpClient: func() (*http.Client, error) {
    32  			return &http.Client{Transport: rt}, nil
    33  		},
    34  		Config: func() (config.Config, error) {
    35  			return config.NewBlankConfig(), nil
    36  		},
    37  		BaseRepo: func() (ghrepo.Interface, error) {
    38  			return ghrepo.New("OWNER", "REPO"), nil
    39  		},
    40  	}
    41  
    42  	cmd := NewCmdList(factory, nil)
    43  
    44  	argv, err := shlex.Split(cli)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	cmd.SetArgs(argv)
    49  
    50  	cmd.SetIn(&bytes.Buffer{})
    51  	cmd.SetOut(ioutil.Discard)
    52  	cmd.SetErr(ioutil.Discard)
    53  
    54  	_, err = cmd.ExecuteC()
    55  	return &test.CmdOut{
    56  		OutBuf: stdout,
    57  		ErrBuf: stderr,
    58  	}, err
    59  }
    60  
    61  func TestIssueList_nontty(t *testing.T) {
    62  	http := &httpmock.Registry{}
    63  	defer http.Verify(t)
    64  
    65  	http.Register(
    66  		httpmock.GraphQL(`query IssueList\b`),
    67  		httpmock.FileResponse("./fixtures/issueList.json"))
    68  
    69  	output, err := runCommand(http, false, "")
    70  	if err != nil {
    71  		t.Errorf("error running command `issue list`: %v", err)
    72  	}
    73  
    74  	assert.Equal(t, "", output.Stderr())
    75  	//nolint:staticcheck // prefer exact matchers over ExpectLines
    76  	test.ExpectLines(t, output.String(),
    77  		`1[\t]+number won[\t]+label[\t]+\d+`,
    78  		`2[\t]+number too[\t]+label[\t]+\d+`,
    79  		`4[\t]+number fore[\t]+label[\t]+\d+`)
    80  }
    81  
    82  func TestIssueList_tty(t *testing.T) {
    83  	http := &httpmock.Registry{}
    84  	defer http.Verify(t)
    85  
    86  	http.Register(
    87  		httpmock.GraphQL(`query IssueList\b`),
    88  		httpmock.FileResponse("./fixtures/issueList.json"))
    89  
    90  	output, err := runCommand(http, true, "")
    91  	if err != nil {
    92  		t.Errorf("error running command `issue list`: %v", err)
    93  	}
    94  
    95  	out := output.String()
    96  	timeRE := regexp.MustCompile(`\d+ years`)
    97  	out = timeRE.ReplaceAllString(out, "X years")
    98  
    99  	assert.Equal(t, heredoc.Doc(`
   100  
   101  		Showing 3 of 3 open issues in OWNER/REPO
   102  
   103  		#1  number won   label  about X years ago
   104  		#2  number too   label  about X years ago
   105  		#4  number fore  label  about X years ago
   106  	`), out)
   107  	assert.Equal(t, ``, output.Stderr())
   108  }
   109  
   110  func TestIssueList_tty_withFlags(t *testing.T) {
   111  	http := &httpmock.Registry{}
   112  	defer http.Verify(t)
   113  
   114  	http.Register(
   115  		httpmock.GraphQL(`query IssueList\b`),
   116  		httpmock.GraphQLQuery(`
   117  		{ "data": {	"repository": {
   118  			"hasIssuesEnabled": true,
   119  			"issues": { "nodes": [] }
   120  		} } }`, func(_ string, params map[string]interface{}) {
   121  			assert.Equal(t, "probablyCher", params["assignee"].(string))
   122  			assert.Equal(t, "foo", params["author"].(string))
   123  			assert.Equal(t, "me", params["mention"].(string))
   124  			assert.Equal(t, "12345", params["milestone"].(string))
   125  			assert.Equal(t, []interface{}{"OPEN"}, params["states"].([]interface{}))
   126  		}))
   127  
   128  	http.Register(
   129  		httpmock.GraphQL(`query RepositoryMilestoneList\b`),
   130  		httpmock.StringResponse(`
   131  		{ "data": { "repository": { "milestones": {
   132  			"nodes": [{ "title":"1.x", "id": "MDk6TWlsZXN0b25lMTIzNDU=" }],
   133  			"pageInfo": { "hasNextPage": false }
   134  		} } } }
   135  		`))
   136  
   137  	output, err := runCommand(http, true, "-a probablyCher -s open -A foo --mention me --milestone 1.x")
   138  	if err != nil {
   139  		t.Errorf("error running command `issue list`: %v", err)
   140  	}
   141  
   142  	assert.Equal(t, "", output.Stderr())
   143  	assert.Equal(t, `
   144  No issues match your search in OWNER/REPO
   145  
   146  `, output.String())
   147  }
   148  
   149  func TestIssueList_withInvalidLimitFlag(t *testing.T) {
   150  	http := &httpmock.Registry{}
   151  	defer http.Verify(t)
   152  
   153  	_, err := runCommand(http, true, "--limit=0")
   154  
   155  	if err == nil || err.Error() != "invalid limit: 0" {
   156  		t.Errorf("error running command `issue list`: %v", err)
   157  	}
   158  }
   159  
   160  func TestIssueList_disabledIssues(t *testing.T) {
   161  	http := &httpmock.Registry{}
   162  	defer http.Verify(t)
   163  
   164  	http.Register(
   165  		httpmock.GraphQL(`query IssueList\b`),
   166  		httpmock.StringResponse(`
   167  			{ "data": {	"repository": {
   168  				"hasIssuesEnabled": false
   169  			} } }`),
   170  	)
   171  
   172  	_, err := runCommand(http, true, "")
   173  	if err == nil || err.Error() != "the 'OWNER/REPO' repository has disabled issues" {
   174  		t.Errorf("error running command `issue list`: %v", err)
   175  	}
   176  }
   177  
   178  func TestIssueList_web(t *testing.T) {
   179  	io, _, stdout, stderr := iostreams.Test()
   180  	io.SetStdoutTTY(true)
   181  	io.SetStderrTTY(true)
   182  	browser := &cmdutil.TestBrowser{}
   183  
   184  	reg := &httpmock.Registry{}
   185  	defer reg.Verify(t)
   186  
   187  	_, cmdTeardown := run.Stub()
   188  	defer cmdTeardown(t)
   189  
   190  	err := listRun(&ListOptions{
   191  		IO:      io,
   192  		Browser: browser,
   193  		HttpClient: func() (*http.Client, error) {
   194  			return &http.Client{Transport: reg}, nil
   195  		},
   196  		BaseRepo: func() (ghrepo.Interface, error) {
   197  			return ghrepo.New("OWNER", "REPO"), nil
   198  		},
   199  		WebMode:      true,
   200  		State:        "all",
   201  		Assignee:     "peter",
   202  		Author:       "john",
   203  		Labels:       []string{"bug", "docs"},
   204  		Mention:      "frank",
   205  		Milestone:    "v1.1",
   206  		LimitResults: 10,
   207  	})
   208  	if err != nil {
   209  		t.Errorf("error running command `issue list` with `--web` flag: %v", err)
   210  	}
   211  
   212  	assert.Equal(t, "", stdout.String())
   213  	assert.Equal(t, "Opening github.com/OWNER/REPO/issues in your browser.\n", stderr.String())
   214  	browser.Verify(t, "https://github.com/OWNER/REPO/issues?q=is%3Aissue+assignee%3Apeter+label%3Abug+label%3Adocs+author%3Ajohn+mentions%3Afrank+milestone%3Av1.1")
   215  }
   216  
   217  func Test_issueList(t *testing.T) {
   218  	type args struct {
   219  		repo    ghrepo.Interface
   220  		filters prShared.FilterOptions
   221  		limit   int
   222  	}
   223  	tests := []struct {
   224  		name      string
   225  		args      args
   226  		httpStubs func(*httpmock.Registry)
   227  		wantErr   bool
   228  	}{
   229  		{
   230  			name: "default",
   231  			args: args{
   232  				limit: 30,
   233  				repo:  ghrepo.New("OWNER", "REPO"),
   234  				filters: prShared.FilterOptions{
   235  					Entity: "issue",
   236  					State:  "open",
   237  				},
   238  			},
   239  			httpStubs: func(reg *httpmock.Registry) {
   240  				reg.Register(
   241  					httpmock.GraphQL(`query IssueList\b`),
   242  					httpmock.GraphQLQuery(`
   243  					{ "data": {	"repository": {
   244  						"hasIssuesEnabled": true,
   245  						"issues": { "nodes": [] }
   246  					} } }`, func(_ string, params map[string]interface{}) {
   247  						assert.Equal(t, map[string]interface{}{
   248  							"owner":  "OWNER",
   249  							"repo":   "REPO",
   250  							"limit":  float64(30),
   251  							"states": []interface{}{"OPEN"},
   252  						}, params)
   253  					}))
   254  			},
   255  		},
   256  		{
   257  			name: "milestone by number",
   258  			args: args{
   259  				limit: 30,
   260  				repo:  ghrepo.New("OWNER", "REPO"),
   261  				filters: prShared.FilterOptions{
   262  					Entity:    "issue",
   263  					State:     "open",
   264  					Milestone: "13",
   265  				},
   266  			},
   267  			httpStubs: func(reg *httpmock.Registry) {
   268  				reg.Register(
   269  					httpmock.GraphQL(`query RepositoryMilestoneByNumber\b`),
   270  					httpmock.StringResponse(`
   271  					{ "data": { "repository": { "milestone": {
   272  						"id": "MDk6TWlsZXN0b25lMTIzNDU="
   273  					} } } }
   274  					`))
   275  				reg.Register(
   276  					httpmock.GraphQL(`query IssueList\b`),
   277  					httpmock.GraphQLQuery(`
   278  					{ "data": {	"repository": {
   279  						"hasIssuesEnabled": true,
   280  						"issues": { "nodes": [] }
   281  					} } }`, func(_ string, params map[string]interface{}) {
   282  						assert.Equal(t, map[string]interface{}{
   283  							"owner":     "OWNER",
   284  							"repo":      "REPO",
   285  							"limit":     float64(30),
   286  							"states":    []interface{}{"OPEN"},
   287  							"milestone": "12345",
   288  						}, params)
   289  					}))
   290  			},
   291  		},
   292  		{
   293  			name: "milestone by number with search",
   294  			args: args{
   295  				limit: 30,
   296  				repo:  ghrepo.New("OWNER", "REPO"),
   297  				filters: prShared.FilterOptions{
   298  					Entity:    "issue",
   299  					State:     "open",
   300  					Milestone: "13",
   301  					Search:    "auth bug",
   302  				},
   303  			},
   304  			httpStubs: func(reg *httpmock.Registry) {
   305  				reg.Register(
   306  					httpmock.GraphQL(`query RepositoryMilestoneByNumber\b`),
   307  					httpmock.StringResponse(`
   308  					{ "data": { "repository": { "milestone": {
   309  						"title": "Big 1.0",
   310  						"id": "MDk6TWlsZXN0b25lMTIzNDU="
   311  					} } } }
   312  					`))
   313  				reg.Register(
   314  					httpmock.GraphQL(`query IssueSearch\b`),
   315  					httpmock.GraphQLQuery(`
   316  					{ "data": {
   317  						"repository": { "hasIssuesEnabled": true },
   318  						"search": {
   319  							"issueCount": 0,
   320  							"nodes": []
   321  						}
   322  					} }`, func(_ string, params map[string]interface{}) {
   323  						assert.Equal(t, map[string]interface{}{
   324  							"owner": "OWNER",
   325  							"repo":  "REPO",
   326  							"limit": float64(30),
   327  							"query": "repo:OWNER/REPO is:issue is:open milestone:\"Big 1.0\" auth bug",
   328  							"type":  "ISSUE",
   329  						}, params)
   330  					}))
   331  			},
   332  		},
   333  		{
   334  			name: "milestone by title with search",
   335  			args: args{
   336  				limit: 30,
   337  				repo:  ghrepo.New("OWNER", "REPO"),
   338  				filters: prShared.FilterOptions{
   339  					Entity:    "issue",
   340  					State:     "open",
   341  					Milestone: "Big 1.0",
   342  					Search:    "auth bug",
   343  				},
   344  			},
   345  			httpStubs: func(reg *httpmock.Registry) {
   346  				reg.Register(
   347  					httpmock.GraphQL(`query IssueSearch\b`),
   348  					httpmock.GraphQLQuery(`
   349  					{ "data": {
   350  						"repository": { "hasIssuesEnabled": true },
   351  						"search": {
   352  							"issueCount": 0,
   353  							"nodes": []
   354  						}
   355  					} }`, func(_ string, params map[string]interface{}) {
   356  						assert.Equal(t, map[string]interface{}{
   357  							"owner": "OWNER",
   358  							"repo":  "REPO",
   359  							"limit": float64(30),
   360  							"query": "repo:OWNER/REPO is:issue is:open milestone:\"Big 1.0\" auth bug",
   361  							"type":  "ISSUE",
   362  						}, params)
   363  					}))
   364  			},
   365  		},
   366  		{
   367  			name: "milestone by title",
   368  			args: args{
   369  				limit: 30,
   370  				repo:  ghrepo.New("OWNER", "REPO"),
   371  				filters: prShared.FilterOptions{
   372  					Entity:    "issue",
   373  					State:     "open",
   374  					Milestone: "1.x",
   375  				},
   376  			},
   377  			httpStubs: func(reg *httpmock.Registry) {
   378  				reg.Register(
   379  					httpmock.GraphQL(`query RepositoryMilestoneList\b`),
   380  					httpmock.StringResponse(`
   381  					{ "data": { "repository": { "milestones": {
   382  						"nodes": [{ "title":"1.x", "id": "MDk6TWlsZXN0b25lMTIzNDU=" }],
   383  						"pageInfo": { "hasNextPage": false }
   384  					} } } }
   385  					`))
   386  				reg.Register(
   387  					httpmock.GraphQL(`query IssueList\b`),
   388  					httpmock.GraphQLQuery(`
   389  					{ "data": {	"repository": {
   390  						"hasIssuesEnabled": true,
   391  						"issues": { "nodes": [] }
   392  					} } }`, func(_ string, params map[string]interface{}) {
   393  						assert.Equal(t, map[string]interface{}{
   394  							"owner":     "OWNER",
   395  							"repo":      "REPO",
   396  							"limit":     float64(30),
   397  							"states":    []interface{}{"OPEN"},
   398  							"milestone": "12345",
   399  						}, params)
   400  					}))
   401  			},
   402  		},
   403  		{
   404  			name: "@me syntax",
   405  			args: args{
   406  				limit: 30,
   407  				repo:  ghrepo.New("OWNER", "REPO"),
   408  				filters: prShared.FilterOptions{
   409  					Entity:   "issue",
   410  					State:    "open",
   411  					Author:   "@me",
   412  					Assignee: "@me",
   413  					Mention:  "@me",
   414  				},
   415  			},
   416  			httpStubs: func(reg *httpmock.Registry) {
   417  				reg.Register(
   418  					httpmock.GraphQL(`query UserCurrent\b`),
   419  					httpmock.StringResponse(`{"data": {"viewer": {"login": "monalisa"} } }`))
   420  				reg.Register(
   421  					httpmock.GraphQL(`query IssueList\b`),
   422  					httpmock.GraphQLQuery(`
   423  					{ "data": {	"repository": {
   424  						"hasIssuesEnabled": true,
   425  						"issues": { "nodes": [] }
   426  					} } }`, func(_ string, params map[string]interface{}) {
   427  						assert.Equal(t, map[string]interface{}{
   428  							"owner":    "OWNER",
   429  							"repo":     "REPO",
   430  							"limit":    float64(30),
   431  							"states":   []interface{}{"OPEN"},
   432  							"assignee": "monalisa",
   433  							"author":   "monalisa",
   434  							"mention":  "monalisa",
   435  						}, params)
   436  					}))
   437  			},
   438  		},
   439  		{
   440  			name: "@me with search",
   441  			args: args{
   442  				limit: 30,
   443  				repo:  ghrepo.New("OWNER", "REPO"),
   444  				filters: prShared.FilterOptions{
   445  					Entity:   "issue",
   446  					State:    "open",
   447  					Author:   "@me",
   448  					Assignee: "@me",
   449  					Mention:  "@me",
   450  					Search:   "auth bug",
   451  				},
   452  			},
   453  			httpStubs: func(reg *httpmock.Registry) {
   454  				reg.Register(
   455  					httpmock.GraphQL(`query IssueSearch\b`),
   456  					httpmock.GraphQLQuery(`
   457  					{ "data": {
   458  						"repository": { "hasIssuesEnabled": true },
   459  						"search": {
   460  							"issueCount": 0,
   461  							"nodes": []
   462  						}
   463  					} }`, func(_ string, params map[string]interface{}) {
   464  						assert.Equal(t, map[string]interface{}{
   465  							"owner": "OWNER",
   466  							"repo":  "REPO",
   467  							"limit": float64(30),
   468  							"query": "repo:OWNER/REPO is:issue is:open assignee:@me author:@me mentions:@me auth bug",
   469  							"type":  "ISSUE",
   470  						}, params)
   471  					}))
   472  			},
   473  		},
   474  		{
   475  			name: "with labels",
   476  			args: args{
   477  				limit: 30,
   478  				repo:  ghrepo.New("OWNER", "REPO"),
   479  				filters: prShared.FilterOptions{
   480  					Entity: "issue",
   481  					State:  "open",
   482  					Labels: []string{"hello", "one world"},
   483  				},
   484  			},
   485  			httpStubs: func(reg *httpmock.Registry) {
   486  				reg.Register(
   487  					httpmock.GraphQL(`query IssueSearch\b`),
   488  					httpmock.GraphQLQuery(`
   489  					{ "data": {
   490  						"repository": { "hasIssuesEnabled": true },
   491  						"search": {
   492  							"issueCount": 0,
   493  							"nodes": []
   494  						}
   495  					} }`, func(_ string, params map[string]interface{}) {
   496  						assert.Equal(t, map[string]interface{}{
   497  							"owner": "OWNER",
   498  							"repo":  "REPO",
   499  							"limit": float64(30),
   500  							"query": `repo:OWNER/REPO is:issue is:open label:hello label:"one world"`,
   501  							"type":  "ISSUE",
   502  						}, params)
   503  					}))
   504  			},
   505  		},
   506  	}
   507  	for _, tt := range tests {
   508  		t.Run(tt.name, func(t *testing.T) {
   509  			httpreg := &httpmock.Registry{}
   510  			defer httpreg.Verify(t)
   511  			if tt.httpStubs != nil {
   512  				tt.httpStubs(httpreg)
   513  			}
   514  			client := &http.Client{Transport: httpreg}
   515  			_, err := issueList(client, tt.args.repo, tt.args.filters, tt.args.limit)
   516  			if tt.wantErr {
   517  				assert.Error(t, err)
   518  			} else {
   519  				assert.NoError(t, err)
   520  			}
   521  		})
   522  	}
   523  }