Skip to content

Commit 026b07d

Browse files
authored
Merge pull request cli#3578 from g14a/fix/empty-gist-contents
Add validation to gists if contents are empty
2 parents 2f94ada + 70a9621 commit 026b07d

File tree

4 files changed

+103
-34
lines changed

4 files changed

+103
-34
lines changed

pkg/cmd/gist/create/create.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,13 @@ func createRun(opts *CreateOptions) error {
151151
var httpError api.HTTPError
152152
if errors.As(err, &httpError) {
153153
if httpError.OAuthScopes != "" && !strings.Contains(httpError.OAuthScopes, "gist") {
154-
return fmt.Errorf("This command requires the 'gist' OAuth scope.\nPlease re-authenticate by doing `gh config set -h github.com oauth_token ''` and running the command again.")
154+
return fmt.Errorf("This command requires the 'gist' OAuth scope.\nPlease re-authenticate with: gh auth refresh -h %s -s gist", host)
155+
}
156+
if httpError.StatusCode == http.StatusUnprocessableEntity {
157+
if detectEmptyFiles(files) {
158+
fmt.Fprintf(errOut, "%s Failed to create gist: %s\n", cs.FailureIcon(), "a gist file cannot be blank")
159+
return cmdutil.SilentError
160+
}
155161
}
156162
}
157163
return fmt.Errorf("%s Failed to create gist: %w", cs.Red("X"), err)
@@ -266,3 +272,12 @@ func createGist(client *http.Client, hostname, description string, public bool,
266272

267273
return &result, nil
268274
}
275+
276+
func detectEmptyFiles(files map[string]*shared.GistFile) bool {
277+
for _, file := range files {
278+
if strings.TrimSpace(file.Content) == "" {
279+
return true
280+
}
281+
}
282+
return false
283+
}

pkg/cmd/gist/create/create_test.go

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
"encoding/json"
66
"io/ioutil"
77
"net/http"
8+
"path"
89
"strings"
910
"testing"
1011

12+
"github.com/MakeNowJust/heredoc"
1113
"github.com/cli/cli/internal/config"
1214
"github.com/cli/cli/internal/run"
1315
"github.com/cli/cli/pkg/cmd/gist/shared"
@@ -18,10 +20,6 @@ import (
1820
"github.com/stretchr/testify/assert"
1921
)
2022

21-
const (
22-
fixtureFile = "../fixture.txt"
23-
)
24-
2523
func Test_processFiles(t *testing.T) {
2624
fakeStdin := strings.NewReader("hey cool how is it going")
2725
files, err := processFiles(ioutil.NopCloser(fakeStdin), "", []string{"-"})
@@ -164,15 +162,22 @@ func TestNewCmdCreate(t *testing.T) {
164162
}
165163

166164
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+
167171
tests := []struct {
168-
name string
169-
opts *CreateOptions
170-
stdin string
171-
wantOut string
172-
wantStderr string
173-
wantParams map[string]interface{}
174-
wantErr bool
175-
wantBrowse string
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
176181
}{
177182
{
178183
name: "public",
@@ -193,6 +198,7 @@ func Test_createRun(t *testing.T) {
193198
},
194199
},
195200
},
201+
responseStatus: http.StatusOK,
196202
},
197203
{
198204
name: "with description",
@@ -213,6 +219,7 @@ func Test_createRun(t *testing.T) {
213219
},
214220
},
215221
},
222+
responseStatus: http.StatusOK,
216223
},
217224
{
218225
name: "multiple files",
@@ -236,6 +243,28 @@ func Test_createRun(t *testing.T) {
236243
},
237244
},
238245
},
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,
239268
},
240269
{
241270
name: "stdin arg",
@@ -256,6 +285,7 @@ func Test_createRun(t *testing.T) {
256285
},
257286
},
258287
},
288+
responseStatus: http.StatusOK,
259289
},
260290
{
261291
name: "web arg",
@@ -277,14 +307,22 @@ func Test_createRun(t *testing.T) {
277307
},
278308
},
279309
},
310+
responseStatus: http.StatusOK,
280311
},
281312
}
282313
for _, tt := range tests {
283314
reg := &httpmock.Registry{}
284-
reg.Register(httpmock.REST("POST", "gists"),
285-
httpmock.JSONResponse(struct {
286-
Html_url string
287-
}{"https://gist.github.com/aa5a315d61ae9438b18d"}))
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+
}
288326

289327
mockClient := func() (*http.Client, error) {
290328
return &http.Client{Transport: reg}, nil
@@ -325,40 +363,57 @@ func Test_createRun(t *testing.T) {
325363
}
326364
}
327365

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+
328392
func Test_CreateRun_reauth(t *testing.T) {
329393
reg := &httpmock.Registry{}
330394
reg.Register(httpmock.REST("POST", "gists"), func(req *http.Request) (*http.Response, error) {
331395
return &http.Response{
332396
StatusCode: 404,
333397
Request: req,
334398
Header: map[string][]string{
335-
"X-Oauth-Scopes": {"coolScope"},
399+
"X-Oauth-Scopes": {"repo, read:org"},
336400
},
337401
Body: ioutil.NopCloser(bytes.NewBufferString("oh no")),
338402
}, nil
339403
})
340404

341-
mockClient := func() (*http.Client, error) {
342-
return &http.Client{Transport: reg}, nil
343-
}
344-
345405
io, _, _, _ := iostreams.Test()
346406

347407
opts := &CreateOptions{
348-
IO: io,
349-
HttpClient: mockClient,
408+
IO: io,
409+
HttpClient: func() (*http.Client, error) {
410+
return &http.Client{Transport: reg}, nil
411+
},
350412
Config: func() (config.Config, error) {
351413
return config.NewBlankConfig(), nil
352414
},
353-
Filenames: []string{fixtureFile},
354415
}
355416

356417
err := createRun(opts)
357-
if err == nil {
358-
t.Fatalf("expected oauth error")
359-
}
360-
361-
if !strings.Contains(err.Error(), "Please re-authenticate") {
362-
t.Errorf("got unexpected error: %s", err)
363-
}
418+
assert.EqualError(t, err, "This command requires the 'gist' OAuth scope.\nPlease re-authenticate with: gh auth refresh -h github.com -s gist")
364419
}

pkg/cmd/gist/fixture.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

pkg/cmd/gist/shared/shared.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type GistFile struct {
2020
Filename string `json:"filename,omitempty"`
2121
Type string `json:"type,omitempty"`
2222
Language string `json:"language,omitempty"`
23-
Content string `json:"content,omitempty"`
23+
Content string `json:"content"`
2424
}
2525

2626
type GistOwner struct {

0 commit comments

Comments
 (0)