package label import ( "bytes" "net/http" "testing" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/browser" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" "github.com/google/shlex" "github.com/stretchr/testify/assert" ) func TestNewCmdList(t *testing.T) { tests := []struct { name string input string output listOptions wantErr bool errMsg string }{ { name: "no argument", input: "", output: listOptions{Query: listQueryOptions{Limit: 30, Order: "asc", Sort: "created"}}, }, { name: "limit flag", input: "--limit 10", output: listOptions{Query: listQueryOptions{Limit: 10, Order: "asc", Sort: "created"}}, }, { name: "invalid limit flag", input: "--limit 0", wantErr: true, errMsg: "invalid limit: 0", }, { name: "web flag", input: "--web", output: listOptions{Query: listQueryOptions{Limit: 30, Order: "asc", Sort: "created"}, WebMode: true}, }, { name: "search flag", input: "--search core", output: listOptions{Query: listQueryOptions{Limit: 30, Query: "core", Order: "asc", Sort: "created"}}, }, { name: "sort name flag", input: "--sort name", output: listOptions{Query: listQueryOptions{Limit: 30, Order: "asc", Sort: "name"}}, }, { name: "sort created flag", input: "--sort created", output: listOptions{Query: listQueryOptions{Limit: 30, Order: "asc", Sort: "created"}}, }, { name: "sort invalid flag", input: "--sort invalid", wantErr: true, errMsg: `invalid argument "invalid" for "--sort" flag: valid values are {created|name}`, }, { name: "order asc flag", input: "--order asc", output: listOptions{Query: listQueryOptions{Limit: 30, Order: "asc", Sort: "created"}}, }, { name: "order desc flag", input: "--order desc", output: listOptions{Query: listQueryOptions{Limit: 30, Order: "desc", Sort: "created"}}, }, { name: "order invalid flag", input: "--order invalid", wantErr: true, errMsg: `invalid argument "invalid" for "--order" flag: valid values are {asc|desc}`, }, { name: "search flag with sort flag", input: "--search test --sort name", wantErr: true, errMsg: "cannot specify `--order` or `--sort` with `--search`", }, { name: "search flag with order flag", input: "--search test --order asc", wantErr: true, errMsg: "cannot specify `--order` or `--sort` with `--search`", }, { name: "search flag with order and sort flags", input: "--search test --order asc --sort name", wantErr: true, errMsg: "cannot specify `--order` or `--sort` with `--search`", }, { name: "json flag", input: "--json name,color,description,createdAt", output: listOptions{ Query: listQueryOptions{ Limit: 30, Order: "asc", Sort: "created", fields: []string{ "name", "color", "description", "createdAt", }, }, }, }, { name: "invalid json flag", input: "--json invalid", wantErr: true, errMsg: heredoc.Doc(` Unknown JSON field: "invalid" Available fields: color createdAt description id isDefault name updatedAt url`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ios, _, _, _ := iostreams.Test() f := &cmdutil.Factory{ IOStreams: ios, } argv, err := shlex.Split(tt.input) assert.NoError(t, err) var gotOpts *listOptions cmd := newCmdList(f, func(opts *listOptions) error { gotOpts = opts return nil }) cmd.SetArgs(argv) cmd.SetIn(&bytes.Buffer{}) cmd.SetOut(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{}) _, err = cmd.ExecuteC() if tt.wantErr { assert.EqualError(t, err, tt.errMsg) return } assert.NoError(t, err) assert.Equal(t, tt.output.Query.Limit, gotOpts.Query.Limit) assert.Equal(t, tt.output.Query.Order, gotOpts.Query.Order) assert.Equal(t, tt.output.Query.Query, tt.output.Query.Query) assert.Equal(t, tt.output.Query.Sort, gotOpts.Query.Sort) assert.Equal(t, tt.output.WebMode, gotOpts.WebMode) }) } } func TestListRun(t *testing.T) { tests := []struct { name string tty bool opts *listOptions httpStubs func(*httpmock.Registry) wantErr bool wantErrMsg string wantStdout string wantStderr string }{ { name: "lists labels", tty: true, opts: &listOptions{}, httpStubs: func(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.StringResponse(` { "data": { "repository": { "labels": { "totalCount": 2, "nodes": [ { "name": "bug", "color": "d73a4a", "description": "This is a bug label" }, { "name": "docs", "color": "ffa8da", "description": "This is a docs label" } ], "pageInfo": { "hasNextPage": false, "endCursor": "Y3Vyc29yOnYyOpK5MjAxOS0xMC0xMVQwMTozODowMyswODowMM5f3HZq" } } } } }`, ), ) }, wantStdout: heredoc.Doc(` Showing 2 of 2 labels in OWNER/REPO NAME DESCRIPTION COLOR bug This is a bug label #d73a4a docs This is a docs label #ffa8da `), }, { name: "lists labels notty", tty: false, opts: &listOptions{}, httpStubs: func(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.StringResponse(` { "data": { "repository": { "labels": { "totalCount": 2, "nodes": [ { "name": "bug", "color": "d73a4a", "description": "This is a bug label" }, { "name": "docs", "color": "ffa8da", "description": "This is a docs label" } ], "pageInfo": { "hasNextPage": false, "endCursor": "Y3Vyc29yOnYyOpK5MjAxOS0xMC0xMVQwMTozODowMyswODowMM5f3HZq" } } } } }`, ), ) }, wantStdout: "bug\tThis is a bug label\t#d73a4a\ndocs\tThis is a docs label\t#ffa8da\n", }, { name: "empty label list", tty: true, opts: &listOptions{}, httpStubs: func(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.StringResponse(` {"data":{"repository":{"labels":{"totalCount":0,"nodes":[],"pageInfo":{"hasNextPage":false,"endCursor":null}}}}}`, ), ) }, wantErr: true, wantErrMsg: "no labels found in OWNER/REPO", }, { name: "empty label list search", tty: true, opts: &listOptions{Query: listQueryOptions{Query: "test"}}, httpStubs: func(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.StringResponse(` {"data":{"repository":{"labels":{"totalCount":0,"nodes":[],"pageInfo":{"hasNextPage":false,"endCursor":null}}}}}`, ), ) }, wantErr: true, wantErrMsg: "no labels in OWNER/REPO matched your search", }, { name: "web mode", tty: true, opts: &listOptions{WebMode: true}, wantStderr: "Opening https://github.com/OWNER/REPO/labels in your browser.\n", }, { name: "order by name ascending", tty: true, opts: &listOptions{ Query: listQueryOptions{ Limit: 30, Order: "asc", Sort: "name", }, }, httpStubs: func(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.GraphQLQuery(` { "data": { "repository": { "labels": { "totalCount": 2, "nodes": [ { "name": "bug", "color": "d73a4a", "description": "This is a bug label", "createdAt": "2022-04-20T06:17:50Z" }, { "name": "docs", "color": "ffa8da", "description": "This is a docs label", "createdAt": "2022-04-20T06:17:49Z" } ], "pageInfo": { "hasNextPage": false, "endCursor": "Y3Vyc29yOnYyOpK5MjAxOS0xMC0xMVQwMTozODowMyswODowMM5f3HZq" } } } } }`, func(s string, m map[string]interface{}) { assert.Equal(t, "OWNER", m["owner"]) assert.Equal(t, "REPO", m["repo"]) assert.Equal(t, float64(30), m["limit"].(float64)) assert.Equal(t, map[string]interface{}{"direction": "ASC", "field": "NAME"}, m["orderBy"]) }), ) }, wantStdout: heredoc.Doc(` Showing 2 of 2 labels in OWNER/REPO NAME DESCRIPTION COLOR bug This is a bug label #d73a4a docs This is a docs label #ffa8da `), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reg := &httpmock.Registry{} if tt.httpStubs != nil { tt.httpStubs(reg) } tt.opts.HttpClient = func() (*http.Client, error) { return &http.Client{Transport: reg}, nil } ios, _, stdout, stderr := iostreams.Test() ios.SetStdoutTTY(tt.tty) ios.SetStdinTTY(tt.tty) ios.SetStderrTTY(tt.tty) tt.opts.IO = ios tt.opts.Browser = &browser.Stub{} tt.opts.BaseRepo = func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil } defer reg.Verify(t) err := listRun(tt.opts) if tt.wantErr { if tt.wantErrMsg != "" { assert.EqualError(t, err, tt.wantErrMsg) } else { assert.Error(t, err) } } else { assert.NoError(t, err) } assert.Equal(t, tt.wantStdout, stdout.String()) assert.Equal(t, tt.wantStderr, stderr.String()) }) } }