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  }