github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/gist/create/create_test.go (about)

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