github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/gist/create/create_test.go (about) 1 package create 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io/ioutil" 7 "net/http" 8 "path" 9 "strings" 10 "testing" 11 12 "github.com/MakeNowJust/heredoc" 13 "github.com/cli/cli/internal/config" 14 "github.com/cli/cli/internal/run" 15 "github.com/cli/cli/pkg/cmd/gist/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 ) 22 23 func Test_processFiles(t *testing.T) { 24 fakeStdin := strings.NewReader("hey cool how is it going") 25 files, err := processFiles(ioutil.NopCloser(fakeStdin), "", []string{"-"}) 26 if err != nil { 27 t.Fatalf("unexpected error processing files: %s", err) 28 } 29 30 assert.Equal(t, 1, len(files)) 31 assert.Equal(t, "hey cool how is it going", files["gistfile0.txt"].Content) 32 } 33 34 func Test_guessGistName_stdin(t *testing.T) { 35 files := map[string]*shared.GistFile{ 36 "gistfile0.txt": {Content: "sample content"}, 37 } 38 39 gistName := guessGistName(files) 40 assert.Equal(t, "", gistName) 41 } 42 43 func Test_guessGistName_userFiles(t *testing.T) { 44 files := map[string]*shared.GistFile{ 45 "fig.txt": {Content: "I am a fig"}, 46 "apple.txt": {Content: "I am an apple"}, 47 "gistfile0.txt": {Content: "sample content"}, 48 } 49 50 gistName := guessGistName(files) 51 assert.Equal(t, "apple.txt", gistName) 52 } 53 54 func TestNewCmdCreate(t *testing.T) { 55 tests := []struct { 56 name string 57 cli string 58 factory func(*cmdutil.Factory) *cmdutil.Factory 59 wants CreateOptions 60 wantsErr bool 61 }{ 62 { 63 name: "no arguments", 64 cli: "", 65 wants: CreateOptions{ 66 Description: "", 67 Public: false, 68 Filenames: []string{""}, 69 }, 70 wantsErr: false, 71 }, 72 { 73 name: "no arguments with TTY stdin", 74 factory: func(f *cmdutil.Factory) *cmdutil.Factory { 75 f.IOStreams.SetStdinTTY(true) 76 return f 77 }, 78 cli: "", 79 wants: CreateOptions{ 80 Description: "", 81 Public: false, 82 Filenames: []string{""}, 83 }, 84 wantsErr: true, 85 }, 86 { 87 name: "stdin argument", 88 cli: "-", 89 wants: CreateOptions{ 90 Description: "", 91 Public: false, 92 Filenames: []string{"-"}, 93 }, 94 wantsErr: false, 95 }, 96 { 97 name: "with description", 98 cli: `-d "my new gist" -`, 99 wants: CreateOptions{ 100 Description: "my new gist", 101 Public: false, 102 Filenames: []string{"-"}, 103 }, 104 wantsErr: false, 105 }, 106 { 107 name: "public", 108 cli: `--public -`, 109 wants: CreateOptions{ 110 Description: "", 111 Public: true, 112 Filenames: []string{"-"}, 113 }, 114 wantsErr: false, 115 }, 116 { 117 name: "list of files", 118 cli: "file1.txt file2.txt", 119 wants: CreateOptions{ 120 Description: "", 121 Public: false, 122 Filenames: []string{"file1.txt", "file2.txt"}, 123 }, 124 wantsErr: false, 125 }, 126 } 127 for _, tt := range tests { 128 t.Run(tt.name, func(t *testing.T) { 129 io, _, _, _ := iostreams.Test() 130 f := &cmdutil.Factory{ 131 IOStreams: io, 132 } 133 134 if tt.factory != nil { 135 f = tt.factory(f) 136 } 137 138 argv, err := shlex.Split(tt.cli) 139 assert.NoError(t, err) 140 141 var gotOpts *CreateOptions 142 cmd := NewCmdCreate(f, func(opts *CreateOptions) error { 143 gotOpts = opts 144 return nil 145 }) 146 cmd.SetArgs(argv) 147 cmd.SetIn(&bytes.Buffer{}) 148 cmd.SetOut(&bytes.Buffer{}) 149 cmd.SetErr(&bytes.Buffer{}) 150 151 _, err = cmd.ExecuteC() 152 if tt.wantsErr { 153 assert.Error(t, err) 154 return 155 } 156 assert.NoError(t, err) 157 158 assert.Equal(t, tt.wants.Description, gotOpts.Description) 159 assert.Equal(t, tt.wants.Public, gotOpts.Public) 160 }) 161 } 162 } 163 164 func Test_createRun(t *testing.T) { 165 tempDir := t.TempDir() 166 fixtureFile := path.Join(tempDir, "fixture.txt") 167 assert.NoError(t, ioutil.WriteFile(fixtureFile, []byte("{}"), 0644)) 168 emptyFile := path.Join(tempDir, "empty.txt") 169 assert.NoError(t, ioutil.WriteFile(emptyFile, []byte(" \t\n"), 0644)) 170 171 tests := []struct { 172 name string 173 opts *CreateOptions 174 stdin string 175 wantOut string 176 wantStderr string 177 wantParams map[string]interface{} 178 wantErr bool 179 wantBrowse string 180 responseStatus int 181 }{ 182 { 183 name: "public", 184 opts: &CreateOptions{ 185 Public: true, 186 Filenames: []string{fixtureFile}, 187 }, 188 wantOut: "https://gist.github.com/aa5a315d61ae9438b18d\n", 189 wantStderr: "- Creating gist fixture.txt\n✓ Created gist fixture.txt\n", 190 wantErr: false, 191 wantParams: map[string]interface{}{ 192 "description": "", 193 "updated_at": "0001-01-01T00:00:00Z", 194 "public": true, 195 "files": map[string]interface{}{ 196 "fixture.txt": map[string]interface{}{ 197 "content": "{}", 198 }, 199 }, 200 }, 201 responseStatus: http.StatusOK, 202 }, 203 { 204 name: "with description", 205 opts: &CreateOptions{ 206 Description: "an incredibly interesting gist", 207 Filenames: []string{fixtureFile}, 208 }, 209 wantOut: "https://gist.github.com/aa5a315d61ae9438b18d\n", 210 wantStderr: "- Creating gist fixture.txt\n✓ Created gist fixture.txt\n", 211 wantErr: false, 212 wantParams: map[string]interface{}{ 213 "description": "an incredibly interesting gist", 214 "updated_at": "0001-01-01T00:00:00Z", 215 "public": false, 216 "files": map[string]interface{}{ 217 "fixture.txt": map[string]interface{}{ 218 "content": "{}", 219 }, 220 }, 221 }, 222 responseStatus: http.StatusOK, 223 }, 224 { 225 name: "multiple files", 226 opts: &CreateOptions{ 227 Filenames: []string{fixtureFile, "-"}, 228 }, 229 stdin: "cool stdin content", 230 wantOut: "https://gist.github.com/aa5a315d61ae9438b18d\n", 231 wantStderr: "- Creating gist with multiple files\n✓ Created gist fixture.txt\n", 232 wantErr: false, 233 wantParams: map[string]interface{}{ 234 "description": "", 235 "updated_at": "0001-01-01T00:00:00Z", 236 "public": false, 237 "files": map[string]interface{}{ 238 "fixture.txt": map[string]interface{}{ 239 "content": "{}", 240 }, 241 "gistfile1.txt": map[string]interface{}{ 242 "content": "cool stdin content", 243 }, 244 }, 245 }, 246 responseStatus: http.StatusOK, 247 }, 248 { 249 name: "file with empty content", 250 opts: &CreateOptions{ 251 Filenames: []string{emptyFile}, 252 }, 253 wantOut: "", 254 wantStderr: heredoc.Doc(` 255 - Creating gist empty.txt 256 X Failed to create gist: a gist file cannot be blank 257 `), 258 wantErr: true, 259 wantParams: map[string]interface{}{ 260 "description": "", 261 "updated_at": "0001-01-01T00:00:00Z", 262 "public": false, 263 "files": map[string]interface{}{ 264 "empty.txt": map[string]interface{}{"content": " \t\n"}, 265 }, 266 }, 267 responseStatus: http.StatusUnprocessableEntity, 268 }, 269 { 270 name: "stdin arg", 271 opts: &CreateOptions{ 272 Filenames: []string{"-"}, 273 }, 274 stdin: "cool stdin content", 275 wantOut: "https://gist.github.com/aa5a315d61ae9438b18d\n", 276 wantStderr: "- Creating gist...\n✓ Created gist\n", 277 wantErr: false, 278 wantParams: map[string]interface{}{ 279 "description": "", 280 "updated_at": "0001-01-01T00:00:00Z", 281 "public": false, 282 "files": map[string]interface{}{ 283 "gistfile0.txt": map[string]interface{}{ 284 "content": "cool stdin content", 285 }, 286 }, 287 }, 288 responseStatus: http.StatusOK, 289 }, 290 { 291 name: "web arg", 292 opts: &CreateOptions{ 293 WebMode: true, 294 Filenames: []string{fixtureFile}, 295 }, 296 wantOut: "Opening gist.github.com/aa5a315d61ae9438b18d in your browser.\n", 297 wantStderr: "- Creating gist fixture.txt\n✓ Created gist fixture.txt\n", 298 wantErr: false, 299 wantBrowse: "https://gist.github.com/aa5a315d61ae9438b18d", 300 wantParams: map[string]interface{}{ 301 "description": "", 302 "updated_at": "0001-01-01T00:00:00Z", 303 "public": false, 304 "files": map[string]interface{}{ 305 "fixture.txt": map[string]interface{}{ 306 "content": "{}", 307 }, 308 }, 309 }, 310 responseStatus: http.StatusOK, 311 }, 312 } 313 for _, tt := range tests { 314 reg := &httpmock.Registry{} 315 if tt.responseStatus == http.StatusOK { 316 reg.Register( 317 httpmock.REST("POST", "gists"), 318 httpmock.StringResponse(`{ 319 "html_url": "https://gist.github.com/aa5a315d61ae9438b18d" 320 }`)) 321 } else { 322 reg.Register( 323 httpmock.REST("POST", "gists"), 324 httpmock.StatusStringResponse(tt.responseStatus, "{}")) 325 } 326 327 mockClient := func() (*http.Client, error) { 328 return &http.Client{Transport: reg}, nil 329 } 330 tt.opts.HttpClient = mockClient 331 332 tt.opts.Config = func() (config.Config, error) { 333 return config.NewBlankConfig(), nil 334 } 335 336 io, stdin, stdout, stderr := iostreams.Test() 337 tt.opts.IO = io 338 339 browser := &cmdutil.TestBrowser{} 340 tt.opts.Browser = browser 341 342 _, teardown := run.Stub() 343 defer teardown(t) 344 345 t.Run(tt.name, func(t *testing.T) { 346 stdin.WriteString(tt.stdin) 347 348 if err := createRun(tt.opts); (err != nil) != tt.wantErr { 349 t.Errorf("createRun() error = %v, wantErr %v", err, tt.wantErr) 350 } 351 bodyBytes, _ := ioutil.ReadAll(reg.Requests[0].Body) 352 reqBody := make(map[string]interface{}) 353 err := json.Unmarshal(bodyBytes, &reqBody) 354 if err != nil { 355 t.Fatalf("error decoding JSON: %v", err) 356 } 357 assert.Equal(t, tt.wantOut, stdout.String()) 358 assert.Equal(t, tt.wantStderr, stderr.String()) 359 assert.Equal(t, tt.wantParams, reqBody) 360 reg.Verify(t) 361 browser.Verify(t, tt.wantBrowse) 362 }) 363 } 364 } 365 366 func Test_detectEmptyFiles(t *testing.T) { 367 tests := []struct { 368 content string 369 isEmptyFile bool 370 }{ 371 { 372 content: "{}", 373 isEmptyFile: false, 374 }, 375 { 376 content: "\n\t", 377 isEmptyFile: true, 378 }, 379 } 380 381 for _, tt := range tests { 382 files := map[string]*shared.GistFile{} 383 files["file"] = &shared.GistFile{ 384 Content: tt.content, 385 } 386 387 isEmptyFile := detectEmptyFiles(files) 388 assert.Equal(t, tt.isEmptyFile, isEmptyFile) 389 } 390 } 391 392 func Test_CreateRun_reauth(t *testing.T) { 393 reg := &httpmock.Registry{} 394 reg.Register(httpmock.REST("POST", "gists"), func(req *http.Request) (*http.Response, error) { 395 return &http.Response{ 396 StatusCode: 404, 397 Request: req, 398 Header: map[string][]string{ 399 "X-Oauth-Scopes": {"repo, read:org"}, 400 }, 401 Body: ioutil.NopCloser(bytes.NewBufferString("oh no")), 402 }, nil 403 }) 404 405 io, _, _, _ := iostreams.Test() 406 407 opts := &CreateOptions{ 408 IO: io, 409 HttpClient: func() (*http.Client, error) { 410 return &http.Client{Transport: reg}, nil 411 }, 412 Config: func() (config.Config, error) { 413 return config.NewBlankConfig(), nil 414 }, 415 } 416 417 err := createRun(opts) 418 assert.EqualError(t, err, "This command requires the 'gist' OAuth scope.\nPlease re-authenticate with: gh auth refresh -h github.com -s gist") 419 }