github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/label/clone_test.go (about)

     1  package label
     2  
     3  import (
     4  	"bytes"
     5  	"net/http"
     6  	"testing"
     7  
     8  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
     9  	"github.com/ungtb10d/cli/v2/pkg/cmdutil"
    10  	"github.com/ungtb10d/cli/v2/pkg/httpmock"
    11  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    12  	"github.com/google/shlex"
    13  	"github.com/stretchr/testify/assert"
    14  )
    15  
    16  func TestNewCmdClone(t *testing.T) {
    17  	tests := []struct {
    18  		name    string
    19  		input   string
    20  		output  cloneOptions
    21  		wantErr bool
    22  		errMsg  string
    23  	}{
    24  		{
    25  			name:    "no argument",
    26  			input:   "",
    27  			wantErr: true,
    28  			errMsg:  "cannot clone labels: source-repository argument required",
    29  		},
    30  		{
    31  			name:  "source-repository argument",
    32  			input: "OWNER/REPO",
    33  			output: cloneOptions{
    34  				SourceRepo: ghrepo.New("OWNER", "REPO"),
    35  			},
    36  		},
    37  		{
    38  			name:  "force flag",
    39  			input: "OWNER/REPO --force",
    40  			output: cloneOptions{
    41  				SourceRepo: ghrepo.New("OWNER", "REPO"),
    42  				Force:      true,
    43  			},
    44  		},
    45  	}
    46  
    47  	for _, tt := range tests {
    48  		t.Run(tt.name, func(t *testing.T) {
    49  			io, _, _, _ := iostreams.Test()
    50  			f := &cmdutil.Factory{
    51  				IOStreams: io,
    52  			}
    53  
    54  			var gotOpts *cloneOptions
    55  			cmd := newCmdClone(f, func(opts *cloneOptions) error {
    56  				gotOpts = opts
    57  				return nil
    58  			})
    59  
    60  			argv, err := shlex.Split(tt.input)
    61  			assert.NoError(t, err)
    62  
    63  			cmd.SetArgs(argv)
    64  			cmd.SetIn(&bytes.Buffer{})
    65  			cmd.SetOut(&bytes.Buffer{})
    66  			cmd.SetErr(&bytes.Buffer{})
    67  
    68  			_, err = cmd.ExecuteC()
    69  			if tt.wantErr {
    70  				assert.EqualError(t, err, tt.errMsg)
    71  				return
    72  			}
    73  
    74  			assert.NoError(t, err)
    75  			assert.Equal(t, tt.output.SourceRepo, gotOpts.SourceRepo)
    76  			assert.Equal(t, tt.output.Force, gotOpts.Force)
    77  		})
    78  	}
    79  }
    80  
    81  func TestCloneRun(t *testing.T) {
    82  	tests := []struct {
    83  		name       string
    84  		tty        bool
    85  		opts       *cloneOptions
    86  		httpStubs  func(*httpmock.Registry)
    87  		wantStdout string
    88  		wantErr    bool
    89  		wantErrMsg string
    90  	}{
    91  		{
    92  			name: "clones all labels",
    93  			tty:  true,
    94  			opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")},
    95  			httpStubs: func(reg *httpmock.Registry) {
    96  				reg.Register(
    97  					httpmock.GraphQL(`query LabelList\b`),
    98  					httpmock.GraphQLQuery(`
    99  					{
   100  						"data": {
   101  							"repository": {
   102  								"labels": {
   103  									"totalCount": 2,
   104  									"nodes": [
   105  										{
   106  											"name": "bug",
   107  											"color": "d73a4a",
   108  											"description": "Something isn't working"
   109  										},
   110  										{
   111  											"name": "docs",
   112  											"color": "6cafc9"
   113  										}
   114  									],
   115  									"pageInfo": {
   116  										"hasNextPage": false,
   117  										"endCursor": "abcd1234"
   118  									}
   119  								}
   120  							}
   121  						}
   122  					}`, func(s string, m map[string]interface{}) {
   123  						expected := map[string]interface{}{
   124  							"owner": "cli",
   125  							"repo":  "cli",
   126  							"orderBy": map[string]interface{}{
   127  								"direction": "ASC",
   128  								"field":     "CREATED_AT",
   129  							},
   130  							"query": "",
   131  							"limit": float64(100),
   132  						}
   133  						assert.Equal(t, expected, m)
   134  					}),
   135  				)
   136  				reg.Register(
   137  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   138  					httpmock.StatusStringResponse(201, `
   139  					{
   140  						"name": "bug",
   141  						"color": "d73a4a",
   142  						"description": "Something isn't working"
   143  					}`),
   144  				)
   145  				reg.Register(
   146  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   147  					httpmock.StatusStringResponse(201, `
   148  					{
   149  						"name": "docs",
   150  						"color": "6cafc9"
   151  					}`),
   152  				)
   153  			},
   154  			wantStdout: "✓ Cloned 2 labels from ungtb10d/cli to OWNER/REPO\n",
   155  		},
   156  		{
   157  			name: "clones one label",
   158  			tty:  true,
   159  			opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")},
   160  			httpStubs: func(reg *httpmock.Registry) {
   161  				reg.Register(
   162  					httpmock.GraphQL(`query LabelList\b`),
   163  					httpmock.StringResponse(`
   164  					{
   165  						"data": {
   166  							"repository": {
   167  								"labels": {
   168  									"totalCount": 1,
   169  									"nodes": [
   170  										{
   171  											"name": "bug",
   172  											"color": "d73a4a",
   173  											"description": "Something isn't working"
   174  										}
   175  									],
   176  									"pageInfo": {
   177  										"hasNextPage": false,
   178  										"endCursor": "abcd1234"
   179  									}
   180  								}
   181  							}
   182  						}
   183  					}`),
   184  				)
   185  				reg.Register(
   186  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   187  					httpmock.StatusStringResponse(201, `
   188  					{
   189  						"name": "bug",
   190  						"color": "d73a4a",
   191  						"description": "Something isn't working"
   192  					}`),
   193  				)
   194  			},
   195  			wantStdout: "✓ Cloned 1 label from ungtb10d/cli to OWNER/REPO\n",
   196  		},
   197  		{
   198  			name: "has no labels",
   199  			tty:  true,
   200  			opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")},
   201  			httpStubs: func(reg *httpmock.Registry) {
   202  				reg.Register(
   203  					httpmock.GraphQL(`query LabelList\b`),
   204  					httpmock.StringResponse(`
   205  					{
   206  						"data": {
   207  							"repository": {
   208  								"labels": {
   209  									"totalCount": 0,
   210  									"nodes": [],
   211  									"pageInfo": {
   212  										"hasNextPage": false,
   213  										"endCursor": "abcd1234"
   214  									}
   215  								}
   216  							}
   217  						}
   218  					}`),
   219  				)
   220  			},
   221  			wantStdout: "✓ Cloned 0 labels from ungtb10d/cli to OWNER/REPO\n",
   222  		},
   223  		{
   224  			name: "clones some labels",
   225  			tty:  true,
   226  			opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")},
   227  			httpStubs: func(reg *httpmock.Registry) {
   228  				reg.Register(
   229  					httpmock.GraphQL(`query LabelList\b`),
   230  					httpmock.StringResponse(`
   231  					{
   232  						"data": {
   233  							"repository": {
   234  								"labels": {
   235  									"totalCount": 2,
   236  									"nodes": [
   237  										{
   238  											"name": "bug",
   239  											"color": "d73a4a",
   240  											"description": "Something isn't working"
   241  										},
   242  										{
   243  											"name": "docs",
   244  											"color": "6cafc9"
   245  										}
   246  									],
   247  									"pageInfo": {
   248  										"hasNextPage": false,
   249  										"endCursor": "abcd1234"
   250  									}
   251  								}
   252  							}
   253  						}
   254  					}`),
   255  				)
   256  				reg.Register(
   257  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   258  					httpmock.StatusStringResponse(201, `
   259  					{
   260  						"name": "bug",
   261  						"color": "d73a4a",
   262  						"description": "Something isn't working"
   263  					}`),
   264  				)
   265  				reg.Register(
   266  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   267  					httpmock.WithHeader(
   268  						httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`),
   269  						"Content-Type",
   270  						"application/json",
   271  					),
   272  				)
   273  			},
   274  			wantStdout: "! Cloned 1 label of 2 from ungtb10d/cli to OWNER/REPO\n",
   275  		},
   276  		{
   277  			name: "clones no labels",
   278  			tty:  true,
   279  			opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli")},
   280  			httpStubs: func(reg *httpmock.Registry) {
   281  				reg.Register(
   282  					httpmock.GraphQL(`query LabelList\b`),
   283  					httpmock.StringResponse(`
   284  					{
   285  						"data": {
   286  							"repository": {
   287  								"labels": {
   288  									"totalCount": 2,
   289  									"nodes": [
   290  										{
   291  											"name": "bug",
   292  											"color": "d73a4a",
   293  											"description": "Something isn't working"
   294  										},
   295  										{
   296  											"name": "docs",
   297  											"color": "6cafc9"
   298  										}
   299  									],
   300  									"pageInfo": {
   301  										"hasNextPage": false,
   302  										"endCursor": "abcd1234"
   303  									}
   304  								}
   305  							}
   306  						}
   307  					}`),
   308  				)
   309  				reg.Register(
   310  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   311  					httpmock.WithHeader(
   312  						httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`),
   313  						"Content-Type",
   314  						"application/json",
   315  					),
   316  				)
   317  				reg.Register(
   318  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   319  					httpmock.WithHeader(
   320  						httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`),
   321  						"Content-Type",
   322  						"application/json",
   323  					),
   324  				)
   325  			},
   326  			wantStdout: "! Cloned 0 labels of 2 from ungtb10d/cli to OWNER/REPO\n",
   327  		},
   328  		{
   329  			name: "overwrites all labels",
   330  			tty:  true,
   331  			opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli"), Force: true},
   332  			httpStubs: func(reg *httpmock.Registry) {
   333  				reg.Register(
   334  					httpmock.GraphQL(`query LabelList\b`),
   335  					httpmock.StringResponse(`
   336  					{
   337  						"data": {
   338  							"repository": {
   339  								"labels": {
   340  									"totalCount": 2,
   341  									"nodes": [
   342  										{
   343  											"name": "bug",
   344  											"color": "d73a4a",
   345  											"description": "Something isn't working"
   346  										},
   347  										{
   348  											"name": "docs",
   349  											"color": "6cafc9"
   350  										}
   351  									],
   352  									"pageInfo": {
   353  										"hasNextPage": false,
   354  										"endCursor": "abcd1234"
   355  									}
   356  								}
   357  							}
   358  						}
   359  					}`),
   360  				)
   361  				reg.Register(
   362  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   363  					httpmock.WithHeader(
   364  						httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`),
   365  						"Content-Type",
   366  						"application/json",
   367  					),
   368  				)
   369  				reg.Register(
   370  					httpmock.REST("PATCH", "repos/OWNER/REPO/labels/bug"),
   371  					httpmock.StatusStringResponse(201, `
   372  					{
   373  						"color": "d73a4a",
   374  						"description": "Something isn't working"
   375  					}`),
   376  				)
   377  				reg.Register(
   378  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   379  					httpmock.WithHeader(
   380  						httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`),
   381  						"Content-Type",
   382  						"application/json",
   383  					),
   384  				)
   385  				reg.Register(
   386  					httpmock.REST("PATCH", "repos/OWNER/REPO/labels/docs"),
   387  					httpmock.StatusStringResponse(201, `
   388  					{
   389  						"color": "6cafc9"
   390  					}`),
   391  				)
   392  			},
   393  			wantStdout: "✓ Cloned 2 labels from ungtb10d/cli to OWNER/REPO\n",
   394  		},
   395  		{
   396  			name: "list error",
   397  			tty:  true,
   398  			opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "invalid"), Force: true},
   399  			httpStubs: func(reg *httpmock.Registry) {
   400  				reg.Register(
   401  					httpmock.GraphQL(`query LabelList\b`),
   402  					httpmock.WithHeader(
   403  						httpmock.StatusStringResponse(200, `
   404  						{
   405  							"data": {
   406  							  "repository": null
   407  							},
   408  							"errors": [
   409  							  {
   410  								"type": "NOT_FOUND",
   411  								"path": [
   412  								  "repository"
   413  								],
   414  								"locations": [
   415  								  {
   416  									"line": 3,
   417  									"column": 1
   418  								  }
   419  								],
   420  								"message": "Could not resolve to a Repository with the name 'cli/invalid'."
   421  							  }
   422  							]
   423  						}`),
   424  						"Content-Type",
   425  						"application/json",
   426  					),
   427  				)
   428  			},
   429  			wantErr:    true,
   430  			wantErrMsg: "GraphQL: Could not resolve to a Repository with the name 'cli/invalid'. (repository)",
   431  		},
   432  		{
   433  			name: "create error",
   434  			tty:  true,
   435  			opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli"), Force: true},
   436  			httpStubs: func(reg *httpmock.Registry) {
   437  				reg.Register(
   438  					httpmock.GraphQL(`query LabelList\b`),
   439  					httpmock.StringResponse(`
   440  					{
   441  						"data": {
   442  							"repository": {
   443  								"labels": {
   444  									"totalCount": 1,
   445  									"nodes": [
   446  										{
   447  											"name": "bug",
   448  											"color": "d73a4a",
   449  											"description": "Something isn't working"
   450  										}
   451  									],
   452  									"pageInfo": {
   453  										"hasNextPage": false,
   454  										"endCursor": "abcd1234"
   455  									}
   456  								}
   457  							}
   458  						}
   459  					}`),
   460  				)
   461  				reg.Register(
   462  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   463  					httpmock.WithHeader(
   464  						httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"invalid","field":"color"}]}`),
   465  						"Content-Type",
   466  						"application/json",
   467  					),
   468  				)
   469  			},
   470  			wantErr:    true,
   471  			wantErrMsg: "HTTP 422: Validation Failed (https://api.github.com/repos/OWNER/REPO/labels)\nLabel.color is invalid",
   472  		},
   473  		{
   474  			name: "clones pages of labels",
   475  			tty:  true,
   476  			opts: &cloneOptions{SourceRepo: ghrepo.New("cli", "cli"), Force: true},
   477  			httpStubs: func(reg *httpmock.Registry) {
   478  				reg.Register(
   479  					httpmock.GraphQL(`query LabelList\b`),
   480  					httpmock.GraphQLQuery(`
   481  					{
   482  						"data": {
   483  							"repository": {
   484  								"labels": {
   485  									"totalCount": 2,
   486  									"nodes": [
   487  										{
   488  											"name": "bug",
   489  											"color": "d73a4a",
   490  											"description": "Something isn't working"
   491  										}
   492  									],
   493  									"pageInfo": {
   494  										"hasNextPage": true,
   495  										"endCursor": "abcd1234"
   496  									}
   497  								}
   498  							}
   499  						}
   500  					}`, func(s string, m map[string]interface{}) {
   501  						assert.Equal(t, "cli", m["owner"])
   502  						assert.Equal(t, "cli", m["repo"])
   503  						assert.Equal(t, float64(100), m["limit"].(float64))
   504  					}),
   505  				)
   506  				reg.Register(
   507  					httpmock.GraphQL(`query LabelList\b`),
   508  					httpmock.GraphQLQuery(`
   509  					{
   510  						"data": {
   511  							"repository": {
   512  								"labels": {
   513  									"totalCount": 2,
   514  									"nodes": [
   515  										{
   516  											"name": "docs",
   517  											"color": "6cafc9"
   518  										}
   519  									],
   520  									"pageInfo": {
   521  										"hasNextPage": false,
   522  										"endCursor": "abcd1234"
   523  									}
   524  								}
   525  							}
   526  						}
   527  					}`, func(s string, m map[string]interface{}) {
   528  						assert.Equal(t, "cli", m["owner"])
   529  						assert.Equal(t, "cli", m["repo"])
   530  						assert.Equal(t, float64(100), m["limit"].(float64))
   531  					}),
   532  				)
   533  				reg.Register(
   534  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   535  					httpmock.StatusStringResponse(201, `
   536  					{
   537  						"name": "bug",
   538  						"color": "d73a4a",
   539  						"description": "Something isn't working"
   540  					}`),
   541  				)
   542  				reg.Register(
   543  					httpmock.REST("POST", "repos/OWNER/REPO/labels"),
   544  					httpmock.StatusStringResponse(201, `
   545  					{
   546  						"name": "docs",
   547  						"color": "6cafc9"
   548  					}`),
   549  				)
   550  			},
   551  			wantStdout: "✓ Cloned 2 labels from ungtb10d/cli to OWNER/REPO\n",
   552  		},
   553  	}
   554  
   555  	for _, tt := range tests {
   556  		t.Run(tt.name, func(t *testing.T) {
   557  			reg := &httpmock.Registry{}
   558  			defer reg.Verify(t)
   559  			if tt.httpStubs != nil {
   560  				tt.httpStubs(reg)
   561  			}
   562  			tt.opts.HttpClient = func() (*http.Client, error) {
   563  				return &http.Client{Transport: reg}, nil
   564  			}
   565  			io, _, stdout, _ := iostreams.Test()
   566  			io.SetStdoutTTY(tt.tty)
   567  			io.SetStdinTTY(tt.tty)
   568  			io.SetStderrTTY(tt.tty)
   569  			tt.opts.IO = io
   570  			tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
   571  				return ghrepo.New("OWNER", "REPO"), nil
   572  			}
   573  
   574  			err := cloneRun(tt.opts)
   575  
   576  			if tt.wantErr {
   577  				assert.EqualError(t, err, tt.wantErrMsg)
   578  			} else {
   579  				assert.NoError(t, err)
   580  			}
   581  
   582  			assert.Equal(t, tt.wantStdout, stdout.String())
   583  		})
   584  	}
   585  }