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 }