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