github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/release/edit/edit_test.go (about) 1 package edit 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "testing" 10 11 "github.com/ungtb10d/cli/v2/internal/ghrepo" 12 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 13 "github.com/ungtb10d/cli/v2/pkg/httpmock" 14 "github.com/ungtb10d/cli/v2/pkg/iostreams" 15 "github.com/google/shlex" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func Test_NewCmdEdit(t *testing.T) { 21 tempDir := t.TempDir() 22 tf, err := os.CreateTemp(tempDir, "release-create") 23 require.NoError(t, err) 24 fmt.Fprint(tf, "MY NOTES") 25 tf.Close() 26 27 tests := []struct { 28 name string 29 args string 30 isTTY bool 31 stdin string 32 want EditOptions 33 wantErr string 34 }{ 35 { 36 name: "no arguments notty", 37 args: "", 38 isTTY: false, 39 wantErr: "accepts 1 arg(s), received 0", 40 }, 41 { 42 name: "provide title and notes", 43 args: "v1.2.3 --title 'Some Title' --notes 'Some Notes'", 44 isTTY: false, 45 want: EditOptions{ 46 TagName: "", 47 Name: stringPtr("Some Title"), 48 Body: stringPtr("Some Notes"), 49 }, 50 }, 51 { 52 name: "provide discussion category", 53 args: "v1.2.3 --discussion-category some-category", 54 isTTY: false, 55 want: EditOptions{ 56 TagName: "", 57 DiscussionCategory: stringPtr("some-category"), 58 }, 59 }, 60 { 61 name: "provide tag and target commitish", 62 args: "v1.2.3 --tag v9.8.7 --target 97ea5e77b4d61d5d80ed08f7512847dee3ec9af5", 63 isTTY: false, 64 want: EditOptions{ 65 TagName: "v9.8.7", 66 Target: "97ea5e77b4d61d5d80ed08f7512847dee3ec9af5", 67 }, 68 }, 69 { 70 name: "provide prerelease", 71 args: "v1.2.3 --prerelease", 72 isTTY: false, 73 want: EditOptions{ 74 TagName: "", 75 Prerelease: boolPtr(true), 76 }, 77 }, 78 { 79 name: "provide prerelease=false", 80 args: "v1.2.3 --prerelease=false", 81 isTTY: false, 82 want: EditOptions{ 83 TagName: "", 84 Prerelease: boolPtr(false), 85 }, 86 }, 87 { 88 name: "provide draft", 89 args: "v1.2.3 --draft", 90 isTTY: false, 91 want: EditOptions{ 92 TagName: "", 93 Draft: boolPtr(true), 94 }, 95 }, 96 { 97 name: "provide draft=false", 98 args: "v1.2.3 --draft=false", 99 isTTY: false, 100 want: EditOptions{ 101 TagName: "", 102 Draft: boolPtr(false), 103 }, 104 }, 105 { 106 name: "latest", 107 args: "v1.2.3 --latest", 108 isTTY: false, 109 want: EditOptions{ 110 TagName: "", 111 IsLatest: boolPtr(true), 112 }, 113 }, 114 { 115 name: "not latest", 116 args: "v1.2.3 --latest=false", 117 isTTY: false, 118 want: EditOptions{ 119 TagName: "", 120 IsLatest: boolPtr(false), 121 }, 122 }, 123 { 124 name: "provide notes from file", 125 args: fmt.Sprintf(`v1.2.3 -F '%s'`, tf.Name()), 126 isTTY: false, 127 want: EditOptions{ 128 TagName: "", 129 Body: stringPtr("MY NOTES"), 130 }, 131 }, 132 { 133 name: "provide notes from stdin", 134 args: "v1.2.3 -F -", 135 isTTY: false, 136 stdin: "MY NOTES", 137 want: EditOptions{ 138 TagName: "", 139 Body: stringPtr("MY NOTES"), 140 }, 141 }, 142 } 143 144 for _, tt := range tests { 145 t.Run(tt.name, func(t *testing.T) { 146 ios, stdin, _, _ := iostreams.Test() 147 if tt.stdin == "" { 148 ios.SetStdinTTY(tt.isTTY) 149 } else { 150 ios.SetStdinTTY(false) 151 fmt.Fprint(stdin, tt.stdin) 152 } 153 ios.SetStdoutTTY(tt.isTTY) 154 ios.SetStderrTTY(tt.isTTY) 155 156 f := &cmdutil.Factory{ 157 IOStreams: ios, 158 } 159 160 var opts *EditOptions 161 cmd := NewCmdEdit(f, func(o *EditOptions) error { 162 opts = o 163 return nil 164 }) 165 cmd.PersistentFlags().StringP("repo", "R", "", "") 166 167 argv, err := shlex.Split(tt.args) 168 require.NoError(t, err) 169 cmd.SetArgs(argv) 170 171 cmd.SetIn(&bytes.Buffer{}) 172 cmd.SetOut(io.Discard) 173 cmd.SetErr(io.Discard) 174 175 _, err = cmd.ExecuteC() 176 if tt.wantErr != "" { 177 require.EqualError(t, err, tt.wantErr) 178 return 179 } else { 180 require.NoError(t, err) 181 } 182 183 assert.Equal(t, tt.want.TagName, opts.TagName) 184 assert.Equal(t, tt.want.Target, opts.Target) 185 assert.Equal(t, tt.want.Name, opts.Name) 186 assert.Equal(t, tt.want.Body, opts.Body) 187 assert.Equal(t, tt.want.DiscussionCategory, opts.DiscussionCategory) 188 assert.Equal(t, tt.want.Draft, opts.Draft) 189 assert.Equal(t, tt.want.Prerelease, opts.Prerelease) 190 assert.Equal(t, tt.want.IsLatest, opts.IsLatest) 191 }) 192 } 193 } 194 195 func Test_editRun(t *testing.T) { 196 tests := []struct { 197 name string 198 isTTY bool 199 opts EditOptions 200 httpStubs func(t *testing.T, reg *httpmock.Registry) 201 wantErr string 202 wantStdout string 203 wantStderr string 204 }{ 205 { 206 name: "edit the tag name", 207 isTTY: true, 208 opts: EditOptions{ 209 TagName: "v1.2.4", 210 }, 211 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 212 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 213 assert.Equal(t, map[string]interface{}{ 214 "tag_name": "v1.2.4", 215 }, params) 216 }) 217 }, 218 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 219 wantStderr: "", 220 }, 221 { 222 name: "edit the target", 223 isTTY: true, 224 opts: EditOptions{ 225 Target: "c0ff33", 226 }, 227 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 228 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 229 assert.Equal(t, map[string]interface{}{ 230 "tag_name": "v1.2.3", 231 "target_commitish": "c0ff33", 232 }, params) 233 }) 234 }, 235 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 236 wantStderr: "", 237 }, 238 { 239 name: "edit the release name", 240 isTTY: true, 241 opts: EditOptions{ 242 Name: stringPtr("Hot Release #1"), 243 }, 244 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 245 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 246 assert.Equal(t, map[string]interface{}{ 247 "tag_name": "v1.2.3", 248 "name": "Hot Release #1", 249 }, params) 250 }) 251 }, 252 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 253 wantStderr: "", 254 }, 255 { 256 name: "edit the discussion category", 257 isTTY: true, 258 opts: EditOptions{ 259 DiscussionCategory: stringPtr("some-category"), 260 }, 261 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 262 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 263 assert.Equal(t, map[string]interface{}{ 264 "tag_name": "v1.2.3", 265 "discussion_category_name": "some-category", 266 }, params) 267 }) 268 }, 269 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 270 wantStderr: "", 271 }, 272 { 273 name: "edit the latest marker", 274 isTTY: false, 275 opts: EditOptions{ 276 IsLatest: boolPtr(true), 277 }, 278 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 279 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 280 assert.Equal(t, map[string]interface{}{ 281 "tag_name": "v1.2.3", 282 "make_latest": "true", 283 }, params) 284 }) 285 }, 286 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 287 wantStderr: "", 288 }, 289 { 290 name: "edit the release name (empty)", 291 isTTY: true, 292 opts: EditOptions{ 293 Name: stringPtr(""), 294 }, 295 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 296 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 297 assert.Equal(t, map[string]interface{}{ 298 "tag_name": "v1.2.3", 299 "name": "", 300 }, params) 301 }) 302 }, 303 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 304 wantStderr: "", 305 }, 306 { 307 name: "edit the release notes", 308 isTTY: true, 309 opts: EditOptions{ 310 Body: stringPtr("Release Notes:\n- Fix Bug #1\n- Fix Bug #2"), 311 }, 312 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 313 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 314 assert.Equal(t, map[string]interface{}{ 315 "tag_name": "v1.2.3", 316 "body": "Release Notes:\n- Fix Bug #1\n- Fix Bug #2", 317 }, params) 318 }) 319 }, 320 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 321 wantStderr: "", 322 }, 323 { 324 name: "edit the release notes (empty)", 325 isTTY: true, 326 opts: EditOptions{ 327 Body: stringPtr(""), 328 }, 329 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 330 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 331 assert.Equal(t, map[string]interface{}{ 332 "tag_name": "v1.2.3", 333 "body": "", 334 }, params) 335 }) 336 }, 337 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 338 wantStderr: "", 339 }, 340 { 341 name: "edit draft (true)", 342 isTTY: true, 343 opts: EditOptions{ 344 Draft: boolPtr(true), 345 }, 346 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 347 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 348 assert.Equal(t, map[string]interface{}{ 349 "tag_name": "v1.2.3", 350 "draft": true, 351 }, params) 352 }) 353 }, 354 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 355 wantStderr: "", 356 }, 357 { 358 name: "edit draft (false)", 359 isTTY: true, 360 opts: EditOptions{ 361 Draft: boolPtr(false), 362 }, 363 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 364 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 365 assert.Equal(t, map[string]interface{}{ 366 "tag_name": "v1.2.3", 367 "draft": false, 368 }, params) 369 }) 370 }, 371 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 372 wantStderr: "", 373 }, 374 { 375 name: "edit prerelease (true)", 376 isTTY: true, 377 opts: EditOptions{ 378 Prerelease: boolPtr(true), 379 }, 380 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 381 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 382 assert.Equal(t, map[string]interface{}{ 383 "tag_name": "v1.2.3", 384 "prerelease": true, 385 }, params) 386 }) 387 }, 388 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 389 wantStderr: "", 390 }, 391 { 392 name: "edit prerelease (false)", 393 isTTY: true, 394 opts: EditOptions{ 395 Prerelease: boolPtr(false), 396 }, 397 httpStubs: func(t *testing.T, reg *httpmock.Registry) { 398 mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { 399 assert.Equal(t, map[string]interface{}{ 400 "tag_name": "v1.2.3", 401 "prerelease": false, 402 }, params) 403 }) 404 }, 405 wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", 406 wantStderr: "", 407 }, 408 } 409 410 for _, tt := range tests { 411 t.Run(tt.name, func(t *testing.T) { 412 ios, _, stdout, stderr := iostreams.Test() 413 ios.SetStdoutTTY(tt.isTTY) 414 ios.SetStdinTTY(tt.isTTY) 415 ios.SetStderrTTY(tt.isTTY) 416 417 fakeHTTP := &httpmock.Registry{} 418 fakeHTTP.Register(httpmock.REST("GET", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.JSONResponse(map[string]interface{}{ 419 "id": 12345, 420 "tag_name": "v1.2.3", 421 })) 422 if tt.httpStubs != nil { 423 tt.httpStubs(t, fakeHTTP) 424 } 425 defer fakeHTTP.Verify(t) 426 427 tt.opts.IO = ios 428 tt.opts.HttpClient = func() (*http.Client, error) { 429 return &http.Client{Transport: fakeHTTP}, nil 430 } 431 tt.opts.BaseRepo = func() (ghrepo.Interface, error) { 432 return ghrepo.FromFullName("OWNER/REPO") 433 } 434 435 err := editRun("v1.2.3", &tt.opts) 436 if tt.wantErr != "" { 437 require.EqualError(t, err, tt.wantErr) 438 return 439 } else { 440 require.NoError(t, err) 441 } 442 443 assert.Equal(t, tt.wantStdout, stdout.String()) 444 assert.Equal(t, tt.wantStderr, stderr.String()) 445 }) 446 } 447 } 448 449 func mockSuccessfulEditResponse(reg *httpmock.Registry, cb func(params map[string]interface{})) { 450 matcher := httpmock.REST("PATCH", "repos/OWNER/REPO/releases/12345") 451 responder := httpmock.RESTPayload(201, `{ 452 "html_url": "https://github.com/OWNER/REPO/releases/tag/v1.2.3" 453 }`, cb) 454 reg.Register(matcher, responder) 455 } 456 457 func boolPtr(b bool) *bool { 458 return &b 459 } 460 461 func stringPtr(s string) *string { 462 return &s 463 }