package refresh import ( "bytes" "io" "net/http" "strings" "testing" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/prompt" "github.com/google/shlex" "github.com/stretchr/testify/assert" ) // TODO prompt cfg test func Test_NewCmdRefresh(t *testing.T) { tests := []struct { name string cli string wants RefreshOptions wantsErr bool tty bool neverPrompt bool }{ { name: "tty no arguments", tty: true, wants: RefreshOptions{ Hostname: "", }, }, { name: "nontty no arguments", wantsErr: true, }, { name: "nontty hostname", cli: "-h aline.cedrac", wants: RefreshOptions{ Hostname: "aline.cedrac", }, }, { name: "tty hostname", tty: true, cli: "-h aline.cedrac", wants: RefreshOptions{ Hostname: "aline.cedrac", }, }, { name: "prompts disabled, no args", tty: true, cli: "", neverPrompt: true, wantsErr: true, }, { name: "prompts disabled, hostname", tty: true, cli: "-h aline.cedrac", neverPrompt: true, wants: RefreshOptions{ Hostname: "aline.cedrac", }, }, { name: "tty one scope", tty: true, cli: "--scopes repo:invite", wants: RefreshOptions{ Scopes: []string{"repo:invite"}, }, }, { name: "tty scopes", tty: true, cli: "--scopes repo:invite,read:public_key", wants: RefreshOptions{ Scopes: []string{"repo:invite", "read:public_key"}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ios, _, _, _ := iostreams.Test() f := &cmdutil.Factory{ IOStreams: ios, } ios.SetStdinTTY(tt.tty) ios.SetStdoutTTY(tt.tty) ios.SetNeverPrompt(tt.neverPrompt) argv, err := shlex.Split(tt.cli) assert.NoError(t, err) var gotOpts *RefreshOptions cmd := NewCmdRefresh(f, func(opts *RefreshOptions) error { gotOpts = opts return nil }) // TODO cobra hack-around cmd.Flags().BoolP("help", "x", false, "") cmd.SetArgs(argv) cmd.SetIn(&bytes.Buffer{}) cmd.SetOut(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{}) _, err = cmd.ExecuteC() if tt.wantsErr { assert.Error(t, err) return } assert.NoError(t, err) assert.Equal(t, tt.wants.Hostname, gotOpts.Hostname) assert.Equal(t, tt.wants.Scopes, gotOpts.Scopes) }) } } type authArgs struct { hostname string scopes []string } func Test_refreshRun(t *testing.T) { tests := []struct { name string opts *RefreshOptions askStubs func(*prompt.AskStubber) cfgHosts []string oldScopes string wantErr string nontty bool wantAuthArgs authArgs }{ { name: "no hosts configured", opts: &RefreshOptions{}, wantErr: `not logged in to any hosts`, }, { name: "hostname given but dne", cfgHosts: []string{ "github.com", "aline.cedrac", }, opts: &RefreshOptions{ Hostname: "obed.morton", }, wantErr: `not logged in to obed.morton`, }, { name: "hostname provided and is configured", cfgHosts: []string{ "obed.morton", "github.com", }, opts: &RefreshOptions{ Hostname: "obed.morton", }, wantAuthArgs: authArgs{ hostname: "obed.morton", scopes: nil, }, }, { name: "no hostname, one host configured", cfgHosts: []string{ "github.com", }, opts: &RefreshOptions{ Hostname: "", }, wantAuthArgs: authArgs{ hostname: "github.com", scopes: nil, }, }, { name: "no hostname, multiple hosts configured", cfgHosts: []string{ "github.com", "aline.cedrac", }, opts: &RefreshOptions{ Hostname: "", }, askStubs: func(as *prompt.AskStubber) { as.StubPrompt("What account do you want to refresh auth for?").AnswerWith("github.com") }, wantAuthArgs: authArgs{ hostname: "github.com", scopes: nil, }, }, { name: "scopes provided", cfgHosts: []string{ "github.com", }, opts: &RefreshOptions{ Scopes: []string{"repo:invite", "public_key:read"}, }, wantAuthArgs: authArgs{ hostname: "github.com", scopes: []string{"repo:invite", "public_key:read"}, }, }, { name: "scopes provided", cfgHosts: []string{ "github.com", }, oldScopes: "delete_repo, codespace", opts: &RefreshOptions{ Scopes: []string{"repo:invite", "public_key:read"}, }, wantAuthArgs: authArgs{ hostname: "github.com", scopes: []string{"repo:invite", "public_key:read", "delete_repo", "codespace"}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { aa := authArgs{} tt.opts.AuthFlow = func(_ config.Config, _ *iostreams.IOStreams, hostname string, scopes []string, interactive bool) error { aa.hostname = hostname aa.scopes = scopes return nil } ios, _, _, _ := iostreams.Test() ios.SetStdinTTY(!tt.nontty) ios.SetStdoutTTY(!tt.nontty) tt.opts.IO = ios cfg := config.NewBlankConfig() tt.opts.Config = func() (config.Config, error) { return cfg, nil } for _, hostname := range tt.cfgHosts { _ = cfg.Set(hostname, "oauth_token", "abc123") } httpReg := &httpmock.Registry{} httpReg.Register( httpmock.REST("GET", ""), func(req *http.Request) (*http.Response, error) { statusCode := 200 if req.Header.Get("Authorization") != "token abc123" { statusCode = 400 } return &http.Response{ Request: req, StatusCode: statusCode, Body: io.NopCloser(strings.NewReader(``)), Header: http.Header{ "X-Oauth-Scopes": {tt.oldScopes}, }, }, nil }, ) tt.opts.httpClient = &http.Client{Transport: httpReg} mainBuf := bytes.Buffer{} hostsBuf := bytes.Buffer{} defer config.StubWriteConfig(&mainBuf, &hostsBuf)() as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) } err := refreshRun(tt.opts) if tt.wantErr != "" { if assert.Error(t, err) { assert.Contains(t, err.Error(), tt.wantErr) } } else { assert.NoError(t, err) } assert.Equal(t, tt.wantAuthArgs.hostname, aa.hostname) assert.Equal(t, tt.wantAuthArgs.scopes, aa.scopes) }) } }