package label import ( "bytes" "net/http" "testing" "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 TestNewCmdClone(t *testing.T) { tests := []struct { name string input string output cloneOptions wantErr bool errMsg string }{ { name: "no argument", input: "", wantErr: true, errMsg: "cannot clone labels: source-repository argument required", }, { name: "source-repository argument", input: "OWNER/REPO", output: cloneOptions{ SourceRepo: ghrepo.New("OWNER", "REPO"), }, }, { name: "force flag", input: "OWNER/REPO --force", output: cloneOptions{ SourceRepo: ghrepo.New("OWNER", "REPO"), Force: true, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { io, _, _, _ := iostreams.Test() f := &cmdutil.Factory{ IOStreams: io, } var gotOpts *cloneOptions cmd := newCmdClone(f, func(opts *cloneOptions) error { gotOpts = opts return nil }) argv, err := shlex.Split(tt.input) assert.NoError(t, err) 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.SourceRepo, gotOpts.SourceRepo) assert.Equal(t, tt.output.Force, gotOpts.Force) }) } } func TestCloneRun(t *testing.T) { tests := []struct { name string tty bool opts *cloneOptions httpStubs func(*httpmock.Registry) wantStdout string wantErr bool wantErrMsg string }{ { name: "clones all labels", tty: true, opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")}, 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": "Something isn't working" }, { "name": "docs", "color": "6cafc9" } ], "pageInfo": { "hasNextPage": false, "endCursor": "abcd1234" } } } } }`, func(s string, m map[string]interface{}) { expected := map[string]interface{}{ "owner": "cli", "repo": "cli", "orderBy": map[string]interface{}{ "direction": "ASC", "field": "CREATED_AT", }, "query": "", "limit": float64(100), } assert.Equal(t, expected, m) }), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.StatusStringResponse(201, ` { "name": "bug", "color": "d73a4a", "description": "Something isn't working" }`), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.StatusStringResponse(201, ` { "name": "docs", "color": "6cafc9" }`), ) }, wantStdout: "✓ Cloned 2 labels from cli/cli to OWNER/REPO\n", }, { name: "clones one label", tty: true, opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")}, httpStubs: func(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.StringResponse(` { "data": { "repository": { "labels": { "totalCount": 1, "nodes": [ { "name": "bug", "color": "d73a4a", "description": "Something isn't working" } ], "pageInfo": { "hasNextPage": false, "endCursor": "abcd1234" } } } } }`), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.StatusStringResponse(201, ` { "name": "bug", "color": "d73a4a", "description": "Something isn't working" }`), ) }, wantStdout: "✓ Cloned 1 label from cli/cli to OWNER/REPO\n", }, { name: "has no labels", tty: true, opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")}, httpStubs: func(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.StringResponse(` { "data": { "repository": { "labels": { "totalCount": 0, "nodes": [], "pageInfo": { "hasNextPage": false, "endCursor": "abcd1234" } } } } }`), ) }, wantStdout: "✓ Cloned 0 labels from cli/cli to OWNER/REPO\n", }, { name: "clones some labels", tty: true, opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")}, 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": "Something isn't working" }, { "name": "docs", "color": "6cafc9" } ], "pageInfo": { "hasNextPage": false, "endCursor": "abcd1234" } } } } }`), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.StatusStringResponse(201, ` { "name": "bug", "color": "d73a4a", "description": "Something isn't working" }`), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.WithHeader( httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`), "Content-Type", "application/json", ), ) }, wantStdout: "! Cloned 1 label of 2 from cli/cli to OWNER/REPO\n", }, { name: "clones no labels", tty: true, opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")}, 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": "Something isn't working" }, { "name": "docs", "color": "6cafc9" } ], "pageInfo": { "hasNextPage": false, "endCursor": "abcd1234" } } } } }`), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.WithHeader( httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`), "Content-Type", "application/json", ), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.WithHeader( httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`), "Content-Type", "application/json", ), ) }, wantStdout: "! Cloned 0 labels of 2 from cli/cli to OWNER/REPO\n", }, { name: "overwrites all labels", tty: true, opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli"), Force: true}, 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": "Something isn't working" }, { "name": "docs", "color": "6cafc9" } ], "pageInfo": { "hasNextPage": false, "endCursor": "abcd1234" } } } } }`), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.WithHeader( httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`), "Content-Type", "application/json", ), ) reg.Register( httpmock.REST("PATCH", "repos/OWNER/REPO/labels/bug"), httpmock.StatusStringResponse(201, ` { "color": "d73a4a", "description": "Something isn't working" }`), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.WithHeader( httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`), "Content-Type", "application/json", ), ) reg.Register( httpmock.REST("PATCH", "repos/OWNER/REPO/labels/docs"), httpmock.StatusStringResponse(201, ` { "color": "6cafc9" }`), ) }, wantStdout: "✓ Cloned 2 labels from cli/cli to OWNER/REPO\n", }, { name: "list error", tty: true, opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "invalid"), Force: true}, httpStubs: func(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.WithHeader( httpmock.StatusStringResponse(200, ` { "data": { "repository": null }, "errors": [ { "type": "NOT_FOUND", "path": [ "repository" ], "locations": [ { "line": 3, "column": 1 } ], "message": "Could not resolve to a Repository with the name 'cli/invalid'." } ] }`), "Content-Type", "application/json", ), ) }, wantErr: true, wantErrMsg: "GraphQL: Could not resolve to a Repository with the name 'cli/invalid'. (repository)", }, { name: "create error", tty: true, opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli"), Force: true}, httpStubs: func(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.StringResponse(` { "data": { "repository": { "labels": { "totalCount": 1, "nodes": [ { "name": "bug", "color": "d73a4a", "description": "Something isn't working" } ], "pageInfo": { "hasNextPage": false, "endCursor": "abcd1234" } } } } }`), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.WithHeader( httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"invalid","field":"color"}]}`), "Content-Type", "application/json", ), ) }, wantErr: true, wantErrMsg: "HTTP 422: Validation Failed (https://api.github.com/repos/OWNER/REPO/labels)\nLabel.color is invalid", }, { name: "clones pages of labels", tty: true, opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli"), Force: true}, 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": "Something isn't working" } ], "pageInfo": { "hasNextPage": true, "endCursor": "abcd1234" } } } } }`, func(s string, m map[string]interface{}) { assert.Equal(t, "cli", m["owner"]) assert.Equal(t, "cli", m["repo"]) assert.Equal(t, float64(100), m["limit"].(float64)) }), ) reg.Register( httpmock.GraphQL(`query LabelList\b`), httpmock.GraphQLQuery(` { "data": { "repository": { "labels": { "totalCount": 2, "nodes": [ { "name": "docs", "color": "6cafc9" } ], "pageInfo": { "hasNextPage": false, "endCursor": "abcd1234" } } } } }`, func(s string, m map[string]interface{}) { assert.Equal(t, "cli", m["owner"]) assert.Equal(t, "cli", m["repo"]) assert.Equal(t, float64(100), m["limit"].(float64)) }), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.StatusStringResponse(201, ` { "name": "bug", "color": "d73a4a", "description": "Something isn't working" }`), ) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/labels"), httpmock.StatusStringResponse(201, ` { "name": "docs", "color": "6cafc9" }`), ) }, wantStdout: "✓ Cloned 2 labels from cli/cli to OWNER/REPO\n", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reg := &httpmock.Registry{} defer reg.Verify(t) if tt.httpStubs != nil { tt.httpStubs(reg) } tt.opts.HttpClient = func() (*http.Client, error) { return &http.Client{Transport: reg}, nil } io, _, stdout, _ := iostreams.Test() io.SetStdoutTTY(tt.tty) io.SetStdinTTY(tt.tty) io.SetStderrTTY(tt.tty) tt.opts.IO = io tt.opts.BaseRepo = func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil } err := cloneRun(tt.opts) if tt.wantErr { assert.EqualError(t, err, tt.wantErrMsg) } else { assert.NoError(t, err) } assert.Equal(t, tt.wantStdout, stdout.String()) }) } }