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