gh auth status
This commit is contained in:
parent
9f5daeca19
commit
ec25b735ab
9 changed files with 492 additions and 47 deletions
|
|
@ -196,6 +196,10 @@ func (err HTTPError) Error() string {
|
|||
return fmt.Sprintf("HTTP %d (%s)", err.StatusCode, err.RequestURL)
|
||||
}
|
||||
|
||||
type MissingScopesError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (c Client) HasMinimumScopes(hostname string) (bool, error) {
|
||||
apiEndpoint := ghinstance.RESTPrefix(hostname)
|
||||
|
||||
|
|
@ -243,11 +247,10 @@ func (c Client) HasMinimumScopes(hostname string) (bool, error) {
|
|||
}
|
||||
|
||||
if len(errorMsgs) > 0 {
|
||||
return false, errors.New(strings.Join(errorMsgs, ";"))
|
||||
return false, &MissingScopesError{error: errors.New(strings.Join(errorMsgs, ";"))}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
|
||||
}
|
||||
|
||||
// GraphQL performs a GraphQL request and parses the response
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
authCmd "github.com/cli/cli/pkg/cmd/auth"
|
||||
authLoginCmd "github.com/cli/cli/pkg/cmd/auth/login"
|
||||
authLogoutCmd "github.com/cli/cli/pkg/cmd/auth/logout"
|
||||
authStatusCmd "github.com/cli/cli/pkg/cmd/auth/status"
|
||||
gistCreateCmd "github.com/cli/cli/pkg/cmd/gist/create"
|
||||
prCmd "github.com/cli/cli/pkg/cmd/pr"
|
||||
repoCmd "github.com/cli/cli/pkg/cmd/repo"
|
||||
|
|
@ -138,6 +139,7 @@ func init() {
|
|||
RootCmd.AddCommand(authCmd.Cmd)
|
||||
authCmd.Cmd.AddCommand(authLoginCmd.NewCmdLogin(cmdFactory, nil))
|
||||
authCmd.Cmd.AddCommand(authLogoutCmd.NewCmdLogout(cmdFactory, nil))
|
||||
authCmd.Cmd.AddCommand(authStatusCmd.NewCmdStatus(cmdFactory, nil))
|
||||
|
||||
resolvedBaseRepo := func() (ghrepo.Interface, error) {
|
||||
httpClient, err := cmdFactory.HttpClient()
|
||||
|
|
|
|||
|
|
@ -8,11 +8,4 @@ var Cmd = &cobra.Command{
|
|||
Use: "auth <command>",
|
||||
Short: "Login, logout, and refresh your authentication",
|
||||
Long: `Manage gh's authentication state.`,
|
||||
// TODO this all doesn't exist yet
|
||||
//Example: heredoc.Doc(`
|
||||
// $ gh auth login
|
||||
// $ gh auth status
|
||||
// $ gh auth refresh --scopes gist
|
||||
// $ gh auth logout
|
||||
//`),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package login
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -8,8 +8,8 @@ import (
|
|||
"github.com/cli/cli/internal/config"
|
||||
)
|
||||
|
||||
func validateHostCfg(hostname string, cfg config.Config) error {
|
||||
apiClient, err := clientFromCfg(hostname, cfg)
|
||||
func ValidateHostCfg(hostname string, cfg config.Config) error {
|
||||
apiClient, err := ClientFromCfg(hostname, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ func validateHostCfg(hostname string, cfg config.Config) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var clientFromCfg = func(hostname string, cfg config.Config) (*api.Client, error) {
|
||||
var ClientFromCfg = func(hostname string, cfg config.Config) (*api.Client, error) {
|
||||
var opts []api.ClientOption
|
||||
|
||||
token, err := cfg.Get(hostname, "oauth_token")
|
||||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/pkg/cmd/auth/client"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
|
|
@ -119,7 +120,7 @@ func loginRun(opts *LoginOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = validateHostCfg(opts.Hostname, cfg)
|
||||
err = client.ValidateHostCfg(opts.Hostname, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -167,9 +168,9 @@ func loginRun(opts *LoginOptions) error {
|
|||
existingToken, _ := cfg.Get(hostname, "oauth_token")
|
||||
|
||||
if existingToken != "" {
|
||||
err := validateHostCfg(hostname, cfg)
|
||||
err := client.ValidateHostCfg(hostname, cfg)
|
||||
if err == nil {
|
||||
apiClient, err := clientFromCfg(hostname, cfg)
|
||||
apiClient, err := client.ClientFromCfg(hostname, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -235,7 +236,7 @@ func loginRun(opts *LoginOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = validateHostCfg(hostname, cfg)
|
||||
err = client.ValidateHostCfg(hostname, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -263,7 +264,7 @@ func loginRun(opts *LoginOptions) error {
|
|||
|
||||
fmt.Fprintf(opts.IO.ErrOut, "%s Configured git protocol\n", utils.GreenCheck())
|
||||
|
||||
apiClient, err := clientFromCfg(hostname, cfg)
|
||||
apiClient, err := client.ClientFromCfg(hostname, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package login
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
|
|
@ -10,6 +9,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmd/auth/client"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
|
|
@ -164,19 +164,6 @@ func Test_NewCmdLogin(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func scopesResponder(scopes string) func(*http.Request) (*http.Response, error) {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Request: req,
|
||||
Header: map[string][]string{
|
||||
"X-Oauth-Scopes": {scopes},
|
||||
},
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString("")),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func Test_loginRun_nontty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -200,7 +187,7 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
Token: "abc123",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), scopesResponder("repo,read:org"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org"))
|
||||
},
|
||||
wantHosts: "albert.wesker:\n oauth_token: abc123\n",
|
||||
},
|
||||
|
|
@ -211,7 +198,7 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
Token: "abc456",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), scopesResponder("read:org"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("read:org"))
|
||||
},
|
||||
wantErr: regexp.MustCompile(`missing required scope 'repo'`),
|
||||
},
|
||||
|
|
@ -222,7 +209,7 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
Token: "abc456",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), scopesResponder("repo"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo"))
|
||||
},
|
||||
wantErr: regexp.MustCompile(`missing required scope 'read:org'`),
|
||||
},
|
||||
|
|
@ -233,7 +220,7 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
Token: "abc456",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), scopesResponder("repo,admin:org"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,admin:org"))
|
||||
},
|
||||
wantHosts: "github.com:\n oauth_token: abc456\n",
|
||||
},
|
||||
|
|
@ -252,11 +239,11 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
tt.opts.IO = io
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
origClientFromCfg := clientFromCfg
|
||||
origClientFromCfg := client.ClientFromCfg
|
||||
defer func() {
|
||||
clientFromCfg = origClientFromCfg
|
||||
client.ClientFromCfg = origClientFromCfg
|
||||
}()
|
||||
clientFromCfg = func(_ string, _ config.Config) (*api.Client, error) {
|
||||
client.ClientFromCfg = func(_ string, _ config.Config) (*api.Client, error) {
|
||||
httpClient := &http.Client{Transport: reg}
|
||||
return api.NewClientFromHTTP(httpClient), nil
|
||||
}
|
||||
|
|
@ -264,7 +251,7 @@ func Test_loginRun_nontty(t *testing.T) {
|
|||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
} else {
|
||||
reg.Register(httpmock.REST("GET", ""), scopesResponder("repo,read:org"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org"))
|
||||
}
|
||||
|
||||
mainBuf := bytes.Buffer{}
|
||||
|
|
@ -305,7 +292,7 @@ func Test_loginRun_Survey(t *testing.T) {
|
|||
_ = cfg.Set("github.com", "oauth_token", "ghi789")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), scopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
|
||||
|
|
@ -328,7 +315,7 @@ func Test_loginRun_Survey(t *testing.T) {
|
|||
as.StubOne("HTTPS") // git_protocol
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), scopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
|
||||
|
|
@ -345,7 +332,7 @@ func Test_loginRun_Survey(t *testing.T) {
|
|||
as.StubOne("HTTPS") // git_protocol
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), scopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
|
||||
|
|
@ -397,18 +384,18 @@ func Test_loginRun_Survey(t *testing.T) {
|
|||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
origClientFromCfg := clientFromCfg
|
||||
origClientFromCfg := client.ClientFromCfg
|
||||
defer func() {
|
||||
clientFromCfg = origClientFromCfg
|
||||
client.ClientFromCfg = origClientFromCfg
|
||||
}()
|
||||
clientFromCfg = func(_ string, _ config.Config) (*api.Client, error) {
|
||||
client.ClientFromCfg = func(_ string, _ config.Config) (*api.Client, error) {
|
||||
httpClient := &http.Client{Transport: reg}
|
||||
return api.NewClientFromHTTP(httpClient), nil
|
||||
}
|
||||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
} else {
|
||||
reg.Register(httpmock.REST("GET", ""), scopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
|
||||
|
|
|
|||
152
pkg/cmd/auth/status/status.go
Normal file
152
pkg/cmd/auth/status/status.go
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/pkg/cmd/auth/client"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type StatusOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (config.Config, error)
|
||||
Token string
|
||||
Hostname string
|
||||
}
|
||||
|
||||
func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Command {
|
||||
opts := &StatusOptions{
|
||||
HttpClient: f.HttpClient,
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "status",
|
||||
Args: cobra.ExactArgs(0),
|
||||
Short: "View authentication status",
|
||||
Long: heredoc.Doc(`Verifies and displays information about your authentication state.
|
||||
|
||||
This command will test your authentication state for each GitHub host that gh knows about and
|
||||
report on any issues.
|
||||
`),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// TODO support other names
|
||||
opts.Token = os.Getenv("GITHUB_TOKEN")
|
||||
|
||||
if opts.Token != "" && opts.Hostname == "" {
|
||||
opts.Hostname = ghinstance.Default()
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
return statusRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "Check a specific hostname's auth status")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func statusRun(opts *StatusOptions) error {
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO check tty
|
||||
|
||||
stderr := opts.IO.ErrOut
|
||||
|
||||
if opts.Token != "" {
|
||||
hostname := opts.Hostname
|
||||
err := cfg.Set(opts.Hostname, "oauth_token", opts.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiClient, err := client.ClientFromCfg(hostname, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = apiClient.HasMinimumScopes(hostname)
|
||||
if err != nil {
|
||||
var missingScopes *api.MissingScopesError
|
||||
if errors.As(err, &missingScopes) {
|
||||
return fmt.Errorf("%s %s: %s", utils.Red("X"), hostname, err)
|
||||
} else {
|
||||
return fmt.Errorf("%s %s: authentication failed", utils.Red("X"), hostname)
|
||||
}
|
||||
} else {
|
||||
username, err := api.CurrentLoginName(apiClient, hostname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s: api call failed: %s\n", utils.Red("X"), hostname, err)
|
||||
}
|
||||
fmt.Fprintf(stderr, "%s token valid for %s as %s\n", utils.GreenCheck(), hostname, utils.Bold(username))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
hostnames, err := cfg.Hosts()
|
||||
if len(hostnames) == 0 || err != nil {
|
||||
fmt.Fprintf(stderr, "You are not logged into any GitHub hosts. Run 'gh auth login' to authenticate.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
var failed bool
|
||||
|
||||
for _, hostname := range hostnames {
|
||||
if opts.Hostname != "" && opts.Hostname != hostname {
|
||||
continue
|
||||
}
|
||||
_, err = apiClient.HasMinimumScopes(hostname)
|
||||
if err != nil {
|
||||
var missingScopes *api.MissingScopesError
|
||||
if errors.As(err, &missingScopes) {
|
||||
fmt.Fprintf(stderr, "%s %s: %s\n", utils.Red("X"), hostname, err)
|
||||
} else {
|
||||
fmt.Fprintf(stderr, "%s %s: authentication failed\n", utils.Red("X"), hostname)
|
||||
}
|
||||
failed = true
|
||||
} else {
|
||||
username, err := api.CurrentLoginName(apiClient, hostname)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "%s %s: api call failed: %s\n", utils.Red("X"), hostname, err)
|
||||
}
|
||||
fmt.Fprintf(stderr, "%s Logged in to %s as %s\n", utils.GreenCheck(), hostname, utils.Bold(username))
|
||||
}
|
||||
|
||||
// NB we could take this opportunity to add or fix the "user" key in the hosts config. I chose
|
||||
// not to since I wanted this command to be read-only.
|
||||
}
|
||||
|
||||
if failed {
|
||||
// TODO unsure about this; want non-zero exit but don't need to print anything more. Is the
|
||||
// non-zero exit worth it? Should we tweak error handling to not print "" errors?
|
||||
return errors.New("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
294
pkg/cmd/auth/status/status_test.go
Normal file
294
pkg/cmd/auth/status/status_test.go
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmd/auth/client"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_NewCmdStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants StatusOptions
|
||||
ghtoken string
|
||||
}{
|
||||
{
|
||||
name: "ghtoken set",
|
||||
cli: "",
|
||||
wants: StatusOptions{
|
||||
Token: "abc123",
|
||||
Hostname: "github.com",
|
||||
},
|
||||
ghtoken: "abc123",
|
||||
},
|
||||
{
|
||||
name: "ghtoken set",
|
||||
cli: "--hostname joel.miller",
|
||||
wants: StatusOptions{
|
||||
Token: "def456",
|
||||
Hostname: "joel.miller",
|
||||
},
|
||||
ghtoken: "def456",
|
||||
},
|
||||
{
|
||||
name: "no arguments",
|
||||
cli: "",
|
||||
wants: StatusOptions{},
|
||||
},
|
||||
{
|
||||
name: "hostname set",
|
||||
cli: "--hostname ellie.williams",
|
||||
wants: StatusOptions{
|
||||
Hostname: "ellie.williams",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ghtoken := os.Getenv("GITHUB_TOKEN")
|
||||
defer func() {
|
||||
os.Setenv("GITHUB_TOKEN", ghtoken)
|
||||
}()
|
||||
os.Setenv("GITHUB_TOKEN", tt.ghtoken)
|
||||
|
||||
f := &cmdutil.Factory{}
|
||||
|
||||
argv, err := shlex.Split(tt.cli)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var gotOpts *StatusOptions
|
||||
cmd := NewCmdStatus(f, func(opts *StatusOptions) 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()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wants.Token, gotOpts.Token)
|
||||
assert.Equal(t, tt.wants.Hostname, gotOpts.Hostname)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_statusRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *StatusOptions
|
||||
httpStubs func(*httpmock.Registry)
|
||||
cfg func(config.Config)
|
||||
wantErr *regexp.Regexp
|
||||
wantErrOut *regexp.Regexp
|
||||
}{
|
||||
{
|
||||
name: "token set, bad token",
|
||||
opts: &StatusOptions{
|
||||
Token: "abc123",
|
||||
Hostname: "github.com",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
httpmock.StatusStringResponse(400, "no bueno"),
|
||||
)
|
||||
},
|
||||
wantErr: regexp.MustCompile(`authentication failed`),
|
||||
},
|
||||
{
|
||||
name: "token set, missing scope",
|
||||
opts: &StatusOptions{
|
||||
Token: "abc123",
|
||||
Hostname: "github.com",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,"))
|
||||
},
|
||||
wantErr: regexp.MustCompile(`missing required scope 'read:org'`),
|
||||
},
|
||||
{
|
||||
name: "token set, good token",
|
||||
opts: &StatusOptions{
|
||||
Token: "abc123",
|
||||
Hostname: "github.com",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
},
|
||||
wantErrOut: regexp.MustCompile(`token valid for github.com as.*tess`),
|
||||
},
|
||||
{
|
||||
name: "hostname set",
|
||||
opts: &StatusOptions{
|
||||
Hostname: "joel.miller",
|
||||
},
|
||||
cfg: func(c config.Config) {
|
||||
_ = c.Set("joel.miller", "oauth_token", "abc123")
|
||||
_ = c.Set("github.com", "oauth_token", "abc123")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
},
|
||||
wantErrOut: regexp.MustCompile(`Logged in to joel.miller as.*tess`),
|
||||
},
|
||||
{
|
||||
name: "hostname set",
|
||||
opts: &StatusOptions{
|
||||
Hostname: "joel.miller",
|
||||
},
|
||||
cfg: func(c config.Config) {
|
||||
_ = c.Set("joel.miller", "oauth_token", "abc123")
|
||||
_ = c.Set("github.com", "oauth_token", "abc123")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
},
|
||||
wantErrOut: regexp.MustCompile(`Logged in to joel.miller as.*tess`),
|
||||
},
|
||||
{
|
||||
name: "missing scope",
|
||||
opts: &StatusOptions{},
|
||||
cfg: func(c config.Config) {
|
||||
_ = c.Set("joel.miller", "oauth_token", "abc123")
|
||||
_ = c.Set("github.com", "oauth_token", "abc123")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
},
|
||||
wantErrOut: regexp.MustCompile(`joel.miller: missing required.*Logged in to github.com as.*tess`),
|
||||
wantErr: regexp.MustCompile(``),
|
||||
},
|
||||
{
|
||||
name: "bad token",
|
||||
opts: &StatusOptions{},
|
||||
cfg: func(c config.Config) {
|
||||
_ = c.Set("joel.miller", "oauth_token", "abc123")
|
||||
_ = c.Set("github.com", "oauth_token", "abc123")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.StatusStringResponse(400, "no bueno"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
},
|
||||
wantErrOut: regexp.MustCompile(`joel.miller: authentication failed.*Logged in to github.com as.*tess`),
|
||||
wantErr: regexp.MustCompile(``),
|
||||
},
|
||||
{
|
||||
name: "all good",
|
||||
opts: &StatusOptions{},
|
||||
cfg: func(c config.Config) {
|
||||
_ = c.Set("joel.miller", "oauth_token", "abc123")
|
||||
_ = c.Set("github.com", "oauth_token", "abc123")
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,"))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`))
|
||||
},
|
||||
wantErrOut: regexp.MustCompile(`(?s)Logged in to github.com as.*tess.*Logged in to joel.miller as.*tess`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.opts == nil {
|
||||
tt.opts = &StatusOptions{}
|
||||
}
|
||||
|
||||
io, _, _, stderr := iostreams.Test()
|
||||
|
||||
io.SetStdinTTY(true)
|
||||
io.SetStderrTTY(true)
|
||||
io.SetStdoutTTY(true)
|
||||
|
||||
tt.opts.IO = io
|
||||
|
||||
cfg := config.NewBlankConfig()
|
||||
|
||||
if tt.cfg != nil {
|
||||
tt.cfg(cfg)
|
||||
}
|
||||
tt.opts.Config = func() (config.Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
reg := &httpmock.Registry{}
|
||||
origClientFromCfg := client.ClientFromCfg
|
||||
defer func() {
|
||||
client.ClientFromCfg = origClientFromCfg
|
||||
}()
|
||||
client.ClientFromCfg = func(_ string, _ config.Config) (*api.Client, error) {
|
||||
httpClient := &http.Client{Transport: reg}
|
||||
return api.NewClientFromHTTP(httpClient), nil
|
||||
}
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
}
|
||||
mainBuf := bytes.Buffer{}
|
||||
hostsBuf := bytes.Buffer{}
|
||||
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
|
||||
|
||||
err := statusRun(tt.opts)
|
||||
assert.Equal(t, tt.wantErr == nil, err == nil)
|
||||
if err != nil {
|
||||
if tt.wantErr != nil {
|
||||
assert.True(t, tt.wantErr.MatchString(err.Error()))
|
||||
return
|
||||
} else {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tt.wantErrOut == nil {
|
||||
assert.Equal(t, "", stderr.String())
|
||||
} else {
|
||||
assert.True(t, tt.wantErrOut.MatchString(stderr.String()))
|
||||
}
|
||||
|
||||
reg.Verify(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -133,6 +133,19 @@ func GraphQLQuery(body string, cb func(string, map[string]interface{})) Responde
|
|||
}
|
||||
}
|
||||
|
||||
func ScopesResponder(scopes string) func(*http.Request) (*http.Response, error) {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Request: req,
|
||||
Header: map[string][]string{
|
||||
"X-Oauth-Scopes": {scopes},
|
||||
},
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString("")),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func httpResponse(status int, req *http.Request, body io.Reader) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: status,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue