github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/gist/edit/edit_test.go (about) 1 package edit 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "net/http" 8 "os" 9 "path/filepath" 10 "testing" 11 12 "github.com/ungtb10d/cli/v2/internal/config" 13 "github.com/ungtb10d/cli/v2/pkg/cmd/gist/shared" 14 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 15 "github.com/ungtb10d/cli/v2/pkg/httpmock" 16 "github.com/ungtb10d/cli/v2/pkg/iostreams" 17 "github.com/ungtb10d/cli/v2/pkg/prompt" 18 "github.com/google/shlex" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 ) 22 23 func Test_getFilesToAdd(t *testing.T) { 24 filename := "gist-test.txt" 25 26 gf, err := getFilesToAdd(filename, []byte("hello")) 27 require.NoError(t, err) 28 29 assert.Equal(t, map[string]*shared.GistFile{ 30 filename: { 31 Filename: filename, 32 Content: "hello", 33 }, 34 }, gf) 35 } 36 37 func TestNewCmdEdit(t *testing.T) { 38 tests := []struct { 39 name string 40 cli string 41 wants EditOptions 42 }{ 43 { 44 name: "no flags", 45 cli: "123", 46 wants: EditOptions{ 47 Selector: "123", 48 }, 49 }, 50 { 51 name: "filename", 52 cli: "123 --filename cool.md", 53 wants: EditOptions{ 54 Selector: "123", 55 EditFilename: "cool.md", 56 }, 57 }, 58 { 59 name: "add", 60 cli: "123 --add cool.md", 61 wants: EditOptions{ 62 Selector: "123", 63 AddFilename: "cool.md", 64 }, 65 }, 66 { 67 name: "add with source", 68 cli: "123 --add cool.md -", 69 wants: EditOptions{ 70 Selector: "123", 71 AddFilename: "cool.md", 72 SourceFile: "-", 73 }, 74 }, 75 { 76 name: "description", 77 cli: `123 --desc "my new description"`, 78 wants: EditOptions{ 79 Selector: "123", 80 Description: "my new description", 81 }, 82 }, 83 } 84 85 for _, tt := range tests { 86 t.Run(tt.name, func(t *testing.T) { 87 f := &cmdutil.Factory{} 88 89 argv, err := shlex.Split(tt.cli) 90 assert.NoError(t, err) 91 92 var gotOpts *EditOptions 93 cmd := NewCmdEdit(f, func(opts *EditOptions) error { 94 gotOpts = opts 95 return nil 96 }) 97 cmd.SetArgs(argv) 98 cmd.SetIn(&bytes.Buffer{}) 99 cmd.SetOut(&bytes.Buffer{}) 100 cmd.SetErr(&bytes.Buffer{}) 101 102 _, err = cmd.ExecuteC() 103 assert.NoError(t, err) 104 105 assert.Equal(t, tt.wants.EditFilename, gotOpts.EditFilename) 106 assert.Equal(t, tt.wants.AddFilename, gotOpts.AddFilename) 107 assert.Equal(t, tt.wants.Selector, gotOpts.Selector) 108 }) 109 } 110 } 111 112 func Test_editRun(t *testing.T) { 113 fileToAdd := filepath.Join(t.TempDir(), "gist-test.txt") 114 err := os.WriteFile(fileToAdd, []byte("hello"), 0600) 115 require.NoError(t, err) 116 117 tests := []struct { 118 name string 119 opts *EditOptions 120 gist *shared.Gist 121 httpStubs func(*httpmock.Registry) 122 askStubs func(*prompt.AskStubber) 123 nontty bool 124 stdin string 125 wantErr string 126 wantParams map[string]interface{} 127 }{ 128 { 129 name: "no such gist", 130 wantErr: "gist not found: 1234", 131 }, 132 { 133 name: "one file", 134 gist: &shared.Gist{ 135 ID: "1234", 136 Files: map[string]*shared.GistFile{ 137 "cicada.txt": { 138 Filename: "cicada.txt", 139 Content: "bwhiizzzbwhuiiizzzz", 140 Type: "text/plain", 141 }, 142 }, 143 Owner: &shared.GistOwner{Login: "octocat"}, 144 }, 145 httpStubs: func(reg *httpmock.Registry) { 146 reg.Register(httpmock.REST("POST", "gists/1234"), 147 httpmock.StatusStringResponse(201, "{}")) 148 }, 149 wantParams: map[string]interface{}{ 150 "description": "", 151 "updated_at": "0001-01-01T00:00:00Z", 152 "public": false, 153 "files": map[string]interface{}{ 154 "cicada.txt": map[string]interface{}{ 155 "content": "new file content", 156 "filename": "cicada.txt", 157 "type": "text/plain", 158 }, 159 }, 160 }, 161 }, 162 { 163 name: "multiple files, submit", 164 askStubs: func(as *prompt.AskStubber) { 165 as.StubPrompt("Edit which file?").AnswerWith("unix.md") 166 as.StubPrompt("What next?").AnswerWith("Submit") 167 }, 168 gist: &shared.Gist{ 169 ID: "1234", 170 Description: "catbug", 171 Files: map[string]*shared.GistFile{ 172 "cicada.txt": { 173 Filename: "cicada.txt", 174 Content: "bwhiizzzbwhuiiizzzz", 175 Type: "text/plain", 176 }, 177 "unix.md": { 178 Filename: "unix.md", 179 Content: "meow", 180 Type: "application/markdown", 181 }, 182 }, 183 Owner: &shared.GistOwner{Login: "octocat"}, 184 }, 185 httpStubs: func(reg *httpmock.Registry) { 186 reg.Register(httpmock.REST("POST", "gists/1234"), 187 httpmock.StatusStringResponse(201, "{}")) 188 }, 189 wantParams: map[string]interface{}{ 190 "description": "catbug", 191 "updated_at": "0001-01-01T00:00:00Z", 192 "public": false, 193 "files": map[string]interface{}{ 194 "cicada.txt": map[string]interface{}{ 195 "content": "bwhiizzzbwhuiiizzzz", 196 "filename": "cicada.txt", 197 "type": "text/plain", 198 }, 199 "unix.md": map[string]interface{}{ 200 "content": "new file content", 201 "filename": "unix.md", 202 "type": "application/markdown", 203 }, 204 }, 205 }, 206 }, 207 { 208 name: "multiple files, cancel", 209 askStubs: func(as *prompt.AskStubber) { 210 as.StubPrompt("Edit which file?").AnswerWith("unix.md") 211 as.StubPrompt("What next?").AnswerWith("Cancel") 212 }, 213 wantErr: "CancelError", 214 gist: &shared.Gist{ 215 ID: "1234", 216 Files: map[string]*shared.GistFile{ 217 "cicada.txt": { 218 Filename: "cicada.txt", 219 Content: "bwhiizzzbwhuiiizzzz", 220 Type: "text/plain", 221 }, 222 "unix.md": { 223 Filename: "unix.md", 224 Content: "meow", 225 Type: "application/markdown", 226 }, 227 }, 228 Owner: &shared.GistOwner{Login: "octocat"}, 229 }, 230 }, 231 { 232 name: "not change", 233 gist: &shared.Gist{ 234 ID: "1234", 235 Files: map[string]*shared.GistFile{ 236 "cicada.txt": { 237 Filename: "cicada.txt", 238 Content: "new file content", 239 Type: "text/plain", 240 }, 241 }, 242 Owner: &shared.GistOwner{Login: "octocat"}, 243 }, 244 }, 245 { 246 name: "another user's gist", 247 gist: &shared.Gist{ 248 ID: "1234", 249 Files: map[string]*shared.GistFile{ 250 "cicada.txt": { 251 Filename: "cicada.txt", 252 Content: "bwhiizzzbwhuiiizzzz", 253 Type: "text/plain", 254 }, 255 }, 256 Owner: &shared.GistOwner{Login: "octocat2"}, 257 }, 258 wantErr: "you do not own this gist", 259 }, 260 { 261 name: "add file to existing gist", 262 gist: &shared.Gist{ 263 ID: "1234", 264 Files: map[string]*shared.GistFile{ 265 "sample.txt": { 266 Filename: "sample.txt", 267 Content: "bwhiizzzbwhuiiizzzz", 268 Type: "text/plain", 269 }, 270 }, 271 Owner: &shared.GistOwner{Login: "octocat"}, 272 }, 273 httpStubs: func(reg *httpmock.Registry) { 274 reg.Register(httpmock.REST("POST", "gists/1234"), 275 httpmock.StatusStringResponse(201, "{}")) 276 }, 277 opts: &EditOptions{ 278 AddFilename: fileToAdd, 279 }, 280 }, 281 { 282 name: "change description", 283 opts: &EditOptions{ 284 Description: "my new description", 285 }, 286 gist: &shared.Gist{ 287 ID: "1234", 288 Description: "my old description", 289 Files: map[string]*shared.GistFile{ 290 "sample.txt": { 291 Filename: "sample.txt", 292 Type: "text/plain", 293 }, 294 }, 295 Owner: &shared.GistOwner{Login: "octocat"}, 296 }, 297 httpStubs: func(reg *httpmock.Registry) { 298 reg.Register(httpmock.REST("POST", "gists/1234"), 299 httpmock.StatusStringResponse(201, "{}")) 300 }, 301 wantParams: map[string]interface{}{ 302 "description": "my new description", 303 "updated_at": "0001-01-01T00:00:00Z", 304 "public": false, 305 "files": map[string]interface{}{ 306 "sample.txt": map[string]interface{}{ 307 "content": "new file content", 308 "filename": "sample.txt", 309 "type": "text/plain", 310 }, 311 }, 312 }, 313 }, 314 { 315 name: "add file to existing gist from source parameter", 316 gist: &shared.Gist{ 317 ID: "1234", 318 Files: map[string]*shared.GistFile{ 319 "sample.txt": { 320 Filename: "sample.txt", 321 Content: "bwhiizzzbwhuiiizzzz", 322 Type: "text/plain", 323 }, 324 }, 325 Owner: &shared.GistOwner{Login: "octocat"}, 326 }, 327 httpStubs: func(reg *httpmock.Registry) { 328 reg.Register(httpmock.REST("POST", "gists/1234"), 329 httpmock.StatusStringResponse(201, "{}")) 330 }, 331 opts: &EditOptions{ 332 AddFilename: "from_source.txt", 333 SourceFile: fileToAdd, 334 }, 335 wantParams: map[string]interface{}{ 336 "description": "", 337 "updated_at": "0001-01-01T00:00:00Z", 338 "public": false, 339 "files": map[string]interface{}{ 340 "from_source.txt": map[string]interface{}{ 341 "content": "hello", 342 "filename": "from_source.txt", 343 }, 344 }, 345 }, 346 }, 347 { 348 name: "add file to existing gist from stdin", 349 gist: &shared.Gist{ 350 ID: "1234", 351 Files: map[string]*shared.GistFile{ 352 "sample.txt": { 353 Filename: "sample.txt", 354 Content: "bwhiizzzbwhuiiizzzz", 355 Type: "text/plain", 356 }, 357 }, 358 Owner: &shared.GistOwner{Login: "octocat"}, 359 }, 360 httpStubs: func(reg *httpmock.Registry) { 361 reg.Register(httpmock.REST("POST", "gists/1234"), 362 httpmock.StatusStringResponse(201, "{}")) 363 }, 364 opts: &EditOptions{ 365 AddFilename: "from_source.txt", 366 SourceFile: "-", 367 }, 368 stdin: "data from stdin", 369 wantParams: map[string]interface{}{ 370 "description": "", 371 "updated_at": "0001-01-01T00:00:00Z", 372 "public": false, 373 "files": map[string]interface{}{ 374 "from_source.txt": map[string]interface{}{ 375 "content": "data from stdin", 376 "filename": "from_source.txt", 377 }, 378 }, 379 }, 380 }, 381 { 382 name: "edit gist using file from source parameter", 383 gist: &shared.Gist{ 384 ID: "1234", 385 Files: map[string]*shared.GistFile{ 386 "sample.txt": { 387 Filename: "sample.txt", 388 Content: "bwhiizzzbwhuiiizzzz", 389 Type: "text/plain", 390 }, 391 }, 392 Owner: &shared.GistOwner{Login: "octocat"}, 393 }, 394 httpStubs: func(reg *httpmock.Registry) { 395 reg.Register(httpmock.REST("POST", "gists/1234"), 396 httpmock.StatusStringResponse(201, "{}")) 397 }, 398 opts: &EditOptions{ 399 SourceFile: fileToAdd, 400 }, 401 wantParams: map[string]interface{}{ 402 "description": "", 403 "updated_at": "0001-01-01T00:00:00Z", 404 "public": false, 405 "files": map[string]interface{}{ 406 "sample.txt": map[string]interface{}{ 407 "content": "hello", 408 "filename": "sample.txt", 409 "type": "text/plain", 410 }, 411 }, 412 }, 413 }, 414 { 415 name: "edit gist using stdin", 416 gist: &shared.Gist{ 417 ID: "1234", 418 Files: map[string]*shared.GistFile{ 419 "sample.txt": { 420 Filename: "sample.txt", 421 Content: "bwhiizzzbwhuiiizzzz", 422 Type: "text/plain", 423 }, 424 }, 425 Owner: &shared.GistOwner{Login: "octocat"}, 426 }, 427 httpStubs: func(reg *httpmock.Registry) { 428 reg.Register(httpmock.REST("POST", "gists/1234"), 429 httpmock.StatusStringResponse(201, "{}")) 430 }, 431 opts: &EditOptions{ 432 SourceFile: "-", 433 }, 434 stdin: "data from stdin", 435 wantParams: map[string]interface{}{ 436 "description": "", 437 "updated_at": "0001-01-01T00:00:00Z", 438 "public": false, 439 "files": map[string]interface{}{ 440 "sample.txt": map[string]interface{}{ 441 "content": "data from stdin", 442 "filename": "sample.txt", 443 "type": "text/plain", 444 }, 445 }, 446 }, 447 }, 448 } 449 450 for _, tt := range tests { 451 reg := &httpmock.Registry{} 452 if tt.gist == nil { 453 reg.Register(httpmock.REST("GET", "gists/1234"), 454 httpmock.StatusStringResponse(404, "Not Found")) 455 } else { 456 reg.Register(httpmock.REST("GET", "gists/1234"), 457 httpmock.JSONResponse(tt.gist)) 458 reg.Register(httpmock.GraphQL(`query UserCurrent\b`), 459 httpmock.StringResponse(`{"data":{"viewer":{"login":"octocat"}}}`)) 460 } 461 462 if tt.httpStubs != nil { 463 tt.httpStubs(reg) 464 } 465 466 if tt.opts == nil { 467 tt.opts = &EditOptions{} 468 } 469 470 tt.opts.Edit = func(_, _, _ string, _ *iostreams.IOStreams) (string, error) { 471 return "new file content", nil 472 } 473 474 tt.opts.HttpClient = func() (*http.Client, error) { 475 return &http.Client{Transport: reg}, nil 476 } 477 ios, stdin, stdout, stderr := iostreams.Test() 478 stdin.WriteString(tt.stdin) 479 ios.SetStdoutTTY(!tt.nontty) 480 ios.SetStdinTTY(!tt.nontty) 481 tt.opts.IO = ios 482 tt.opts.Selector = "1234" 483 484 tt.opts.Config = func() (config.Config, error) { 485 return config.NewBlankConfig(), nil 486 } 487 488 t.Run(tt.name, func(t *testing.T) { 489 //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock 490 as := prompt.NewAskStubber(t) 491 if tt.askStubs != nil { 492 tt.askStubs(as) 493 } 494 495 err := editRun(tt.opts) 496 reg.Verify(t) 497 if tt.wantErr != "" { 498 assert.EqualError(t, err, tt.wantErr) 499 return 500 } 501 assert.NoError(t, err) 502 503 if tt.wantParams != nil { 504 bodyBytes, _ := io.ReadAll(reg.Requests[2].Body) 505 reqBody := make(map[string]interface{}) 506 err = json.Unmarshal(bodyBytes, &reqBody) 507 if err != nil { 508 t.Fatalf("error decoding JSON: %v", err) 509 } 510 assert.Equal(t, tt.wantParams, reqBody) 511 } 512 513 assert.Equal(t, "", stdout.String()) 514 assert.Equal(t, "", stderr.String()) 515 }) 516 } 517 }