fix(auth status): return JSON entries under hosts
Signed-off-by: Babak K. Shandiz <babakks@github.com>
This commit is contained in:
parent
914531e6f1
commit
5fddcef0a8
3 changed files with 161 additions and 225 deletions
|
|
@ -1,28 +0,0 @@
|
|||
package status
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type authState int
|
||||
|
||||
const (
|
||||
authStateSuccess authState = iota
|
||||
authStateTimeout
|
||||
authStateError
|
||||
)
|
||||
|
||||
func (s authState) String() string {
|
||||
switch s {
|
||||
case authStateSuccess:
|
||||
return "success"
|
||||
case authStateTimeout:
|
||||
return "timeout"
|
||||
case authStateError:
|
||||
return "error"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (s authState) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
|
@ -19,40 +19,56 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type authEntryState string
|
||||
|
||||
const (
|
||||
authEntryStateSuccess = "success"
|
||||
authEntryStateTimeout = "timeout"
|
||||
authEntryStateError = "error"
|
||||
)
|
||||
|
||||
type authEntry struct {
|
||||
State authState `json:"state"`
|
||||
Error string `json:"error"`
|
||||
Active bool `json:"active"`
|
||||
Host string `json:"host"`
|
||||
Login string `json:"login"`
|
||||
TokenSource string `json:"tokenSource"`
|
||||
Token string `json:"token"`
|
||||
Scopes string `json:"scopes"`
|
||||
GitProtocol string `json:"gitProtocol"`
|
||||
State authEntryState `json:"state"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Active bool `json:"active"`
|
||||
Host string `json:"host"`
|
||||
Login string `json:"login"`
|
||||
TokenSource string `json:"tokenSource"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Scopes string `json:"scopes,omitempty"`
|
||||
GitProtocol string `json:"gitProtocol"`
|
||||
}
|
||||
|
||||
var authFields = []string{
|
||||
"state",
|
||||
"error",
|
||||
"active",
|
||||
"host",
|
||||
"login",
|
||||
"tokenSource",
|
||||
"token",
|
||||
"scopes",
|
||||
"gitProtocol",
|
||||
type authStatus struct {
|
||||
Hosts map[string][]authEntry `json:"hosts"`
|
||||
}
|
||||
|
||||
func (e authEntry) String(cs *iostreams.ColorScheme, showToken bool) string {
|
||||
func newAuthStatus() *authStatus {
|
||||
return &authStatus{
|
||||
Hosts: make(map[string][]authEntry),
|
||||
}
|
||||
}
|
||||
|
||||
var authStatusFields = []string{
|
||||
"hosts",
|
||||
}
|
||||
|
||||
func (a authStatus) ExportData(fields []string) map[string]interface{} {
|
||||
return cmdutil.StructExportData(a, fields)
|
||||
}
|
||||
|
||||
func (e authEntry) String(cs *iostreams.ColorScheme) string {
|
||||
var sb strings.Builder
|
||||
if e.State == authStateSuccess {
|
||||
|
||||
switch e.State {
|
||||
case authEntryStateSuccess:
|
||||
sb.WriteString(
|
||||
fmt.Sprintf(" %s Logged in to %s account %s (%s)\n", cs.SuccessIcon(), e.Host, cs.Bold(e.Login), e.TokenSource),
|
||||
)
|
||||
activeStr := fmt.Sprintf("%v", e.Active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
sb.WriteString(fmt.Sprintf(" - Git operations protocol: %s\n", cs.Bold(e.GitProtocol)))
|
||||
sb.WriteString(fmt.Sprintf(" - Token: %s\n", cs.Bold(displayToken(e.Token, showToken))))
|
||||
sb.WriteString(fmt.Sprintf(" - Token: %s\n", cs.Bold(e.Token)))
|
||||
|
||||
if expectScopes(e.Token) {
|
||||
sb.WriteString(fmt.Sprintf(" - Token scopes: %s\n", cs.Bold(displayScopes(e.Scopes))))
|
||||
|
|
@ -68,18 +84,15 @@ func (e authEntry) String(cs *iostreams.ColorScheme, showToken bool) string {
|
|||
}
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
if e.Login != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s %s to %s account %s (%s)\n", cs.Red("X"), e.Error, e.Host, cs.Bold(e.Login), e.TokenSource))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(" %s %s to %s using token (%s)\n", cs.Red("X"), e.Error, e.Host, e.TokenSource))
|
||||
}
|
||||
activeStr := fmt.Sprintf("%v", e.Active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
|
||||
if e.State == authStateError {
|
||||
case authEntryStateError:
|
||||
if e.Login != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s Failed to log in to %s account %s (%s)\n", cs.Red("X"), e.Host, cs.Bold(e.Login), e.TokenSource))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(" %s Failed to log in to %s using token (%s)\n", cs.Red("X"), e.Host, e.TokenSource))
|
||||
}
|
||||
activeStr := fmt.Sprintf("%v", e.Active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
sb.WriteString(fmt.Sprintf(" - The token in %s is invalid.\n", e.TokenSource))
|
||||
if authTokenWriteable(e.TokenSource) {
|
||||
loginInstructions := fmt.Sprintf("gh auth login -h %s", e.Host)
|
||||
|
|
@ -87,12 +100,18 @@ func (e authEntry) String(cs *iostreams.ColorScheme, showToken bool) string {
|
|||
sb.WriteString(fmt.Sprintf(" - To re-authenticate, run: %s\n", cs.Bold(loginInstructions)))
|
||||
sb.WriteString(fmt.Sprintf(" - To forget about this account, run: %s\n", cs.Bold(logoutInstructions)))
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (e authEntry) ExportData(fields []string) map[string]interface{} {
|
||||
return cmdutil.StructExportData(e, fields)
|
||||
case authEntryStateTimeout:
|
||||
if e.Login != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s Timeout trying to log in to %s account %s (%s)\n", cs.Red("X"), e.Host, cs.Bold(e.Login), e.TokenSource))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(" %s Timeout trying to log in to %s using token (%s)\n", cs.Red("X"), e.Host, e.TokenSource))
|
||||
}
|
||||
activeStr := fmt.Sprintf("%v", e.Active)
|
||||
sb.WriteString(fmt.Sprintf(" - Active account: %s\n", cs.Bold(activeStr)))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type StatusOptions struct {
|
||||
|
|
@ -138,22 +157,15 @@ func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Co
|
|||
$ gh auth status --show-token
|
||||
|
||||
# Format authentication status as JSON
|
||||
$ gh auth status --json active,login,host
|
||||
$ gh auth status --json hosts
|
||||
|
||||
# Include plain text token in JSON output
|
||||
$ gh auth status --json token,login
|
||||
$ gh auth status --json hosts --show-token
|
||||
|
||||
# Format output as a flat JSON array
|
||||
$ gh auth status --json active,host,login --jq add
|
||||
# Format hosts as a flat JSON array
|
||||
$ gh auth status --json hosts --jq '.hosts | add'
|
||||
`),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := cmdutil.MutuallyExclusive(
|
||||
"`--json` and `--show-token` cannot be used together. To include the token in the JSON output, use `--json token`.",
|
||||
opts.Exporter != nil,
|
||||
opts.ShowToken,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
|
@ -167,7 +179,7 @@ func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Co
|
|||
cmd.Flags().BoolVarP(&opts.Active, "active", "a", false, "Display the active account only")
|
||||
|
||||
// the json flags are intentionally not given a shorthand to avoid conflict with -t/--show-token
|
||||
cmdutil.AddJSONFlagsWithoutShorthand(cmd, &opts.Exporter, authFields)
|
||||
cmdutil.AddJSONFlagsWithoutShorthand(cmd, &opts.Exporter, authStatusFields)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -183,20 +195,13 @@ func statusRun(opts *StatusOptions) error {
|
|||
stdout := opts.IO.Out
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
showToken := opts.ShowToken
|
||||
if opts.Exporter != nil && slices.Contains(opts.Exporter.Fields(), "token") {
|
||||
showToken = true
|
||||
}
|
||||
|
||||
statuses := make(map[string][]authEntry)
|
||||
|
||||
hostnames := authCfg.Hosts()
|
||||
if len(hostnames) == 0 {
|
||||
fmt.Fprintf(stderr,
|
||||
"You are not logged into any GitHub hosts. To log in, run: %s\n", cs.Bold("gh auth login"))
|
||||
if opts.Exporter != nil {
|
||||
// In machine-friendly mode, we always exit with no error.
|
||||
opts.Exporter.Write(opts.IO, struct{}{})
|
||||
opts.Exporter.Write(opts.IO, newAuthStatus())
|
||||
return nil
|
||||
}
|
||||
return cmdutil.SilentError
|
||||
|
|
@ -207,7 +212,7 @@ func statusRun(opts *StatusOptions) error {
|
|||
"You are not logged into any accounts on %s\n", opts.Hostname)
|
||||
if opts.Exporter != nil {
|
||||
// In machine-friendly mode, we always exit with no error.
|
||||
opts.Exporter.Write(opts.IO, struct{}{})
|
||||
opts.Exporter.Write(opts.IO, newAuthStatus())
|
||||
return nil
|
||||
}
|
||||
return cmdutil.SilentError
|
||||
|
|
@ -218,6 +223,9 @@ func statusRun(opts *StatusOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var finalErr error
|
||||
statuses := newAuthStatus()
|
||||
|
||||
for _, hostname := range hostnames {
|
||||
if opts.Hostname != "" && opts.Hostname != hostname {
|
||||
continue
|
||||
|
|
@ -233,15 +241,14 @@ func statusRun(opts *StatusOptions) error {
|
|||
active: true,
|
||||
gitProtocol: gitProtocol,
|
||||
hostname: hostname,
|
||||
showToken: showToken,
|
||||
token: activeUserToken,
|
||||
tokenSource: activeUserTokenSource,
|
||||
username: activeUser,
|
||||
})
|
||||
statuses[hostname] = append(statuses[hostname], entry)
|
||||
statuses.Hosts[hostname] = append(statuses.Hosts[hostname], entry)
|
||||
|
||||
if err == nil && !isValidEntry(entry) {
|
||||
err = cmdutil.SilentError
|
||||
if finalErr == nil && entry.State != authEntryStateSuccess {
|
||||
finalErr = cmdutil.SilentError
|
||||
}
|
||||
|
||||
if opts.Active {
|
||||
|
|
@ -258,33 +265,46 @@ func statusRun(opts *StatusOptions) error {
|
|||
active: false,
|
||||
gitProtocol: gitProtocol,
|
||||
hostname: hostname,
|
||||
showToken: showToken,
|
||||
token: token,
|
||||
tokenSource: tokenSource,
|
||||
username: username,
|
||||
})
|
||||
statuses[hostname] = append(statuses[hostname], entry)
|
||||
statuses.Hosts[hostname] = append(statuses.Hosts[hostname], entry)
|
||||
|
||||
if err == nil && !isValidEntry(entry) {
|
||||
err = cmdutil.SilentError
|
||||
if finalErr == nil && entry.State != authEntryStateSuccess {
|
||||
finalErr = cmdutil.SilentError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.ShowToken {
|
||||
for _, host := range statuses.Hosts {
|
||||
for i := range host {
|
||||
if opts.Exporter != nil {
|
||||
// In machine-readable we just drop the token
|
||||
host[i].Token = ""
|
||||
} else {
|
||||
host[i].Token = maskToken(host[i].Token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Exporter != nil {
|
||||
// In machine-friendly mode, we always exit with no error.
|
||||
opts.Exporter.Write(opts.IO, statuses)
|
||||
return nil
|
||||
}
|
||||
|
||||
prevEntry := false
|
||||
for _, hostname := range hostnames {
|
||||
entries, ok := statuses[hostname]
|
||||
entries, ok := statuses.Hosts[hostname]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
stream := stdout
|
||||
if err != nil {
|
||||
if finalErr != nil {
|
||||
stream = stderr
|
||||
}
|
||||
|
||||
|
|
@ -294,26 +314,21 @@ func statusRun(opts *StatusOptions) error {
|
|||
prevEntry = true
|
||||
fmt.Fprintf(stream, "%s\n", cs.Bold(hostname))
|
||||
for i, entry := range entries {
|
||||
fmt.Fprintf(stream, "%s", entry.String(cs, showToken))
|
||||
fmt.Fprintf(stream, "%s", entry.String(cs))
|
||||
if i < len(entries)-1 {
|
||||
fmt.Fprint(stream, "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return finalErr
|
||||
}
|
||||
|
||||
func displayToken(token string, printRaw bool) string {
|
||||
if printRaw {
|
||||
return token
|
||||
}
|
||||
|
||||
func maskToken(token string) string {
|
||||
if idx := strings.LastIndexByte(token, '_'); idx > -1 {
|
||||
prefix := token[0 : idx+1]
|
||||
return prefix + strings.Repeat("*", len(token)-len(prefix))
|
||||
}
|
||||
|
||||
return strings.Repeat("*", len(token))
|
||||
}
|
||||
|
||||
|
|
@ -336,7 +351,6 @@ type buildEntryOptions struct {
|
|||
active bool
|
||||
gitProtocol string
|
||||
hostname string
|
||||
showToken bool
|
||||
token string
|
||||
tokenSource string
|
||||
username string
|
||||
|
|
@ -367,8 +381,8 @@ func buildEntry(httpClient *http.Client, opts buildEntryOptions) authEntry {
|
|||
var err error
|
||||
entry.Login, err = api.CurrentLoginName(apiClient, opts.hostname)
|
||||
if err != nil {
|
||||
entry.State = authStateError
|
||||
entry.Error = "Failed to log in"
|
||||
entry.State = authEntryStateError
|
||||
entry.Error = err.Error()
|
||||
return entry
|
||||
}
|
||||
}
|
||||
|
|
@ -378,25 +392,21 @@ func buildEntry(httpClient *http.Client, opts buildEntryOptions) authEntry {
|
|||
if err != nil {
|
||||
var networkError net.Error
|
||||
if errors.As(err, &networkError) && networkError.Timeout() {
|
||||
entry.State = authStateTimeout
|
||||
entry.Error = "Timeout trying to log in"
|
||||
entry.State = authEntryStateTimeout
|
||||
entry.Error = err.Error()
|
||||
return entry
|
||||
}
|
||||
|
||||
entry.State = authStateError
|
||||
entry.Error = "Failed to log in"
|
||||
entry.State = authEntryStateError
|
||||
entry.Error = err.Error()
|
||||
return entry
|
||||
}
|
||||
entry.Scopes = scopesHeader
|
||||
|
||||
entry.State = authStateSuccess
|
||||
entry.State = authEntryStateSuccess
|
||||
return entry
|
||||
}
|
||||
|
||||
func authTokenWriteable(src string) bool {
|
||||
return !strings.HasSuffix(src, "_TOKEN")
|
||||
}
|
||||
|
||||
func isValidEntry(entry authEntry) bool {
|
||||
return entry.State == authStateSuccess
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,9 @@ import (
|
|||
|
||||
func Test_NewCmdStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants StatusOptions
|
||||
wantErr error
|
||||
name string
|
||||
cli string
|
||||
wants StatusOptions
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
|
|
@ -53,11 +52,6 @@ func Test_NewCmdStatus(t *testing.T) {
|
|||
Active: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "both --show-token and --json flags",
|
||||
cli: "--show-token --json state,token",
|
||||
wantErr: cmdutil.FlagErrorf("`--json` and `--show-token` cannot be used together. To include the token in the JSON output, use `--json token`."),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
@ -82,30 +76,18 @@ func Test_NewCmdStatus(t *testing.T) {
|
|||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
if tt.wantErr == nil {
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wants.Hostname, gotOpts.Hostname)
|
||||
assert.Equal(t, tt.wants.ShowToken, gotOpts.ShowToken)
|
||||
assert.Equal(t, tt.wants.Active, gotOpts.Active)
|
||||
} else {
|
||||
assert.Equal(t, tt.wantErr, err)
|
||||
}
|
||||
assert.Equal(t, tt.wants.Hostname, gotOpts.Hostname)
|
||||
assert.Equal(t, tt.wants.ShowToken, gotOpts.ShowToken)
|
||||
assert.Equal(t, tt.wants.Active, gotOpts.Active)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONFields(t *testing.T) {
|
||||
jsonfieldstest.ExpectCommandToSupportJSONFields(t, NewCmdStatus, []string{
|
||||
"state",
|
||||
"error",
|
||||
"active",
|
||||
"host",
|
||||
"login",
|
||||
"tokenSource",
|
||||
"token",
|
||||
"scopes",
|
||||
"gitProtocol",
|
||||
"hosts",
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +95,7 @@ func Test_statusRun(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
opts StatusOptions
|
||||
jsonFields []string
|
||||
env map[string]string
|
||||
httpStubs func(*httpmock.Registry)
|
||||
cfgStubs func(*testing.T, gh.Config)
|
||||
|
|
@ -556,30 +539,30 @@ func Test_statusRun(t *testing.T) {
|
|||
`),
|
||||
},
|
||||
{
|
||||
name: "No tokens with json flag",
|
||||
opts: StatusOptions{
|
||||
Exporter: defaultJsonExporter(),
|
||||
},
|
||||
wantOut: "{}\n",
|
||||
name: "json, no tokens",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
wantOut: "{\"hosts\":{}}\n",
|
||||
wantErrOut: "You are not logged into any GitHub hosts. To log in, run: gh auth login\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "No token for the given --hostname with json flag",
|
||||
name: "json, no token for given --hostname",
|
||||
opts: StatusOptions{
|
||||
Hostname: "foo.com",
|
||||
Exporter: defaultJsonExporter(),
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "gho_abc123", "https")
|
||||
},
|
||||
wantOut: "{}\n",
|
||||
wantOut: "{\"hosts\":{}}\n",
|
||||
wantErrOut: "You are not logged into any accounts on foo.com\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "All valid tokens with json flag",
|
||||
opts: StatusOptions{
|
||||
Exporter: defaultJsonExporter(),
|
||||
},
|
||||
name: "json, all valid tokens",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "gho_abc123", "https")
|
||||
login(t, c, "github.com", "monalisa2", "gho_abc123", "https")
|
||||
|
|
@ -598,23 +581,15 @@ func Test_statusRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "api/v3/"),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
|
||||
},
|
||||
wantOut: `{` +
|
||||
`"ghe.io":[` +
|
||||
`{"active":true,"host":"ghe.io","login":"monalisa-ghe","state":"success"}` +
|
||||
`],` +
|
||||
`"github.com":[` +
|
||||
`{"active":true,"host":"github.com","login":"monalisa2","state":"success"},` +
|
||||
`{"active":false,"host":"github.com","login":"monalisa","state":"success"}` +
|
||||
"]}\n",
|
||||
wantOut: `{"hosts":{"ghe.io":[{"state":"success","active":true,"host":"ghe.io","login":"monalisa-ghe","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}],"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa2","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"},{"state":"success","active":false,"host":"github.com","login":"monalisa","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "All valid tokens with hostname and json flag",
|
||||
name: "json, all valid tokens with hostname",
|
||||
opts: StatusOptions{
|
||||
Hostname: "github.com",
|
||||
Exporter: defaultJsonExporter(),
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "gho_abc123", "https")
|
||||
login(t, c, "github.com", "monalisa2", "gho_abc123", "https")
|
||||
|
|
@ -629,18 +604,14 @@ func Test_statusRun(t *testing.T) {
|
|||
httpmock.REST("GET", ""),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
},
|
||||
wantOut: `{` +
|
||||
`"github.com":[` +
|
||||
`{"active":true,"host":"github.com","login":"monalisa2","state":"success"},` +
|
||||
`{"active":false,"host":"github.com","login":"monalisa","state":"success"}` +
|
||||
"]}\n",
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa2","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"},{"state":"success","active":false,"host":"github.com","login":"monalisa","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "All valid tokens with active and json flag",
|
||||
name: "json, all valid tokens with active",
|
||||
opts: StatusOptions{
|
||||
Active: true,
|
||||
Exporter: defaultJsonExporter(),
|
||||
Active: true,
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "gho_abc123", "https")
|
||||
login(t, c, "github.com", "monalisa2", "gho_abc123", "https")
|
||||
|
|
@ -655,20 +626,13 @@ func Test_statusRun(t *testing.T) {
|
|||
httpmock.REST("GET", "api/v3/"),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
},
|
||||
wantOut: `{` +
|
||||
`"ghe.io":[` +
|
||||
`{"active":true,"host":"ghe.io","login":"monalisa-ghe","state":"success"}` +
|
||||
`],` +
|
||||
`"github.com":[` +
|
||||
`{"active":true,"host":"github.com","login":"monalisa2","state":"success"}` +
|
||||
"]}\n",
|
||||
wantOut: `{"hosts":{"ghe.io":[{"state":"success","active":true,"host":"ghe.io","login":"monalisa-ghe","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}],"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa2","tokenSource":"GH_CONFIG_DIR/hosts.yml","scopes":"repo, read:org","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "token from env with json flag",
|
||||
opts: StatusOptions{
|
||||
Exporter: defaultJsonExporter(),
|
||||
},
|
||||
env: map[string]string{"GH_TOKEN": "gho_abc123"},
|
||||
name: "json, token from env",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
env: map[string]string{"GH_TOKEN": "gho_abc123"},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", ""),
|
||||
|
|
@ -677,16 +641,12 @@ func Test_statusRun(t *testing.T) {
|
|||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"monalisa"}}}`))
|
||||
},
|
||||
wantOut: `{` +
|
||||
`"github.com":[` +
|
||||
`{"active":true,"host":"github.com","login":"monalisa","state":"success"}` +
|
||||
"]}\n",
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa","tokenSource":"GH_TOKEN","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "bad token with json flag",
|
||||
opts: StatusOptions{
|
||||
Exporter: addFieldsToExporter(defaultJsonExporter(), []string{"error"}),
|
||||
},
|
||||
name: "json, bad token",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "ghe.io", "monalisa-ghe", "gho_abc123", "https")
|
||||
},
|
||||
|
|
@ -694,28 +654,29 @@ func Test_statusRun(t *testing.T) {
|
|||
// mock for HeaderHasMinimumScopes api requests to a non-github.com host
|
||||
reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.StatusStringResponse(400, "no bueno"))
|
||||
},
|
||||
wantOut: `{"ghe.io":[{"active":true,"error":"Failed to log in","host":"ghe.io","login":"monalisa-ghe","state":"error"}]}` + "\n",
|
||||
wantOut: `{"hosts":{"ghe.io":[{"state":"error","error":"HTTP 400 (https://ghe.io/api/v3/)","active":true,"host":"ghe.io","login":"monalisa-ghe","tokenSource":"GH_CONFIG_DIR/hosts.yml","gitProtocol":"https"}]}}` + "\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "bad token from env with json flag",
|
||||
opts: StatusOptions{
|
||||
Exporter: addFieldsToExporter(defaultJsonExporter(), []string{"error"}),
|
||||
},
|
||||
env: map[string]string{"GH_TOKEN": "gho_abc123"},
|
||||
name: "json, bad token from env",
|
||||
opts: StatusOptions{},
|
||||
jsonFields: []string{"hosts"},
|
||||
env: map[string]string{"GH_TOKEN": "gho_abc123"},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
// mock for HeaderHasMinimumScopes api requests to a non-github.com host
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StatusStringResponse(400, "no bueno"))
|
||||
httpmock.StatusStringResponse(400, `no bueno`))
|
||||
},
|
||||
wantOut: `{"github.com":[{"active":true,"error":"Failed to log in","host":"github.com","login":"","state":"error"}]}` + "\n",
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"error","error":"non-200 OK status code: body: \"no bueno\"","active":true,"host":"github.com","login":"","tokenSource":"GH_TOKEN","gitProtocol":"https"}]}}` + "\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "timeout error with json flag",
|
||||
name: "json, timeout error",
|
||||
opts: StatusOptions{
|
||||
Hostname: "github.com",
|
||||
Exporter: addFieldsToExporter(defaultJsonExporter(), []string{"error"}),
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "abc123", "https")
|
||||
},
|
||||
|
|
@ -725,14 +686,16 @@ func Test_statusRun(t *testing.T) {
|
|||
return nil, context.DeadlineExceeded
|
||||
})
|
||||
},
|
||||
wantOut: `{"github.com":[{"active":true,"error":"Timeout trying to log in","host":"github.com","login":"monalisa","state":"timeout"}]}` + "\n",
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"timeout","error":"Get \"https://api.github.com/\": context deadline exceeded","active":true,"host":"github.com","login":"monalisa","tokenSource":"GH_CONFIG_DIR/hosts.yml","gitProtocol":"https"}]}}` + "\n",
|
||||
wantErr: nil, // should not return error in machine-readable mode
|
||||
},
|
||||
{
|
||||
name: "token is not masked with json flag",
|
||||
name: "json, with show token",
|
||||
opts: StatusOptions{
|
||||
Hostname: "github.com",
|
||||
Exporter: addFieldsToExporter(defaultJsonExporter(), []string{"token"}),
|
||||
Hostname: "github.com",
|
||||
ShowToken: true,
|
||||
},
|
||||
jsonFields: []string{"hosts"},
|
||||
cfgStubs: func(t *testing.T, c gh.Config) {
|
||||
login(t, c, "github.com", "monalisa", "abc123", "https")
|
||||
},
|
||||
|
|
@ -742,18 +705,18 @@ func Test_statusRun(t *testing.T) {
|
|||
httpmock.REST("GET", ""),
|
||||
httpmock.WithHeader(httpmock.ScopesResponder("repo,read:org"), "X-Oauth-Scopes", "repo, read:org"))
|
||||
},
|
||||
wantOut: `{"github.com":[{"active":true,"host":"github.com","login":"monalisa","state":"success","token":"abc123"}]}` + "\n",
|
||||
wantOut: `{"hosts":{"github.com":[{"state":"success","active":true,"host":"github.com","login":"monalisa","tokenSource":"GH_CONFIG_DIR/hosts.yml","token":"abc123","scopes":"repo, read:org","gitProtocol":"https"}]}}` + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
|
||||
ios.SetStdinTTY(true)
|
||||
ios.SetStderrTTY(true)
|
||||
ios.SetStdoutTTY(true)
|
||||
tt.opts.IO = ios
|
||||
|
||||
cfg, _ := config.NewIsolatedTestConfig(t)
|
||||
if tt.cfgStubs != nil {
|
||||
tt.cfgStubs(t, cfg)
|
||||
|
|
@ -771,6 +734,12 @@ func Test_statusRun(t *testing.T) {
|
|||
tt.httpStubs(reg)
|
||||
}
|
||||
|
||||
if tt.jsonFields != nil {
|
||||
jsonExporter := cmdutil.NewJSONExporter()
|
||||
jsonExporter.SetFields(tt.jsonFields)
|
||||
tt.opts.Exporter = jsonExporter
|
||||
}
|
||||
|
||||
for k, v := range tt.env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
|
|
@ -795,18 +764,3 @@ func login(t *testing.T, c gh.Config, hostname, username, token, protocol string
|
|||
_, err := c.Authentication().Login(hostname, username, token, protocol, false)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
type exporter interface {
|
||||
cmdutil.Exporter
|
||||
SetFields(fields []string)
|
||||
}
|
||||
|
||||
func addFieldsToExporter(e exporter, fields []string) exporter {
|
||||
newFields := append(e.Fields(), fields...)
|
||||
e.SetFields(newFields)
|
||||
return e
|
||||
}
|
||||
func defaultJsonExporter() exporter {
|
||||
return addFieldsToExporter(cmdutil.NewJSONExporter(), []string{"login", "host", "state", "active"})
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue