package shared import ( "fmt" "net/http" "testing" "time" "github.com/cli/cli/v2/internal/browser" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/gh" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/search" "github.com/stretchr/testify/assert" ) func TestSearcher(t *testing.T) { f := &cmdutil.Factory{ Config: func() (gh.Config, error) { return config.NewBlankConfig(), nil }, HttpClient: func() (*http.Client, error) { return &http.Client{}, nil }, } _, err := Searcher(f) assert.NoError(t, err) } func TestSearchIssues(t *testing.T) { var now = time.Date(2022, 2, 28, 12, 30, 0, 0, time.UTC) var updatedAt = time.Date(2021, 2, 28, 12, 30, 0, 0, time.UTC) var query = search.Query{ Keywords: []string{"keyword"}, Kind: "issues", Limit: 30, Qualifiers: search.Qualifiers{ Language: "go", Type: "issue", Is: []string{"public", "locked"}, }, } tests := []struct { errMsg string name string opts *IssuesOptions tty bool wantErr bool wantStderr string wantStdout string }{ { name: "displays results tty", opts: &IssuesOptions{ Entity: Issues, Query: query, Searcher: &search.SearcherMock{ IssuesFunc: func(query search.Query) (search.IssuesResult, error) { return search.IssuesResult{ IncompleteResults: false, Items: []search.Issue{ {RepositoryURL: "github.com/test/cli", Number: 123, StateInternal: "open", Title: "something broken", Labels: []search.Label{{Name: "bug"}, {Name: "p1"}}, UpdatedAt: updatedAt}, {RepositoryURL: "github.com/what/what", Number: 456, StateInternal: "closed", Title: "feature request", Labels: []search.Label{{Name: "enhancement"}}, UpdatedAt: updatedAt}, {RepositoryURL: "github.com/blah/test", Number: 789, StateInternal: "open", Title: "some title", UpdatedAt: updatedAt}, }, Total: 300, }, nil }, }, }, tty: true, wantStdout: "\nShowing 3 of 300 issues\n\nREPO ID TITLE LABELS UPDATED\ntest/cli #123 something broken bug, p1 about 1 year ago\nwhat/what #456 feature request enhancement about 1 year ago\nblah/test #789 some title about 1 year ago\n", }, { name: "displays issues and pull requests tty", opts: &IssuesOptions{ Entity: Both, Query: query, Searcher: &search.SearcherMock{ IssuesFunc: func(query search.Query) (search.IssuesResult, error) { return search.IssuesResult{ IncompleteResults: false, Items: []search.Issue{ {RepositoryURL: "github.com/test/cli", Number: 123, StateInternal: "open", Title: "bug", Labels: []search.Label{{Name: "bug"}, {Name: "p1"}}, UpdatedAt: updatedAt}, {RepositoryURL: "github.com/what/what", Number: 456, StateInternal: "open", Title: "fix bug", Labels: []search.Label{{Name: "fix"}}, PullRequest: search.PullRequest{URL: "someurl"}, UpdatedAt: updatedAt}, }, Total: 300, }, nil }, }, }, tty: true, wantStdout: "\nShowing 2 of 300 issues and pull requests\n\nKIND REPO ID TITLE LABELS UPDATED\nissue test/cli #123 bug bug, p1 about 1 year ago\npr what/what #456 fix bug fix about 1 year ago\n", }, { name: "displays results notty", opts: &IssuesOptions{ Entity: Issues, Query: query, Searcher: &search.SearcherMock{ IssuesFunc: func(query search.Query) (search.IssuesResult, error) { return search.IssuesResult{ IncompleteResults: false, Items: []search.Issue{ {RepositoryURL: "github.com/test/cli", Number: 123, StateInternal: "open", Title: "something broken", Labels: []search.Label{{Name: "bug"}, {Name: "p1"}}, UpdatedAt: updatedAt}, {RepositoryURL: "github.com/what/what", Number: 456, StateInternal: "closed", Title: "feature request", Labels: []search.Label{{Name: "enhancement"}}, UpdatedAt: updatedAt}, {RepositoryURL: "github.com/blah/test", Number: 789, StateInternal: "open", Title: "some title", UpdatedAt: updatedAt}, }, Total: 300, }, nil }, }, }, wantStdout: "test/cli\t123\topen\tsomething broken\tbug, p1\t2021-02-28T12:30:00Z\nwhat/what\t456\tclosed\tfeature request\tenhancement\t2021-02-28T12:30:00Z\nblah/test\t789\topen\tsome title\t\t2021-02-28T12:30:00Z\n", }, { name: "displays issues and pull requests notty", opts: &IssuesOptions{ Entity: Both, Query: query, Searcher: &search.SearcherMock{ IssuesFunc: func(query search.Query) (search.IssuesResult, error) { return search.IssuesResult{ IncompleteResults: false, Items: []search.Issue{ {RepositoryURL: "github.com/test/cli", Number: 123, StateInternal: "open", Title: "bug", Labels: []search.Label{{Name: "bug"}, {Name: "p1"}}, UpdatedAt: updatedAt}, {RepositoryURL: "github.com/what/what", Number: 456, StateInternal: "open", Title: "fix bug", Labels: []search.Label{{Name: "fix"}}, PullRequest: search.PullRequest{URL: "someurl"}, UpdatedAt: updatedAt}, }, Total: 300, }, nil }, }, }, wantStdout: "issue\ttest/cli\t123\topen\tbug\tbug, p1\t2021-02-28T12:30:00Z\npr\twhat/what\t456\topen\tfix bug\tfix\t2021-02-28T12:30:00Z\n", }, { name: "displays no results", opts: &IssuesOptions{ Entity: Issues, Query: query, Searcher: &search.SearcherMock{ IssuesFunc: func(query search.Query) (search.IssuesResult, error) { return search.IssuesResult{}, nil }, }, }, wantErr: true, errMsg: "no issues matched your search", }, { name: "displays search error", opts: &IssuesOptions{ Entity: Issues, Query: query, Searcher: &search.SearcherMock{ IssuesFunc: func(query search.Query) (search.IssuesResult, error) { return search.IssuesResult{}, fmt.Errorf("error with query") }, }, }, errMsg: "error with query", wantErr: true, }, { name: "opens browser for web mode tty", opts: &IssuesOptions{ Browser: &browser.Stub{}, Entity: Issues, Query: query, Searcher: &search.SearcherMock{ URLFunc: func(query search.Query) string { return "https://github.com/search?type=issues&q=cli" }, }, WebMode: true, }, tty: true, wantStderr: "Opening https://github.com/search in your browser.\n", }, { name: "opens browser for web mode notty", opts: &IssuesOptions{ Browser: &browser.Stub{}, Entity: Issues, Query: query, Searcher: &search.SearcherMock{ URLFunc: func(query search.Query) string { return "https://github.com/search?type=issues&q=cli" }, }, WebMode: true, }, }, } for _, tt := range tests { ios, _, stdout, stderr := iostreams.Test() ios.SetStdinTTY(tt.tty) ios.SetStdoutTTY(tt.tty) ios.SetStderrTTY(tt.tty) tt.opts.IO = ios tt.opts.Now = now t.Run(tt.name, func(t *testing.T) { err := SearchIssues(tt.opts) if tt.wantErr { assert.EqualError(t, err, tt.errMsg) return } else if err != nil { t.Fatalf("SearchIssues unexpected error: %v", err) } assert.Equal(t, tt.wantStdout, stdout.String()) assert.Equal(t, tt.wantStderr, stderr.String()) }) } }