588 lines
15 KiB
Go
588 lines
15 KiB
Go
package ghcmd
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/cli/cli/v2/api"
|
|
"github.com/cli/cli/v2/internal/config"
|
|
"github.com/cli/cli/v2/internal/gh"
|
|
ghmock "github.com/cli/cli/v2/internal/gh/mock"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
ghAPI "github.com/cli/go-gh/v2/pkg/api"
|
|
"github.com/spf13/cobra"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func Test_printError(t *testing.T) {
|
|
cmd := &cobra.Command{}
|
|
|
|
type args struct {
|
|
err error
|
|
cmd *cobra.Command
|
|
debug bool
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantOut string
|
|
}{
|
|
{
|
|
name: "generic error",
|
|
args: args{
|
|
err: errors.New("the app exploded"),
|
|
cmd: nil,
|
|
debug: false,
|
|
},
|
|
wantOut: "the app exploded\n",
|
|
},
|
|
{
|
|
name: "DNS error",
|
|
args: args{
|
|
err: fmt.Errorf("DNS oopsie: %w", &net.DNSError{
|
|
Name: "api.github.com",
|
|
}),
|
|
cmd: nil,
|
|
debug: false,
|
|
},
|
|
wantOut: `error connecting to api.github.com
|
|
check your internet connection or https://githubstatus.com
|
|
`,
|
|
},
|
|
{
|
|
name: "Cobra flag error",
|
|
args: args{
|
|
err: cmdutil.FlagErrorf("unknown flag --foo"),
|
|
cmd: cmd,
|
|
debug: false,
|
|
},
|
|
wantOut: "unknown flag --foo\n\nUsage:\n\n",
|
|
},
|
|
{
|
|
name: "unknown Cobra command error",
|
|
args: args{
|
|
err: errors.New("unknown command foo"),
|
|
cmd: cmd,
|
|
debug: false,
|
|
},
|
|
wantOut: "unknown command foo\n\nUsage:\n\n",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
out := &bytes.Buffer{}
|
|
printError(out, tt.args.err, tt.args.cmd, tt.args.debug)
|
|
if gotOut := out.String(); gotOut != tt.wantOut {
|
|
t.Errorf("printError() = %q, want %q", gotOut, tt.wantOut)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_newIOStreams_pager(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
env map[string]string
|
|
config gh.Config
|
|
wantPager string
|
|
}{
|
|
{
|
|
name: "GH_PAGER and PAGER set",
|
|
env: map[string]string{
|
|
"GH_PAGER": "GH_PAGER",
|
|
"PAGER": "PAGER",
|
|
},
|
|
wantPager: "GH_PAGER",
|
|
},
|
|
{
|
|
name: "GH_PAGER and config pager set",
|
|
env: map[string]string{
|
|
"GH_PAGER": "GH_PAGER",
|
|
},
|
|
config: pagerConfig(),
|
|
wantPager: "GH_PAGER",
|
|
},
|
|
{
|
|
name: "config pager and PAGER set",
|
|
env: map[string]string{
|
|
"PAGER": "PAGER",
|
|
},
|
|
config: pagerConfig(),
|
|
wantPager: "CONFIG_PAGER",
|
|
},
|
|
{
|
|
name: "only PAGER set",
|
|
env: map[string]string{
|
|
"PAGER": "PAGER",
|
|
},
|
|
wantPager: "PAGER",
|
|
},
|
|
{
|
|
name: "GH_PAGER set to blank string",
|
|
env: map[string]string{
|
|
"GH_PAGER": "",
|
|
"PAGER": "PAGER",
|
|
},
|
|
wantPager: "",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.env != nil {
|
|
for k, v := range tt.env {
|
|
t.Setenv(k, v)
|
|
}
|
|
}
|
|
var cfg gh.Config
|
|
if tt.config != nil {
|
|
cfg = tt.config
|
|
} else {
|
|
cfg = config.NewBlankConfig()
|
|
}
|
|
io := newIOStreams(cfg)
|
|
assert.Equal(t, tt.wantPager, io.GetPager())
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_newIOStreams_prompt(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config gh.Config
|
|
promptDisabled bool
|
|
env map[string]string
|
|
}{
|
|
{
|
|
name: "default config",
|
|
promptDisabled: false,
|
|
},
|
|
{
|
|
name: "config with prompt disabled",
|
|
config: disablePromptConfig(),
|
|
promptDisabled: true,
|
|
},
|
|
{
|
|
name: "prompt disabled via GH_PROMPT_DISABLED env var",
|
|
env: map[string]string{"GH_PROMPT_DISABLED": "1"},
|
|
promptDisabled: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.env != nil {
|
|
for k, v := range tt.env {
|
|
t.Setenv(k, v)
|
|
}
|
|
}
|
|
var cfg gh.Config
|
|
if tt.config != nil {
|
|
cfg = tt.config
|
|
} else {
|
|
cfg = config.NewBlankConfig()
|
|
}
|
|
io := newIOStreams(cfg)
|
|
assert.Equal(t, tt.promptDisabled, io.GetNeverPrompt())
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_newIOStreams_spinnerDisabled(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config gh.Config
|
|
spinnerDisabled bool
|
|
env map[string]string
|
|
}{
|
|
{
|
|
name: "default config",
|
|
spinnerDisabled: false,
|
|
},
|
|
{
|
|
name: "config with spinner disabled",
|
|
config: disableSpinnersConfig(),
|
|
spinnerDisabled: true,
|
|
},
|
|
{
|
|
name: "config with spinner enabled",
|
|
config: enableSpinnersConfig(),
|
|
spinnerDisabled: false,
|
|
},
|
|
{
|
|
name: "spinner disabled via GH_SPINNER_DISABLED env var = 0",
|
|
env: map[string]string{"GH_SPINNER_DISABLED": "0"},
|
|
spinnerDisabled: false,
|
|
},
|
|
{
|
|
name: "spinner disabled via GH_SPINNER_DISABLED env var = false",
|
|
env: map[string]string{"GH_SPINNER_DISABLED": "false"},
|
|
spinnerDisabled: false,
|
|
},
|
|
{
|
|
name: "spinner disabled via GH_SPINNER_DISABLED env var = no",
|
|
env: map[string]string{"GH_SPINNER_DISABLED": "no"},
|
|
spinnerDisabled: false,
|
|
},
|
|
{
|
|
name: "spinner enabled via GH_SPINNER_DISABLED env var = 1",
|
|
env: map[string]string{"GH_SPINNER_DISABLED": "1"},
|
|
spinnerDisabled: true,
|
|
},
|
|
{
|
|
name: "spinner enabled via GH_SPINNER_DISABLED env var = true",
|
|
env: map[string]string{"GH_SPINNER_DISABLED": "true"},
|
|
spinnerDisabled: true,
|
|
},
|
|
{
|
|
name: "config enabled but env disabled, respects env",
|
|
config: enableSpinnersConfig(),
|
|
env: map[string]string{"GH_SPINNER_DISABLED": "true"},
|
|
spinnerDisabled: true,
|
|
},
|
|
{
|
|
name: "config disabled but env enabled, respects env",
|
|
config: disableSpinnersConfig(),
|
|
env: map[string]string{"GH_SPINNER_DISABLED": "false"},
|
|
spinnerDisabled: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
for k, v := range tt.env {
|
|
t.Setenv(k, v)
|
|
}
|
|
var cfg gh.Config
|
|
if tt.config != nil {
|
|
cfg = tt.config
|
|
} else {
|
|
cfg = config.NewBlankConfig()
|
|
}
|
|
io := newIOStreams(cfg)
|
|
assert.Equal(t, tt.spinnerDisabled, io.GetSpinnerDisabled())
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_newIOStreams_accessiblePrompterEnabled(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config gh.Config
|
|
accessiblePrompterEnabled bool
|
|
env map[string]string
|
|
}{
|
|
{
|
|
name: "default config",
|
|
accessiblePrompterEnabled: false,
|
|
},
|
|
{
|
|
name: "config with accessible prompter enabled",
|
|
config: enableAccessiblePrompterConfig(),
|
|
accessiblePrompterEnabled: true,
|
|
},
|
|
{
|
|
name: "config with accessible prompter disabled",
|
|
config: disableAccessiblePrompterConfig(),
|
|
accessiblePrompterEnabled: false,
|
|
},
|
|
{
|
|
name: "accessible prompter enabled via GH_ACCESSIBLE_PROMPTER env var = 1",
|
|
env: map[string]string{"GH_ACCESSIBLE_PROMPTER": "1"},
|
|
accessiblePrompterEnabled: true,
|
|
},
|
|
{
|
|
name: "accessible prompter enabled via GH_ACCESSIBLE_PROMPTER env var = true",
|
|
env: map[string]string{"GH_ACCESSIBLE_PROMPTER": "true"},
|
|
accessiblePrompterEnabled: true,
|
|
},
|
|
{
|
|
name: "accessible prompter disabled via GH_ACCESSIBLE_PROMPTER env var = 0",
|
|
env: map[string]string{"GH_ACCESSIBLE_PROMPTER": "0"},
|
|
accessiblePrompterEnabled: false,
|
|
},
|
|
{
|
|
name: "config disabled but env enabled, respects env",
|
|
config: disableAccessiblePrompterConfig(),
|
|
env: map[string]string{"GH_ACCESSIBLE_PROMPTER": "true"},
|
|
accessiblePrompterEnabled: true,
|
|
},
|
|
{
|
|
name: "config enabled but env disabled, respects env",
|
|
config: enableAccessiblePrompterConfig(),
|
|
env: map[string]string{"GH_ACCESSIBLE_PROMPTER": "false"},
|
|
accessiblePrompterEnabled: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
for k, v := range tt.env {
|
|
t.Setenv(k, v)
|
|
}
|
|
var cfg gh.Config
|
|
if tt.config != nil {
|
|
cfg = tt.config
|
|
} else {
|
|
cfg = config.NewBlankConfig()
|
|
}
|
|
io := newIOStreams(cfg)
|
|
assert.Equal(t, tt.accessiblePrompterEnabled, io.AccessiblePrompterEnabled())
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_newIOStreams_colorLabels(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config gh.Config
|
|
colorLabelsEnabled bool
|
|
env map[string]string
|
|
}{
|
|
{
|
|
name: "default config",
|
|
colorLabelsEnabled: false,
|
|
},
|
|
{
|
|
name: "config with colorLabels enabled",
|
|
config: enableColorLabelsConfig(),
|
|
colorLabelsEnabled: true,
|
|
},
|
|
{
|
|
name: "config with colorLabels disabled",
|
|
config: disableColorLabelsConfig(),
|
|
colorLabelsEnabled: false,
|
|
},
|
|
{
|
|
name: "colorLabels enabled via `1` in GH_COLOR_LABELS env var",
|
|
env: map[string]string{"GH_COLOR_LABELS": "1"},
|
|
colorLabelsEnabled: true,
|
|
},
|
|
{
|
|
name: "colorLabels enabled via `true` in GH_COLOR_LABELS env var",
|
|
env: map[string]string{"GH_COLOR_LABELS": "true"},
|
|
colorLabelsEnabled: true,
|
|
},
|
|
{
|
|
name: "colorLabels enabled via `yes` in GH_COLOR_LABELS env var",
|
|
env: map[string]string{"GH_COLOR_LABELS": "yes"},
|
|
colorLabelsEnabled: true,
|
|
},
|
|
{
|
|
name: "colorLabels disable via empty string in GH_COLOR_LABELS env var",
|
|
env: map[string]string{"GH_COLOR_LABELS": ""},
|
|
colorLabelsEnabled: false,
|
|
},
|
|
{
|
|
name: "colorLabels disabled via `0` in GH_COLOR_LABELS env var",
|
|
env: map[string]string{"GH_COLOR_LABELS": "0"},
|
|
colorLabelsEnabled: false,
|
|
},
|
|
{
|
|
name: "colorLabels disabled via `false` in GH_COLOR_LABELS env var",
|
|
env: map[string]string{"GH_COLOR_LABELS": "false"},
|
|
colorLabelsEnabled: false,
|
|
},
|
|
{
|
|
name: "colorLabels disabled via `no` in GH_COLOR_LABELS env var",
|
|
env: map[string]string{"GH_COLOR_LABELS": "no"},
|
|
colorLabelsEnabled: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.env != nil {
|
|
for k, v := range tt.env {
|
|
t.Setenv(k, v)
|
|
}
|
|
}
|
|
var cfg gh.Config
|
|
if tt.config != nil {
|
|
cfg = tt.config
|
|
} else {
|
|
cfg = config.NewBlankConfig()
|
|
}
|
|
io := newIOStreams(cfg)
|
|
assert.Equal(t, tt.colorLabelsEnabled, io.ColorLabels())
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_mightBeGHESUser(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
env map[string]string
|
|
config gh.Config
|
|
want bool
|
|
}{
|
|
{
|
|
name: "GH_ENTERPRISE_TOKEN set",
|
|
env: map[string]string{"GH_ENTERPRISE_TOKEN": "some-token"},
|
|
config: config.NewBlankConfig(),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "GITHUB_ENTERPRISE_TOKEN set",
|
|
env: map[string]string{"GITHUB_ENTERPRISE_TOKEN": "some-token"},
|
|
config: config.NewBlankConfig(),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "no env vars, config has enterprise host",
|
|
config: config.NewFromString("hosts:\n ghes.example.com:\n oauth_token: abc123\n"),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "no env vars, config has only github.com",
|
|
config: config.NewFromString("hosts:\n github.com:\n oauth_token: abc123\n"),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "no env vars, config has no hosts",
|
|
config: config.NewBlankConfig(),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "no env vars, config has github.com and enterprise host",
|
|
config: config.NewFromString("hosts:\n github.com:\n oauth_token: abc123\n ghes.example.com:\n oauth_token: def456\n"),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "no env vars, config has tenancy host",
|
|
config: config.NewFromString("hosts:\n my-company.ghe.com:\n oauth_token: abc123\n"),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "GH_HOST set to enterprise host",
|
|
env: map[string]string{"GH_HOST": "ghes.example.com"},
|
|
config: config.NewBlankConfig(),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "GH_HOST set to github.com",
|
|
env: map[string]string{"GH_HOST": "github.com"},
|
|
config: config.NewBlankConfig(),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "GH_HOST set to tenancy host",
|
|
env: map[string]string{"GH_HOST": "my-company.ghe.com"},
|
|
config: config.NewBlankConfig(),
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
for k, v := range tt.env {
|
|
t.Setenv(k, v)
|
|
}
|
|
got := mightBeGHESUser(tt.config)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func pagerConfig() gh.Config {
|
|
return config.NewFromString("pager: CONFIG_PAGER")
|
|
}
|
|
|
|
func disablePromptConfig() gh.Config {
|
|
return config.NewFromString("prompt: disabled")
|
|
}
|
|
|
|
func enableAccessiblePrompterConfig() gh.Config {
|
|
return config.NewFromString("accessible_prompter: enabled")
|
|
}
|
|
|
|
func disableAccessiblePrompterConfig() gh.Config {
|
|
return config.NewFromString("accessible_prompter: disabled")
|
|
}
|
|
|
|
func disableSpinnersConfig() gh.Config {
|
|
return config.NewFromString("spinner: disabled")
|
|
}
|
|
|
|
func enableSpinnersConfig() gh.Config {
|
|
return config.NewFromString("spinner: enabled")
|
|
}
|
|
|
|
func disableColorLabelsConfig() gh.Config {
|
|
return config.NewFromString("color_labels: disabled")
|
|
}
|
|
|
|
func enableColorLabelsConfig() gh.Config {
|
|
return config.NewFromString("color_labels: enabled")
|
|
}
|
|
|
|
func Test_authRecoveryCommand(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
token string
|
|
source string
|
|
requestURL string
|
|
want string
|
|
}{
|
|
{
|
|
name: "stored oauth token",
|
|
token: "gho_abc123",
|
|
source: "oauth_token",
|
|
requestURL: "https://api.github.com/graphql",
|
|
want: "gh auth refresh -h github.com",
|
|
},
|
|
{
|
|
name: "stored pat",
|
|
token: "github_pat_abc123",
|
|
source: "oauth_token",
|
|
requestURL: "https://api.github.com/graphql",
|
|
want: "gh auth login -h github.com",
|
|
},
|
|
{
|
|
name: "env token",
|
|
token: "gho_abc123",
|
|
source: "GH_TOKEN",
|
|
requestURL: "https://api.github.com/graphql",
|
|
want: "gh auth login -h github.com",
|
|
},
|
|
{
|
|
name: "missing request url",
|
|
token: "gho_abc123",
|
|
source: "oauth_token",
|
|
want: "gh auth login",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
authCfg := config.NewBlankConfig().Authentication()
|
|
authCfg.SetActiveToken(tt.token, tt.source)
|
|
cfg := &ghmock.ConfigMock{
|
|
AuthenticationFunc: func() gh.AuthConfig {
|
|
return authCfg
|
|
},
|
|
}
|
|
|
|
var requestURL *url.URL
|
|
if tt.requestURL != "" {
|
|
var err error
|
|
requestURL, err = url.Parse(tt.requestURL)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse request URL: %v", err)
|
|
}
|
|
}
|
|
|
|
httpErr := api.HTTPError{
|
|
HTTPError: &ghAPI.HTTPError{
|
|
RequestURL: requestURL,
|
|
StatusCode: 401,
|
|
},
|
|
}
|
|
|
|
got := authRecoveryCommand(cfg, httpErr)
|
|
if got != tt.want {
|
|
t.Errorf("authRecoveryCommand() = %q, want %q", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|