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 }