github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/auth/refresh/refresh_test.go (about)

     1  package refresh
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"net/http"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/ungtb10d/cli/v2/internal/config"
    11  	"github.com/ungtb10d/cli/v2/internal/prompter"
    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/google/shlex"
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  // TODO prompt cfg test
    20  
    21  func Test_NewCmdRefresh(t *testing.T) {
    22  	tests := []struct {
    23  		name        string
    24  		cli         string
    25  		wants       RefreshOptions
    26  		wantsErr    bool
    27  		tty         bool
    28  		neverPrompt bool
    29  	}{
    30  		{
    31  			name: "tty no arguments",
    32  			tty:  true,
    33  			wants: RefreshOptions{
    34  				Hostname: "",
    35  			},
    36  		},
    37  		{
    38  			name:     "nontty no arguments",
    39  			wantsErr: true,
    40  		},
    41  		{
    42  			name: "nontty hostname",
    43  			cli:  "-h aline.cedrac",
    44  			wants: RefreshOptions{
    45  				Hostname: "aline.cedrac",
    46  			},
    47  		},
    48  		{
    49  			name: "tty hostname",
    50  			tty:  true,
    51  			cli:  "-h aline.cedrac",
    52  			wants: RefreshOptions{
    53  				Hostname: "aline.cedrac",
    54  			},
    55  		},
    56  		{
    57  			name:        "prompts disabled, no args",
    58  			tty:         true,
    59  			cli:         "",
    60  			neverPrompt: true,
    61  			wantsErr:    true,
    62  		},
    63  		{
    64  			name:        "prompts disabled, hostname",
    65  			tty:         true,
    66  			cli:         "-h aline.cedrac",
    67  			neverPrompt: true,
    68  			wants: RefreshOptions{
    69  				Hostname: "aline.cedrac",
    70  			},
    71  		},
    72  		{
    73  			name: "tty one scope",
    74  			tty:  true,
    75  			cli:  "--scopes repo:invite",
    76  			wants: RefreshOptions{
    77  				Scopes: []string{"repo:invite"},
    78  			},
    79  		},
    80  		{
    81  			name: "tty scopes",
    82  			tty:  true,
    83  			cli:  "--scopes repo:invite,read:public_key",
    84  			wants: RefreshOptions{
    85  				Scopes: []string{"repo:invite", "read:public_key"},
    86  			},
    87  		},
    88  	}
    89  
    90  	for _, tt := range tests {
    91  		t.Run(tt.name, func(t *testing.T) {
    92  			ios, _, _, _ := iostreams.Test()
    93  			f := &cmdutil.Factory{
    94  				IOStreams: ios,
    95  			}
    96  			ios.SetStdinTTY(tt.tty)
    97  			ios.SetStdoutTTY(tt.tty)
    98  			ios.SetNeverPrompt(tt.neverPrompt)
    99  
   100  			argv, err := shlex.Split(tt.cli)
   101  			assert.NoError(t, err)
   102  
   103  			var gotOpts *RefreshOptions
   104  			cmd := NewCmdRefresh(f, func(opts *RefreshOptions) error {
   105  				gotOpts = opts
   106  				return nil
   107  			})
   108  			// TODO cobra hack-around
   109  			cmd.Flags().BoolP("help", "x", false, "")
   110  
   111  			cmd.SetArgs(argv)
   112  			cmd.SetIn(&bytes.Buffer{})
   113  			cmd.SetOut(&bytes.Buffer{})
   114  			cmd.SetErr(&bytes.Buffer{})
   115  
   116  			_, err = cmd.ExecuteC()
   117  			if tt.wantsErr {
   118  				assert.Error(t, err)
   119  				return
   120  			}
   121  			assert.NoError(t, err)
   122  			assert.Equal(t, tt.wants.Hostname, gotOpts.Hostname)
   123  			assert.Equal(t, tt.wants.Scopes, gotOpts.Scopes)
   124  		})
   125  	}
   126  }
   127  
   128  type authArgs struct {
   129  	hostname string
   130  	scopes   []string
   131  }
   132  
   133  func Test_refreshRun(t *testing.T) {
   134  	tests := []struct {
   135  		name          string
   136  		opts          *RefreshOptions
   137  		prompterStubs func(*prompter.PrompterMock)
   138  		cfgHosts      []string
   139  		oldScopes     string
   140  		wantErr       string
   141  		nontty        bool
   142  		wantAuthArgs  authArgs
   143  	}{
   144  		{
   145  			name:    "no hosts configured",
   146  			opts:    &RefreshOptions{},
   147  			wantErr: `not logged in to any hosts`,
   148  		},
   149  		{
   150  			name: "hostname given but dne",
   151  			cfgHosts: []string{
   152  				"github.com",
   153  				"aline.cedrac",
   154  			},
   155  			opts: &RefreshOptions{
   156  				Hostname: "obed.morton",
   157  			},
   158  			wantErr: `not logged in to obed.morton`,
   159  		},
   160  		{
   161  			name: "hostname provided and is configured",
   162  			cfgHosts: []string{
   163  				"obed.morton",
   164  				"github.com",
   165  			},
   166  			opts: &RefreshOptions{
   167  				Hostname: "obed.morton",
   168  			},
   169  			wantAuthArgs: authArgs{
   170  				hostname: "obed.morton",
   171  				scopes:   nil,
   172  			},
   173  		},
   174  		{
   175  			name: "no hostname, one host configured",
   176  			cfgHosts: []string{
   177  				"github.com",
   178  			},
   179  			opts: &RefreshOptions{
   180  				Hostname: "",
   181  			},
   182  			wantAuthArgs: authArgs{
   183  				hostname: "github.com",
   184  				scopes:   nil,
   185  			},
   186  		},
   187  		{
   188  			name: "no hostname, multiple hosts configured",
   189  			cfgHosts: []string{
   190  				"github.com",
   191  				"aline.cedrac",
   192  			},
   193  			opts: &RefreshOptions{
   194  				Hostname: "",
   195  			},
   196  			prompterStubs: func(pm *prompter.PrompterMock) {
   197  				pm.SelectFunc = func(_, _ string, opts []string) (int, error) {
   198  					return prompter.IndexFor(opts, "github.com")
   199  				}
   200  			},
   201  			wantAuthArgs: authArgs{
   202  				hostname: "github.com",
   203  				scopes:   nil,
   204  			},
   205  		},
   206  		{
   207  			name: "scopes provided",
   208  			cfgHosts: []string{
   209  				"github.com",
   210  			},
   211  			opts: &RefreshOptions{
   212  				Scopes: []string{"repo:invite", "public_key:read"},
   213  			},
   214  			wantAuthArgs: authArgs{
   215  				hostname: "github.com",
   216  				scopes:   []string{"repo:invite", "public_key:read"},
   217  			},
   218  		},
   219  		{
   220  			name: "scopes provided",
   221  			cfgHosts: []string{
   222  				"github.com",
   223  			},
   224  			oldScopes: "delete_repo, codespace",
   225  			opts: &RefreshOptions{
   226  				Scopes: []string{"repo:invite", "public_key:read"},
   227  			},
   228  			wantAuthArgs: authArgs{
   229  				hostname: "github.com",
   230  				scopes:   []string{"repo:invite", "public_key:read", "delete_repo", "codespace"},
   231  			},
   232  		},
   233  	}
   234  	for _, tt := range tests {
   235  		t.Run(tt.name, func(t *testing.T) {
   236  			aa := authArgs{}
   237  			tt.opts.AuthFlow = func(_ config.Config, _ *iostreams.IOStreams, hostname string, scopes []string, interactive bool) error {
   238  				aa.hostname = hostname
   239  				aa.scopes = scopes
   240  				return nil
   241  			}
   242  
   243  			_ = config.StubWriteConfig(t)
   244  			cfg := config.NewFromString("")
   245  			for _, hostname := range tt.cfgHosts {
   246  				cfg.Set(hostname, "oauth_token", "abc123")
   247  			}
   248  			tt.opts.Config = func() (config.Config, error) {
   249  				return cfg, nil
   250  			}
   251  
   252  			ios, _, _, _ := iostreams.Test()
   253  			ios.SetStdinTTY(!tt.nontty)
   254  			ios.SetStdoutTTY(!tt.nontty)
   255  			tt.opts.IO = ios
   256  
   257  			httpReg := &httpmock.Registry{}
   258  			httpReg.Register(
   259  				httpmock.REST("GET", ""),
   260  				func(req *http.Request) (*http.Response, error) {
   261  					statusCode := 200
   262  					if req.Header.Get("Authorization") != "token abc123" {
   263  						statusCode = 400
   264  					}
   265  					return &http.Response{
   266  						Request:    req,
   267  						StatusCode: statusCode,
   268  						Body:       io.NopCloser(strings.NewReader(``)),
   269  						Header: http.Header{
   270  							"X-Oauth-Scopes": {tt.oldScopes},
   271  						},
   272  					}, nil
   273  				},
   274  			)
   275  			tt.opts.HttpClient = &http.Client{Transport: httpReg}
   276  
   277  			pm := &prompter.PrompterMock{}
   278  			if tt.prompterStubs != nil {
   279  				tt.prompterStubs(pm)
   280  			}
   281  			tt.opts.Prompter = pm
   282  
   283  			err := refreshRun(tt.opts)
   284  			if tt.wantErr != "" {
   285  				if assert.Error(t, err) {
   286  					assert.Contains(t, err.Error(), tt.wantErr)
   287  				}
   288  			} else {
   289  				assert.NoError(t, err)
   290  			}
   291  
   292  			assert.Equal(t, tt.wantAuthArgs.hostname, aa.hostname)
   293  			assert.Equal(t, tt.wantAuthArgs.scopes, aa.scopes)
   294  		})
   295  	}
   296  }