github.com/andrewhsu/cli/v2@v2.0.1-0.20210910131313-d4b4061f5b89/pkg/cmd/repo/view/view_test.go (about)

     1  package view
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"testing"
     8  
     9  	"github.com/MakeNowJust/heredoc"
    10  	"github.com/andrewhsu/cli/v2/api"
    11  	"github.com/andrewhsu/cli/v2/internal/ghrepo"
    12  	"github.com/andrewhsu/cli/v2/internal/run"
    13  	"github.com/andrewhsu/cli/v2/pkg/cmdutil"
    14  	"github.com/andrewhsu/cli/v2/pkg/httpmock"
    15  	"github.com/andrewhsu/cli/v2/pkg/iostreams"
    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  		wantsErr bool
    26  	}{
    27  		{
    28  			name: "no args",
    29  			cli:  "",
    30  			wants: ViewOptions{
    31  				RepoArg: "",
    32  				Web:     false,
    33  			},
    34  		},
    35  		{
    36  			name: "sets repo arg",
    37  			cli:  "some/repo",
    38  			wants: ViewOptions{
    39  				RepoArg: "some/repo",
    40  				Web:     false,
    41  			},
    42  		},
    43  		{
    44  			name: "sets web",
    45  			cli:  "-w",
    46  			wants: ViewOptions{
    47  				RepoArg: "",
    48  				Web:     true,
    49  			},
    50  		},
    51  		{
    52  			name: "sets branch",
    53  			cli:  "-b feat/awesome",
    54  			wants: ViewOptions{
    55  				RepoArg: "",
    56  				Branch:  "feat/awesome",
    57  			},
    58  		},
    59  	}
    60  
    61  	for _, tt := range tests {
    62  		t.Run(tt.name, func(t *testing.T) {
    63  			io, _, _, _ := iostreams.Test()
    64  
    65  			f := &cmdutil.Factory{
    66  				IOStreams: io,
    67  			}
    68  
    69  			// THOUGHT: this seems ripe for cmdutil. It's almost identical to the set up for the same test
    70  			// in gist create.
    71  			argv, err := shlex.Split(tt.cli)
    72  			assert.NoError(t, err)
    73  
    74  			var gotOpts *ViewOptions
    75  			cmd := NewCmdView(f, func(opts *ViewOptions) error {
    76  				gotOpts = opts
    77  				return nil
    78  			})
    79  			cmd.SetArgs(argv)
    80  			cmd.SetIn(&bytes.Buffer{})
    81  			cmd.SetOut(&bytes.Buffer{})
    82  			cmd.SetErr(&bytes.Buffer{})
    83  
    84  			_, err = cmd.ExecuteC()
    85  			if tt.wantsErr {
    86  				assert.Error(t, err)
    87  				return
    88  			}
    89  			assert.NoError(t, err)
    90  
    91  			assert.Equal(t, tt.wants.Web, gotOpts.Web)
    92  			assert.Equal(t, tt.wants.Branch, gotOpts.Branch)
    93  			assert.Equal(t, tt.wants.RepoArg, gotOpts.RepoArg)
    94  		})
    95  	}
    96  }
    97  
    98  func Test_RepoView_Web(t *testing.T) {
    99  	tests := []struct {
   100  		name       string
   101  		stdoutTTY  bool
   102  		wantStderr string
   103  		wantBrowse string
   104  	}{
   105  		{
   106  			name:       "tty",
   107  			stdoutTTY:  true,
   108  			wantStderr: "Opening github.com/OWNER/REPO in your browser.\n",
   109  			wantBrowse: "https://github.com/OWNER/REPO",
   110  		},
   111  		{
   112  			name:       "nontty",
   113  			stdoutTTY:  false,
   114  			wantStderr: "",
   115  			wantBrowse: "https://github.com/OWNER/REPO",
   116  		},
   117  	}
   118  
   119  	for _, tt := range tests {
   120  		reg := &httpmock.Registry{}
   121  		reg.StubRepoInfoResponse("OWNER", "REPO", "main")
   122  
   123  		browser := &cmdutil.TestBrowser{}
   124  		opts := &ViewOptions{
   125  			Web: true,
   126  			HttpClient: func() (*http.Client, error) {
   127  				return &http.Client{Transport: reg}, nil
   128  			},
   129  			BaseRepo: func() (ghrepo.Interface, error) {
   130  				return ghrepo.New("OWNER", "REPO"), nil
   131  			},
   132  			Browser: browser,
   133  		}
   134  
   135  		io, _, stdout, stderr := iostreams.Test()
   136  
   137  		opts.IO = io
   138  
   139  		t.Run(tt.name, func(t *testing.T) {
   140  			io.SetStdoutTTY(tt.stdoutTTY)
   141  
   142  			_, teardown := run.Stub()
   143  			defer teardown(t)
   144  
   145  			if err := viewRun(opts); err != nil {
   146  				t.Errorf("viewRun() error = %v", err)
   147  			}
   148  			assert.Equal(t, "", stdout.String())
   149  			assert.Equal(t, tt.wantStderr, stderr.String())
   150  			reg.Verify(t)
   151  			browser.Verify(t, tt.wantBrowse)
   152  		})
   153  	}
   154  }
   155  
   156  func Test_ViewRun(t *testing.T) {
   157  	tests := []struct {
   158  		name       string
   159  		opts       *ViewOptions
   160  		repoName   string
   161  		stdoutTTY  bool
   162  		wantOut    string
   163  		wantStderr string
   164  		wantErr    bool
   165  	}{
   166  		{
   167  			name: "nontty",
   168  			wantOut: heredoc.Doc(`
   169  				name:	OWNER/REPO
   170  				description:	social distancing
   171  				--
   172  				# truly cool readme check it out
   173  				`),
   174  		},
   175  		{
   176  			name:     "url arg",
   177  			repoName: "jill/valentine",
   178  			opts: &ViewOptions{
   179  				RepoArg: "https://github.com/jill/valentine",
   180  			},
   181  			stdoutTTY: true,
   182  			wantOut: heredoc.Doc(`
   183  				jill/valentine
   184  				social distancing
   185  
   186  
   187  				  # truly cool readme check it out                                            
   188  
   189  
   190  
   191  				View this repository on GitHub: https://github.com/jill/valentine
   192  			`),
   193  		},
   194  		{
   195  			name:     "name arg",
   196  			repoName: "jill/valentine",
   197  			opts: &ViewOptions{
   198  				RepoArg: "jill/valentine",
   199  			},
   200  			stdoutTTY: true,
   201  			wantOut: heredoc.Doc(`
   202  				jill/valentine
   203  				social distancing
   204  
   205  
   206  				  # truly cool readme check it out                                            
   207  
   208  
   209  
   210  				View this repository on GitHub: https://github.com/jill/valentine
   211  			`),
   212  		},
   213  		{
   214  			name: "branch arg",
   215  			opts: &ViewOptions{
   216  				Branch: "feat/awesome",
   217  			},
   218  			stdoutTTY: true,
   219  			wantOut: heredoc.Doc(`
   220  				OWNER/REPO
   221  				social distancing
   222  
   223  
   224  				  # truly cool readme check it out                                            
   225  
   226  
   227  
   228  				View this repository on GitHub: https://github.com/OWNER/REPO/tree/feat%2Fawesome
   229  			`),
   230  		},
   231  		{
   232  			name:      "no args",
   233  			stdoutTTY: true,
   234  			wantOut: heredoc.Doc(`
   235  				OWNER/REPO
   236  				social distancing
   237  
   238  
   239  				  # truly cool readme check it out                                            
   240  
   241  
   242  
   243  				View this repository on GitHub: https://github.com/OWNER/REPO
   244  			`),
   245  		},
   246  	}
   247  	for _, tt := range tests {
   248  		if tt.opts == nil {
   249  			tt.opts = &ViewOptions{}
   250  		}
   251  
   252  		if tt.repoName == "" {
   253  			tt.repoName = "OWNER/REPO"
   254  		}
   255  
   256  		tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
   257  			repo, _ := ghrepo.FromFullName(tt.repoName)
   258  			return repo, nil
   259  		}
   260  
   261  		reg := &httpmock.Registry{}
   262  		reg.Register(
   263  			httpmock.GraphQL(`query RepositoryInfo\b`),
   264  			httpmock.StringResponse(`
   265  		{ "data": {
   266  			"repository": {
   267  			"description": "social distancing"
   268  		} } }`))
   269  		reg.Register(
   270  			httpmock.REST("GET", fmt.Sprintf("repos/%s/readme", tt.repoName)),
   271  			httpmock.StringResponse(`
   272  		{ "name": "readme.md",
   273  		"content": "IyB0cnVseSBjb29sIHJlYWRtZSBjaGVjayBpdCBvdXQ="}`))
   274  
   275  		tt.opts.HttpClient = func() (*http.Client, error) {
   276  			return &http.Client{Transport: reg}, nil
   277  		}
   278  
   279  		io, _, stdout, stderr := iostreams.Test()
   280  		tt.opts.IO = io
   281  
   282  		t.Run(tt.name, func(t *testing.T) {
   283  			io.SetStdoutTTY(tt.stdoutTTY)
   284  
   285  			if err := viewRun(tt.opts); (err != nil) != tt.wantErr {
   286  				t.Errorf("viewRun() error = %v, wantErr %v", err, tt.wantErr)
   287  			}
   288  			assert.Equal(t, tt.wantStderr, stderr.String())
   289  			assert.Equal(t, tt.wantOut, stdout.String())
   290  			reg.Verify(t)
   291  		})
   292  	}
   293  }
   294  
   295  func Test_ViewRun_NonMarkdownReadme(t *testing.T) {
   296  	tests := []struct {
   297  		name      string
   298  		stdoutTTY bool
   299  		wantOut   string
   300  	}{
   301  		{
   302  			name: "tty",
   303  			wantOut: heredoc.Doc(`
   304  			OWNER/REPO
   305  			social distancing
   306  
   307  			# truly cool readme check it out
   308  
   309  			View this repository on GitHub: https://github.com/OWNER/REPO
   310  			`),
   311  			stdoutTTY: true,
   312  		},
   313  		{
   314  			name: "nontty",
   315  			wantOut: heredoc.Doc(`
   316  			name:	OWNER/REPO
   317  			description:	social distancing
   318  			--
   319  			# truly cool readme check it out
   320  			`),
   321  		},
   322  	}
   323  
   324  	for _, tt := range tests {
   325  		reg := &httpmock.Registry{}
   326  		reg.Register(
   327  			httpmock.GraphQL(`query RepositoryInfo\b`),
   328  			httpmock.StringResponse(`
   329  		{ "data": {
   330  				"repository": {
   331  				"description": "social distancing"
   332  		} } }`))
   333  		reg.Register(
   334  			httpmock.REST("GET", "repos/OWNER/REPO/readme"),
   335  			httpmock.StringResponse(`
   336  		{ "name": "readme.org",
   337  		"content": "IyB0cnVseSBjb29sIHJlYWRtZSBjaGVjayBpdCBvdXQ="}`))
   338  
   339  		opts := &ViewOptions{
   340  			HttpClient: func() (*http.Client, error) {
   341  				return &http.Client{Transport: reg}, nil
   342  			},
   343  			BaseRepo: func() (ghrepo.Interface, error) {
   344  				return ghrepo.New("OWNER", "REPO"), nil
   345  			},
   346  		}
   347  
   348  		io, _, stdout, stderr := iostreams.Test()
   349  
   350  		opts.IO = io
   351  
   352  		t.Run(tt.name, func(t *testing.T) {
   353  			io.SetStdoutTTY(tt.stdoutTTY)
   354  
   355  			if err := viewRun(opts); err != nil {
   356  				t.Errorf("viewRun() error = %v", err)
   357  			}
   358  			assert.Equal(t, tt.wantOut, stdout.String())
   359  			assert.Equal(t, "", stderr.String())
   360  			reg.Verify(t)
   361  		})
   362  	}
   363  }
   364  
   365  func Test_ViewRun_NoReadme(t *testing.T) {
   366  	tests := []struct {
   367  		name      string
   368  		stdoutTTY bool
   369  		wantOut   string
   370  	}{
   371  		{
   372  			name: "tty",
   373  			wantOut: heredoc.Doc(`
   374  			OWNER/REPO
   375  			social distancing
   376  
   377  			This repository does not have a README
   378  
   379  			View this repository on GitHub: https://github.com/OWNER/REPO
   380  			`),
   381  			stdoutTTY: true,
   382  		},
   383  		{
   384  			name: "nontty",
   385  			wantOut: heredoc.Doc(`
   386  			name:	OWNER/REPO
   387  			description:	social distancing
   388  			`),
   389  		},
   390  	}
   391  
   392  	for _, tt := range tests {
   393  		reg := &httpmock.Registry{}
   394  		reg.Register(
   395  			httpmock.GraphQL(`query RepositoryInfo\b`),
   396  			httpmock.StringResponse(`
   397  		{ "data": {
   398  				"repository": {
   399  				"description": "social distancing"
   400  		} } }`))
   401  		reg.Register(
   402  			httpmock.REST("GET", "repos/OWNER/REPO/readme"),
   403  			httpmock.StatusStringResponse(404, `{}`))
   404  
   405  		opts := &ViewOptions{
   406  			HttpClient: func() (*http.Client, error) {
   407  				return &http.Client{Transport: reg}, nil
   408  			},
   409  			BaseRepo: func() (ghrepo.Interface, error) {
   410  				return ghrepo.New("OWNER", "REPO"), nil
   411  			},
   412  		}
   413  
   414  		io, _, stdout, stderr := iostreams.Test()
   415  
   416  		opts.IO = io
   417  
   418  		t.Run(tt.name, func(t *testing.T) {
   419  			io.SetStdoutTTY(tt.stdoutTTY)
   420  
   421  			if err := viewRun(opts); err != nil {
   422  				t.Errorf("viewRun() error = %v", err)
   423  			}
   424  			assert.Equal(t, tt.wantOut, stdout.String())
   425  			assert.Equal(t, "", stderr.String())
   426  			reg.Verify(t)
   427  		})
   428  	}
   429  }
   430  
   431  func Test_ViewRun_NoDescription(t *testing.T) {
   432  	tests := []struct {
   433  		name      string
   434  		stdoutTTY bool
   435  		wantOut   string
   436  	}{
   437  		{
   438  			name: "tty",
   439  			wantOut: heredoc.Doc(`
   440  			OWNER/REPO
   441  			No description provided
   442  
   443  			# truly cool readme check it out
   444  
   445  			View this repository on GitHub: https://github.com/OWNER/REPO
   446  			`),
   447  			stdoutTTY: true,
   448  		},
   449  		{
   450  			name: "nontty",
   451  			wantOut: heredoc.Doc(`
   452  			name:	OWNER/REPO
   453  			description:	
   454  			--
   455  			# truly cool readme check it out
   456  			`),
   457  		},
   458  	}
   459  
   460  	for _, tt := range tests {
   461  		reg := &httpmock.Registry{}
   462  		reg.Register(
   463  			httpmock.GraphQL(`query RepositoryInfo\b`),
   464  			httpmock.StringResponse(`
   465  		{ "data": {
   466  				"repository": {
   467  				"description": ""
   468  		} } }`))
   469  		reg.Register(
   470  			httpmock.REST("GET", "repos/OWNER/REPO/readme"),
   471  			httpmock.StringResponse(`
   472  		{ "name": "readme.org",
   473  		"content": "IyB0cnVseSBjb29sIHJlYWRtZSBjaGVjayBpdCBvdXQ="}`))
   474  
   475  		opts := &ViewOptions{
   476  			HttpClient: func() (*http.Client, error) {
   477  				return &http.Client{Transport: reg}, nil
   478  			},
   479  			BaseRepo: func() (ghrepo.Interface, error) {
   480  				return ghrepo.New("OWNER", "REPO"), nil
   481  			},
   482  		}
   483  
   484  		io, _, stdout, stderr := iostreams.Test()
   485  
   486  		opts.IO = io
   487  
   488  		t.Run(tt.name, func(t *testing.T) {
   489  			io.SetStdoutTTY(tt.stdoutTTY)
   490  
   491  			if err := viewRun(opts); err != nil {
   492  				t.Errorf("viewRun() error = %v", err)
   493  			}
   494  			assert.Equal(t, tt.wantOut, stdout.String())
   495  			assert.Equal(t, "", stderr.String())
   496  			reg.Verify(t)
   497  		})
   498  	}
   499  }
   500  
   501  func Test_ViewRun_WithoutUsername(t *testing.T) {
   502  	reg := &httpmock.Registry{}
   503  	reg.Register(
   504  		httpmock.GraphQL(`query UserCurrent\b`),
   505  		httpmock.StringResponse(`
   506  		{ "data": { "viewer": {
   507  			"login": "OWNER"
   508  		}}}`))
   509  	reg.Register(
   510  		httpmock.GraphQL(`query RepositoryInfo\b`),
   511  		httpmock.StringResponse(`
   512  	{ "data": {
   513  		"repository": {
   514  		"description": "social distancing"
   515  	} } }`))
   516  	reg.Register(
   517  		httpmock.REST("GET", "repos/OWNER/REPO/readme"),
   518  		httpmock.StringResponse(`
   519  	{ "name": "readme.md",
   520  	"content": "IyB0cnVseSBjb29sIHJlYWRtZSBjaGVjayBpdCBvdXQ="}`))
   521  
   522  	io, _, stdout, stderr := iostreams.Test()
   523  	io.SetStdoutTTY(false)
   524  
   525  	opts := &ViewOptions{
   526  		RepoArg: "REPO",
   527  		HttpClient: func() (*http.Client, error) {
   528  			return &http.Client{Transport: reg}, nil
   529  		},
   530  		IO: io,
   531  	}
   532  
   533  	if err := viewRun(opts); err != nil {
   534  		t.Errorf("viewRun() error = %v", err)
   535  	}
   536  
   537  	assert.Equal(t, heredoc.Doc(`
   538  			name:	OWNER/REPO
   539  			description:	social distancing
   540  			--
   541  			# truly cool readme check it out
   542  			`), stdout.String())
   543  	assert.Equal(t, "", stderr.String())
   544  	reg.Verify(t)
   545  }
   546  
   547  func Test_ViewRun_HandlesSpecialCharacters(t *testing.T) {
   548  	tests := []struct {
   549  		name       string
   550  		opts       *ViewOptions
   551  		repoName   string
   552  		stdoutTTY  bool
   553  		wantOut    string
   554  		wantStderr string
   555  		wantErr    bool
   556  	}{
   557  		{
   558  			name: "nontty",
   559  			wantOut: heredoc.Doc(`
   560  				name:	OWNER/REPO
   561  				description:	Some basic special characters " & / < > '
   562  				--
   563  				# < is always > than & ' and "
   564  				`),
   565  		},
   566  		{
   567  			name:      "no args",
   568  			stdoutTTY: true,
   569  			wantOut: heredoc.Doc(`
   570  				OWNER/REPO
   571  				Some basic special characters " & / < > '
   572  
   573  
   574  				  # < is always > than & ' and "                                              
   575  
   576  
   577  
   578  				View this repository on GitHub: https://github.com/OWNER/REPO
   579  			`),
   580  		},
   581  	}
   582  	for _, tt := range tests {
   583  		if tt.opts == nil {
   584  			tt.opts = &ViewOptions{}
   585  		}
   586  
   587  		if tt.repoName == "" {
   588  			tt.repoName = "OWNER/REPO"
   589  		}
   590  
   591  		tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
   592  			repo, _ := ghrepo.FromFullName(tt.repoName)
   593  			return repo, nil
   594  		}
   595  
   596  		reg := &httpmock.Registry{}
   597  		reg.Register(
   598  			httpmock.GraphQL(`query RepositoryInfo\b`),
   599  			httpmock.StringResponse(`
   600  		{ "data": {
   601  			"repository": {
   602  			"description": "Some basic special characters \" & / < > '"
   603  		} } }`))
   604  		reg.Register(
   605  			httpmock.REST("GET", fmt.Sprintf("repos/%s/readme", tt.repoName)),
   606  			httpmock.StringResponse(`
   607  		{ "name": "readme.md",
   608  		"content": "IyA8IGlzIGFsd2F5cyA+IHRoYW4gJiAnIGFuZCAi"}`))
   609  
   610  		tt.opts.HttpClient = func() (*http.Client, error) {
   611  			return &http.Client{Transport: reg}, nil
   612  		}
   613  
   614  		io, _, stdout, stderr := iostreams.Test()
   615  		tt.opts.IO = io
   616  
   617  		t.Run(tt.name, func(t *testing.T) {
   618  			io.SetStdoutTTY(tt.stdoutTTY)
   619  
   620  			if err := viewRun(tt.opts); (err != nil) != tt.wantErr {
   621  				t.Errorf("viewRun() error = %v, wantErr %v", err, tt.wantErr)
   622  			}
   623  			assert.Equal(t, tt.wantStderr, stderr.String())
   624  			assert.Equal(t, tt.wantOut, stdout.String())
   625  			reg.Verify(t)
   626  		})
   627  	}
   628  }
   629  
   630  func Test_viewRun_json(t *testing.T) {
   631  	io, _, stdout, stderr := iostreams.Test()
   632  	io.SetStdoutTTY(false)
   633  
   634  	reg := &httpmock.Registry{}
   635  	defer reg.Verify(t)
   636  	reg.StubRepoInfoResponse("OWNER", "REPO", "main")
   637  
   638  	opts := &ViewOptions{
   639  		IO: io,
   640  		HttpClient: func() (*http.Client, error) {
   641  			return &http.Client{Transport: reg}, nil
   642  		},
   643  		BaseRepo: func() (ghrepo.Interface, error) {
   644  			return ghrepo.New("OWNER", "REPO"), nil
   645  		},
   646  		Exporter: &testExporter{
   647  			fields: []string{"name", "defaultBranchRef"},
   648  		},
   649  	}
   650  
   651  	_, teardown := run.Stub()
   652  	defer teardown(t)
   653  
   654  	err := viewRun(opts)
   655  	assert.NoError(t, err)
   656  	assert.Equal(t, heredoc.Doc(`
   657  		name: REPO
   658  		defaultBranchRef: main
   659  	`), stdout.String())
   660  	assert.Equal(t, "", stderr.String())
   661  }
   662  
   663  type testExporter struct {
   664  	fields []string
   665  }
   666  
   667  func (e *testExporter) Fields() []string {
   668  	return e.fields
   669  }
   670  
   671  func (e *testExporter) Write(io *iostreams.IOStreams, data interface{}) error {
   672  	r := data.(*api.Repository)
   673  	fmt.Fprintf(io.Out, "name: %s\n", r.Name)
   674  	fmt.Fprintf(io.Out, "defaultBranchRef: %s\n", r.DefaultBranchRef.Name)
   675  	return nil
   676  }