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

     1  package view
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/ungtb10d/cli/v2/internal/config"
    11  	"github.com/ungtb10d/cli/v2/pkg/cmd/gist/shared"
    12  	"github.com/ungtb10d/cli/v2/pkg/cmdutil"
    13  	"github.com/ungtb10d/cli/v2/pkg/httpmock"
    14  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    15  	"github.com/ungtb10d/cli/v2/pkg/prompt"
    16  	"github.com/google/shlex"
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func TestNewCmdView(t *testing.T) {
    21  	tests := []struct {
    22  		name  string
    23  		cli   string
    24  		wants ViewOptions
    25  		tty   bool
    26  	}{
    27  		{
    28  			name: "tty no arguments",
    29  			tty:  true,
    30  			cli:  "123",
    31  			wants: ViewOptions{
    32  				Raw:       false,
    33  				Selector:  "123",
    34  				ListFiles: false,
    35  			},
    36  		},
    37  		{
    38  			name: "nontty no arguments",
    39  			cli:  "123",
    40  			wants: ViewOptions{
    41  				Raw:       true,
    42  				Selector:  "123",
    43  				ListFiles: false,
    44  			},
    45  		},
    46  		{
    47  			name: "filename passed",
    48  			cli:  "-fcool.txt 123",
    49  			tty:  true,
    50  			wants: ViewOptions{
    51  				Raw:       false,
    52  				Selector:  "123",
    53  				Filename:  "cool.txt",
    54  				ListFiles: false,
    55  			},
    56  		},
    57  		{
    58  			name: "files passed",
    59  			cli:  "--files 123",
    60  			tty:  true,
    61  			wants: ViewOptions{
    62  				Raw:       false,
    63  				Selector:  "123",
    64  				ListFiles: true,
    65  			},
    66  		},
    67  		{
    68  			name: "tty no ID supplied",
    69  			cli:  "",
    70  			tty:  true,
    71  			wants: ViewOptions{
    72  				Raw:       false,
    73  				Selector:  "",
    74  				ListFiles: true,
    75  			},
    76  		},
    77  	}
    78  
    79  	for _, tt := range tests {
    80  		t.Run(tt.name, func(t *testing.T) {
    81  			ios, _, _, _ := iostreams.Test()
    82  			ios.SetStdoutTTY(tt.tty)
    83  
    84  			f := &cmdutil.Factory{
    85  				IOStreams: ios,
    86  			}
    87  
    88  			argv, err := shlex.Split(tt.cli)
    89  			assert.NoError(t, err)
    90  
    91  			var gotOpts *ViewOptions
    92  			cmd := NewCmdView(f, func(opts *ViewOptions) error {
    93  				gotOpts = opts
    94  				return nil
    95  			})
    96  			cmd.SetArgs(argv)
    97  			cmd.SetIn(&bytes.Buffer{})
    98  			cmd.SetOut(&bytes.Buffer{})
    99  			cmd.SetErr(&bytes.Buffer{})
   100  
   101  			_, err = cmd.ExecuteC()
   102  			assert.NoError(t, err)
   103  
   104  			assert.Equal(t, tt.wants.Raw, gotOpts.Raw)
   105  			assert.Equal(t, tt.wants.Selector, gotOpts.Selector)
   106  			assert.Equal(t, tt.wants.Filename, gotOpts.Filename)
   107  		})
   108  	}
   109  }
   110  
   111  func Test_viewRun(t *testing.T) {
   112  	tests := []struct {
   113  		name         string
   114  		opts         *ViewOptions
   115  		wantOut      string
   116  		gist         *shared.Gist
   117  		wantErr      bool
   118  		mockGistList bool
   119  	}{
   120  		{
   121  			name: "no such gist",
   122  			opts: &ViewOptions{
   123  				Selector:  "1234",
   124  				ListFiles: false,
   125  			},
   126  			wantErr: true,
   127  		},
   128  		{
   129  			name: "one file",
   130  			opts: &ViewOptions{
   131  				Selector:  "1234",
   132  				ListFiles: false,
   133  			},
   134  			gist: &shared.Gist{
   135  				Files: map[string]*shared.GistFile{
   136  					"cicada.txt": {
   137  						Content: "bwhiizzzbwhuiiizzzz",
   138  						Type:    "text/plain",
   139  					},
   140  				},
   141  			},
   142  			wantOut: "bwhiizzzbwhuiiizzzz\n",
   143  		},
   144  		{
   145  			name: "one file, no ID supplied",
   146  			opts: &ViewOptions{
   147  				Selector:  "",
   148  				ListFiles: false,
   149  			},
   150  			mockGistList: true,
   151  			gist: &shared.Gist{
   152  				Files: map[string]*shared.GistFile{
   153  					"cicada.txt": {
   154  						Content: "test interactive mode",
   155  						Type:    "text/plain",
   156  					},
   157  				},
   158  			},
   159  			wantOut: "test interactive mode\n",
   160  		},
   161  		{
   162  			name: "filename selected",
   163  			opts: &ViewOptions{
   164  				Selector:  "1234",
   165  				Filename:  "cicada.txt",
   166  				ListFiles: false,
   167  			},
   168  			gist: &shared.Gist{
   169  				Files: map[string]*shared.GistFile{
   170  					"cicada.txt": {
   171  						Content: "bwhiizzzbwhuiiizzzz",
   172  						Type:    "text/plain",
   173  					},
   174  					"foo.md": {
   175  						Content: "# foo",
   176  						Type:    "application/markdown",
   177  					},
   178  				},
   179  			},
   180  			wantOut: "bwhiizzzbwhuiiizzzz\n",
   181  		},
   182  		{
   183  			name: "filename selected, raw",
   184  			opts: &ViewOptions{
   185  				Selector:  "1234",
   186  				Filename:  "cicada.txt",
   187  				Raw:       true,
   188  				ListFiles: false,
   189  			},
   190  			gist: &shared.Gist{
   191  				Files: map[string]*shared.GistFile{
   192  					"cicada.txt": {
   193  						Content: "bwhiizzzbwhuiiizzzz",
   194  						Type:    "text/plain",
   195  					},
   196  					"foo.md": {
   197  						Content: "# foo",
   198  						Type:    "application/markdown",
   199  					},
   200  				},
   201  			},
   202  			wantOut: "bwhiizzzbwhuiiizzzz\n",
   203  		},
   204  		{
   205  			name: "multiple files, no description",
   206  			opts: &ViewOptions{
   207  				Selector:  "1234",
   208  				ListFiles: false,
   209  			},
   210  			gist: &shared.Gist{
   211  				Files: map[string]*shared.GistFile{
   212  					"cicada.txt": {
   213  						Content: "bwhiizzzbwhuiiizzzz",
   214  						Type:    "text/plain",
   215  					},
   216  					"foo.md": {
   217  						Content: "# foo",
   218  						Type:    "application/markdown",
   219  					},
   220  				},
   221  			},
   222  			wantOut: "cicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n\n  # foo                                                                       \n\n",
   223  		},
   224  		{
   225  			name: "multiple files, trailing newlines",
   226  			opts: &ViewOptions{
   227  				Selector:  "1234",
   228  				ListFiles: false,
   229  			},
   230  			gist: &shared.Gist{
   231  				Files: map[string]*shared.GistFile{
   232  					"cicada.txt": {
   233  						Content: "bwhiizzzbwhuiiizzzz\n",
   234  						Type:    "text/plain",
   235  					},
   236  					"foo.txt": {
   237  						Content: "bar\n",
   238  						Type:    "text/plain",
   239  					},
   240  				},
   241  			},
   242  			wantOut: "cicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.txt\n\nbar\n",
   243  		},
   244  		{
   245  			name: "multiple files, description",
   246  			opts: &ViewOptions{
   247  				Selector:  "1234",
   248  				ListFiles: false,
   249  			},
   250  			gist: &shared.Gist{
   251  				Description: "some files",
   252  				Files: map[string]*shared.GistFile{
   253  					"cicada.txt": {
   254  						Content: "bwhiizzzbwhuiiizzzz",
   255  						Type:    "text/plain",
   256  					},
   257  					"foo.md": {
   258  						Content: "- foo",
   259  						Type:    "application/markdown",
   260  					},
   261  				},
   262  			},
   263  			wantOut: "some files\n\ncicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n\n                                                                              \n  • foo                                                                       \n\n",
   264  		},
   265  		{
   266  			name: "multiple files, raw",
   267  			opts: &ViewOptions{
   268  				Selector:  "1234",
   269  				Raw:       true,
   270  				ListFiles: false,
   271  			},
   272  			gist: &shared.Gist{
   273  				Description: "some files",
   274  				Files: map[string]*shared.GistFile{
   275  					"cicada.txt": {
   276  						Content: "bwhiizzzbwhuiiizzzz",
   277  						Type:    "text/plain",
   278  					},
   279  					"foo.md": {
   280  						Content: "- foo",
   281  						Type:    "application/markdown",
   282  					},
   283  				},
   284  			},
   285  			wantOut: "some files\n\ncicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n- foo\n",
   286  		},
   287  		{
   288  			name: "one file, list files",
   289  			opts: &ViewOptions{
   290  				Selector:  "1234",
   291  				Raw:       false,
   292  				ListFiles: true,
   293  			},
   294  			gist: &shared.Gist{
   295  				Description: "some files",
   296  				Files: map[string]*shared.GistFile{
   297  					"cicada.txt": {
   298  						Content: "bwhiizzzbwhuiiizzzz",
   299  						Type:    "text/plain",
   300  					},
   301  				},
   302  			},
   303  			wantOut: "cicada.txt\n",
   304  		},
   305  		{
   306  			name: "multiple file, list files",
   307  			opts: &ViewOptions{
   308  				Selector:  "1234",
   309  				Raw:       false,
   310  				ListFiles: true,
   311  			},
   312  			gist: &shared.Gist{
   313  				Description: "some files",
   314  				Files: map[string]*shared.GistFile{
   315  					"cicada.txt": {
   316  						Content: "bwhiizzzbwhuiiizzzz",
   317  						Type:    "text/plain",
   318  					},
   319  					"foo.md": {
   320  						Content: "- foo",
   321  						Type:    "application/markdown",
   322  					},
   323  				},
   324  			},
   325  			wantOut: "cicada.txt\nfoo.md\n",
   326  		},
   327  	}
   328  
   329  	for _, tt := range tests {
   330  		reg := &httpmock.Registry{}
   331  		if tt.gist == nil {
   332  			reg.Register(httpmock.REST("GET", "gists/1234"),
   333  				httpmock.StatusStringResponse(404, "Not Found"))
   334  		} else {
   335  			reg.Register(httpmock.REST("GET", "gists/1234"),
   336  				httpmock.JSONResponse(tt.gist))
   337  		}
   338  
   339  		if tt.mockGistList {
   340  			sixHours, _ := time.ParseDuration("6h")
   341  			sixHoursAgo := time.Now().Add(-sixHours)
   342  			reg.Register(
   343  				httpmock.GraphQL(`query GistList\b`),
   344  				httpmock.StringResponse(fmt.Sprintf(
   345  					`{ "data": { "viewer": { "gists": { "nodes": [
   346  							{
   347  								"name": "1234",
   348  								"files": [{ "name": "cool.txt" }],
   349  								"description": "",
   350  								"updatedAt": "%s",
   351  								"isPublic": true
   352  							}
   353  						] } } } }`,
   354  					sixHoursAgo.Format(time.RFC3339),
   355  				)),
   356  			)
   357  
   358  			//nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock
   359  			as := prompt.NewAskStubber(t)
   360  			as.StubPrompt("Select a gist").AnswerDefault()
   361  		}
   362  
   363  		if tt.opts == nil {
   364  			tt.opts = &ViewOptions{}
   365  		}
   366  
   367  		tt.opts.HttpClient = func() (*http.Client, error) {
   368  			return &http.Client{Transport: reg}, nil
   369  		}
   370  
   371  		tt.opts.Config = func() (config.Config, error) {
   372  			return config.NewBlankConfig(), nil
   373  		}
   374  
   375  		ios, _, stdout, _ := iostreams.Test()
   376  		ios.SetStdoutTTY(true)
   377  		tt.opts.IO = ios
   378  
   379  		t.Run(tt.name, func(t *testing.T) {
   380  			err := viewRun(tt.opts)
   381  			if tt.wantErr {
   382  				assert.Error(t, err)
   383  				return
   384  			}
   385  			assert.NoError(t, err)
   386  
   387  			assert.Equal(t, tt.wantOut, stdout.String())
   388  			reg.Verify(t)
   389  		})
   390  	}
   391  }
   392  
   393  func Test_promptGists(t *testing.T) {
   394  	tests := []struct {
   395  		name     string
   396  		askStubs func(as *prompt.AskStubber)
   397  		response string
   398  		wantOut  string
   399  		gist     *shared.Gist
   400  		wantErr  bool
   401  	}{
   402  		{
   403  			name: "multiple files, select first gist",
   404  			askStubs: func(as *prompt.AskStubber) {
   405  				as.StubPrompt("Select a gist").AnswerWith("cool.txt  about 6 hours ago")
   406  			},
   407  			response: `{ "data": { "viewer": { "gists": { "nodes": [
   408  							{
   409  								"name": "gistid1",
   410  								"files": [{ "name": "cool.txt" }],
   411  								"description": "",
   412  								"updatedAt": "%[1]v",
   413  								"isPublic": true
   414  							},
   415  							{
   416  								"name": "gistid2",
   417  								"files": [{ "name": "gistfile0.txt" }],
   418  								"description": "",
   419  								"updatedAt": "%[1]v",
   420  								"isPublic": true
   421  							}
   422  						] } } } }`,
   423  			wantOut: "gistid1",
   424  		},
   425  		{
   426  			name: "multiple files, select second gist",
   427  			askStubs: func(as *prompt.AskStubber) {
   428  				as.StubPrompt("Select a gist").AnswerWith("gistfile0.txt  about 6 hours ago")
   429  			},
   430  			response: `{ "data": { "viewer": { "gists": { "nodes": [
   431  							{
   432  								"name": "gistid1",
   433  								"files": [{ "name": "cool.txt" }],
   434  								"description": "",
   435  								"updatedAt": "%[1]v",
   436  								"isPublic": true
   437  							},
   438  							{
   439  								"name": "gistid2",
   440  								"files": [{ "name": "gistfile0.txt" }],
   441  								"description": "",
   442  								"updatedAt": "%[1]v",
   443  								"isPublic": true
   444  							}
   445  						] } } } }`,
   446  			wantOut: "gistid2",
   447  		},
   448  		{
   449  			name:     "no files",
   450  			response: `{ "data": { "viewer": { "gists": { "nodes": [] } } } }`,
   451  			wantOut:  "",
   452  		},
   453  	}
   454  
   455  	ios, _, _, _ := iostreams.Test()
   456  
   457  	for _, tt := range tests {
   458  		reg := &httpmock.Registry{}
   459  
   460  		const query = `query GistList\b`
   461  		sixHours, _ := time.ParseDuration("6h")
   462  		sixHoursAgo := time.Now().Add(-sixHours)
   463  		reg.Register(
   464  			httpmock.GraphQL(query),
   465  			httpmock.StringResponse(fmt.Sprintf(
   466  				tt.response,
   467  				sixHoursAgo.Format(time.RFC3339),
   468  			)),
   469  		)
   470  		client := &http.Client{Transport: reg}
   471  
   472  		t.Run(tt.name, func(t *testing.T) {
   473  			//nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock
   474  			as := prompt.NewAskStubber(t)
   475  			if tt.askStubs != nil {
   476  				tt.askStubs(as)
   477  			}
   478  
   479  			gistID, err := promptGists(client, "github.com", ios.ColorScheme())
   480  			assert.NoError(t, err)
   481  			assert.Equal(t, tt.wantOut, gistID)
   482  			reg.Verify(t)
   483  		})
   484  	}
   485  }