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  }