566 lines
12 KiB
Go
566 lines
12 KiB
Go
package list
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
"github.com/google/shlex"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/cli/cli/v2/internal/config"
|
|
fd "github.com/cli/cli/v2/internal/featuredetection"
|
|
"github.com/cli/cli/v2/internal/gh"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/httpmock"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/cli/cli/v2/pkg/jsonfieldstest"
|
|
"github.com/cli/cli/v2/test"
|
|
)
|
|
|
|
func TestJSONFields(t *testing.T) {
|
|
jsonfieldstest.ExpectCommandToSupportJSONFields(t, NewCmdList, []string{
|
|
"archivedAt",
|
|
"assignableUsers",
|
|
"codeOfConduct",
|
|
"contactLinks",
|
|
"createdAt",
|
|
"defaultBranchRef",
|
|
"deleteBranchOnMerge",
|
|
"description",
|
|
"diskUsage",
|
|
"forkCount",
|
|
"fundingLinks",
|
|
"hasDiscussionsEnabled",
|
|
"hasIssuesEnabled",
|
|
"hasProjectsEnabled",
|
|
"hasWikiEnabled",
|
|
"homepageUrl",
|
|
"id",
|
|
"isArchived",
|
|
"isBlankIssuesEnabled",
|
|
"isEmpty",
|
|
"isFork",
|
|
"isInOrganization",
|
|
"isMirror",
|
|
"isPrivate",
|
|
"isSecurityPolicyEnabled",
|
|
"isTemplate",
|
|
"isUserConfigurationRepository",
|
|
"issueTemplates",
|
|
"issues",
|
|
"labels",
|
|
"languages",
|
|
"latestRelease",
|
|
"licenseInfo",
|
|
"mentionableUsers",
|
|
"mergeCommitAllowed",
|
|
"milestones",
|
|
"mirrorUrl",
|
|
"name",
|
|
"nameWithOwner",
|
|
"openGraphImageUrl",
|
|
"owner",
|
|
"parent",
|
|
"primaryLanguage",
|
|
"projects",
|
|
"projectsV2",
|
|
"pullRequestTemplates",
|
|
"pullRequests",
|
|
"pushedAt",
|
|
"rebaseMergeAllowed",
|
|
"repositoryTopics",
|
|
"securityPolicyUrl",
|
|
"sshUrl",
|
|
"squashMergeAllowed",
|
|
"stargazerCount",
|
|
"templateRepository",
|
|
"updatedAt",
|
|
"url",
|
|
"usesCustomOpenGraphImage",
|
|
"viewerCanAdminister",
|
|
"viewerDefaultCommitEmail",
|
|
"viewerDefaultMergeMethod",
|
|
"viewerHasStarred",
|
|
"viewerPermission",
|
|
"viewerPossibleCommitEmails",
|
|
"viewerSubscription",
|
|
"visibility",
|
|
"watchers",
|
|
})
|
|
}
|
|
|
|
func TestNewCmdList(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cli string
|
|
wants ListOptions
|
|
wantsErr string
|
|
}{
|
|
{
|
|
name: "no arguments",
|
|
cli: "",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string(nil),
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "with owner",
|
|
cli: "monalisa",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "monalisa",
|
|
Visibility: "",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string(nil),
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "with limit",
|
|
cli: "-L 101",
|
|
wants: ListOptions{
|
|
Limit: 101,
|
|
Owner: "",
|
|
Visibility: "",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string(nil),
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "only public",
|
|
cli: "--visibility=public",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "public",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string(nil),
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "only private",
|
|
cli: "--visibility=private",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "private",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string(nil),
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "only forks",
|
|
cli: "--fork",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "",
|
|
Fork: true,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string(nil),
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "only sources",
|
|
cli: "--source",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "",
|
|
Fork: false,
|
|
Source: true,
|
|
Language: "",
|
|
Topic: []string(nil),
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "with language",
|
|
cli: "-l go",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "go",
|
|
Topic: []string(nil),
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "only archived",
|
|
cli: "--archived",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string(nil),
|
|
Archived: true,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "only non-archived",
|
|
cli: "--no-archived",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string(nil),
|
|
Archived: false,
|
|
NonArchived: true,
|
|
},
|
|
},
|
|
{
|
|
name: "with topic",
|
|
cli: "--topic cli",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string{"cli"},
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "with multiple topic",
|
|
cli: "--topic cli --topic multiple-topic",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Owner: "",
|
|
Visibility: "",
|
|
Fork: false,
|
|
Source: false,
|
|
Language: "",
|
|
Topic: []string{"cli", "multiple-topic"},
|
|
Archived: false,
|
|
NonArchived: false,
|
|
},
|
|
},
|
|
{
|
|
name: "invalid visibility",
|
|
cli: "--visibility=bad",
|
|
wantsErr: "invalid argument \"bad\" for \"--visibility\" flag: valid values are {public|private|internal}",
|
|
},
|
|
{
|
|
name: "no forks with sources",
|
|
cli: "--fork --source",
|
|
wantsErr: "specify only one of `--source` or `--fork`",
|
|
},
|
|
{
|
|
name: "conflicting archived",
|
|
cli: "--archived --no-archived",
|
|
wantsErr: "specify only one of `--archived` or `--no-archived`",
|
|
},
|
|
{
|
|
name: "too many arguments",
|
|
cli: "monalisa hubot",
|
|
wantsErr: "accepts at most 1 arg(s), received 2",
|
|
},
|
|
{
|
|
name: "invalid limit",
|
|
cli: "-L 0",
|
|
wantsErr: "invalid limit: 0",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
f := &cmdutil.Factory{}
|
|
|
|
argv, err := shlex.Split(tt.cli)
|
|
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.wantsErr != "" {
|
|
assert.EqualError(t, err, tt.wantsErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.wants.Limit, gotOpts.Limit)
|
|
assert.Equal(t, tt.wants.Owner, gotOpts.Owner)
|
|
assert.Equal(t, tt.wants.Visibility, gotOpts.Visibility)
|
|
assert.Equal(t, tt.wants.Fork, gotOpts.Fork)
|
|
assert.Equal(t, tt.wants.Topic, gotOpts.Topic)
|
|
assert.Equal(t, tt.wants.Source, gotOpts.Source)
|
|
assert.Equal(t, tt.wants.Archived, gotOpts.Archived)
|
|
assert.Equal(t, tt.wants.NonArchived, gotOpts.NonArchived)
|
|
})
|
|
}
|
|
}
|
|
|
|
func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) {
|
|
ios, _, stdout, stderr := iostreams.Test()
|
|
ios.SetStdoutTTY(isTTY)
|
|
ios.SetStdinTTY(isTTY)
|
|
ios.SetStderrTTY(isTTY)
|
|
|
|
factory := &cmdutil.Factory{
|
|
IOStreams: ios,
|
|
HttpClient: func() (*http.Client, error) {
|
|
return &http.Client{Transport: rt}, nil
|
|
},
|
|
Config: func() (gh.Config, error) {
|
|
return config.NewBlankConfig(), nil
|
|
},
|
|
}
|
|
|
|
cmd := NewCmdList(factory, nil)
|
|
|
|
argv, err := shlex.Split(cli)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd.SetArgs(argv)
|
|
|
|
cmd.SetIn(&bytes.Buffer{})
|
|
cmd.SetOut(io.Discard)
|
|
cmd.SetErr(io.Discard)
|
|
|
|
_, err = cmd.ExecuteC()
|
|
return &test.CmdOut{
|
|
OutBuf: stdout,
|
|
ErrBuf: stderr,
|
|
}, err
|
|
}
|
|
|
|
func TestRepoList_nontty(t *testing.T) {
|
|
ios, _, stdout, stderr := iostreams.Test()
|
|
ios.SetStdoutTTY(false)
|
|
ios.SetStdinTTY(false)
|
|
ios.SetStderrTTY(false)
|
|
|
|
httpReg := &httpmock.Registry{}
|
|
defer httpReg.Verify(t)
|
|
|
|
httpReg.Register(
|
|
httpmock.GraphQL(`query RepositoryList\b`),
|
|
httpmock.FileResponse("./fixtures/repoList.json"),
|
|
)
|
|
|
|
opts := ListOptions{
|
|
IO: ios,
|
|
HttpClient: func() (*http.Client, error) {
|
|
return &http.Client{Transport: httpReg}, nil
|
|
},
|
|
Config: func() (gh.Config, error) {
|
|
return config.NewBlankConfig(), nil
|
|
},
|
|
Now: func() time.Time {
|
|
t, _ := time.Parse(time.RFC822, "19 Feb 21 15:00 UTC")
|
|
return t
|
|
},
|
|
Limit: 30,
|
|
}
|
|
|
|
err := listRun(&opts)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "", stderr.String())
|
|
|
|
assert.Equal(t, heredoc.Doc(`
|
|
octocat/hello-world My first repository public 2021-02-19T06:34:58Z
|
|
octocat/cli GitHub CLI public, fork 2021-02-19T06:06:06Z
|
|
octocat/testing private 2021-02-11T22:32:05Z
|
|
`), stdout.String())
|
|
}
|
|
|
|
func TestRepoList_tty(t *testing.T) {
|
|
ios, _, stdout, stderr := iostreams.Test()
|
|
ios.SetStdoutTTY(true)
|
|
ios.SetStdinTTY(true)
|
|
ios.SetStderrTTY(true)
|
|
|
|
httpReg := &httpmock.Registry{}
|
|
defer httpReg.Verify(t)
|
|
|
|
httpReg.Register(
|
|
httpmock.GraphQL(`query RepositoryList\b`),
|
|
httpmock.FileResponse("./fixtures/repoList.json"),
|
|
)
|
|
|
|
opts := ListOptions{
|
|
IO: ios,
|
|
HttpClient: func() (*http.Client, error) {
|
|
return &http.Client{Transport: httpReg}, nil
|
|
},
|
|
Config: func() (gh.Config, error) {
|
|
return config.NewBlankConfig(), nil
|
|
},
|
|
Now: func() time.Time {
|
|
t, _ := time.Parse(time.RFC822, "19 Feb 21 15:00 UTC")
|
|
return t
|
|
},
|
|
Limit: 30,
|
|
}
|
|
|
|
err := listRun(&opts)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "", stderr.String())
|
|
|
|
assert.Equal(t, heredoc.Doc(`
|
|
|
|
Showing 3 of 3 repositories in @octocat
|
|
|
|
NAME DESCRIPTION INFO UPDATED
|
|
octocat/hello-world My first repository public about 8 hours ago
|
|
octocat/cli GitHub CLI public, fork about 8 hours ago
|
|
octocat/testing private about 7 days ago
|
|
`), stdout.String())
|
|
}
|
|
|
|
func TestRepoList_filtering(t *testing.T) {
|
|
http := &httpmock.Registry{}
|
|
defer http.Verify(t)
|
|
|
|
http.Register(
|
|
httpmock.GraphQL(`query RepositoryList\b`),
|
|
httpmock.GraphQLQuery(`{}`, func(_ string, params map[string]interface{}) {
|
|
assert.Equal(t, "PRIVATE", params["privacy"])
|
|
assert.Equal(t, float64(2), params["perPage"])
|
|
}),
|
|
)
|
|
|
|
output, err := runCommand(http, true, `--visibility=private --limit 2 `)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.Equal(t, "", output.Stderr())
|
|
assert.Equal(t, "\nNo results match your search\n\n", output.String())
|
|
}
|
|
|
|
func TestRepoList_noVisibilityField(t *testing.T) {
|
|
ios, _, stdout, stderr := iostreams.Test()
|
|
ios.SetStdoutTTY(false)
|
|
ios.SetStdinTTY(false)
|
|
ios.SetStderrTTY(false)
|
|
|
|
reg := &httpmock.Registry{}
|
|
defer reg.Verify(t)
|
|
|
|
reg.Register(
|
|
httpmock.GraphQL(`query RepositoryList\b`),
|
|
httpmock.GraphQLQuery(`{"data":{"repositoryOwner":{"login":"octocat","repositories":{"totalCount":0}}}}`,
|
|
func(query string, params map[string]interface{}) {
|
|
assert.False(t, strings.Contains(query, "visibility"))
|
|
},
|
|
),
|
|
)
|
|
|
|
opts := ListOptions{
|
|
IO: ios,
|
|
HttpClient: func() (*http.Client, error) {
|
|
return &http.Client{Transport: reg}, nil
|
|
},
|
|
Config: func() (gh.Config, error) {
|
|
return config.NewBlankConfig(), nil
|
|
},
|
|
Now: func() time.Time {
|
|
t, _ := time.Parse(time.RFC822, "19 Feb 21 15:00 UTC")
|
|
return t
|
|
},
|
|
Limit: 30,
|
|
Detector: &fd.DisabledDetectorMock{},
|
|
}
|
|
|
|
err := listRun(&opts)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "", stderr.String())
|
|
assert.Equal(t, "", stdout.String())
|
|
}
|
|
|
|
func TestRepoList_invalidOwner(t *testing.T) {
|
|
ios, _, stdout, stderr := iostreams.Test()
|
|
ios.SetStdoutTTY(false)
|
|
ios.SetStdinTTY(false)
|
|
ios.SetStderrTTY(false)
|
|
|
|
reg := &httpmock.Registry{}
|
|
defer reg.Verify(t)
|
|
|
|
reg.Register(
|
|
httpmock.GraphQL(`query RepositoryList\b`),
|
|
httpmock.StringResponse(`{ "data": { "repositoryOwner": null } }`),
|
|
)
|
|
|
|
opts := ListOptions{
|
|
Owner: "nonexist",
|
|
IO: ios,
|
|
HttpClient: func() (*http.Client, error) {
|
|
return &http.Client{Transport: reg}, nil
|
|
},
|
|
Config: func() (gh.Config, error) {
|
|
return config.NewBlankConfig(), nil
|
|
},
|
|
Now: func() time.Time {
|
|
t, _ := time.Parse(time.RFC822, "19 Feb 21 15:00 UTC")
|
|
return t
|
|
},
|
|
Limit: 30,
|
|
Detector: &fd.DisabledDetectorMock{},
|
|
}
|
|
|
|
err := listRun(&opts)
|
|
assert.EqualError(t, err, `the owner handle "nonexist" was not recognized as either a GitHub user or an organization`)
|
|
assert.Equal(t, "", stderr.String())
|
|
assert.Equal(t, "", stdout.String())
|
|
}
|