556 lines
15 KiB
Go
556 lines
15 KiB
Go
package logout
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
"github.com/cli/cli/v2/internal/config"
|
|
"github.com/cli/cli/v2/internal/keyring"
|
|
"github.com/cli/cli/v2/internal/prompter"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/google/shlex"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func Test_NewCmdLogout(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cli string
|
|
wants LogoutOptions
|
|
tty bool
|
|
}{
|
|
{
|
|
name: "nontty no arguments",
|
|
cli: "",
|
|
wants: LogoutOptions{},
|
|
},
|
|
{
|
|
name: "tty no arguments",
|
|
tty: true,
|
|
cli: "",
|
|
wants: LogoutOptions{},
|
|
},
|
|
{
|
|
name: "tty with hostname",
|
|
tty: true,
|
|
cli: "--hostname github.com",
|
|
wants: LogoutOptions{
|
|
Hostname: "github.com",
|
|
},
|
|
},
|
|
{
|
|
name: "nontty with hostname",
|
|
cli: "--hostname github.com",
|
|
wants: LogoutOptions{
|
|
Hostname: "github.com",
|
|
},
|
|
},
|
|
{
|
|
name: "tty with user",
|
|
tty: true,
|
|
cli: "--user monalisa",
|
|
wants: LogoutOptions{
|
|
Username: "github.com",
|
|
},
|
|
},
|
|
{
|
|
name: "nontty with user",
|
|
cli: "--user monalisa",
|
|
wants: LogoutOptions{
|
|
Username: "github.com",
|
|
},
|
|
},
|
|
{
|
|
name: "tty with hostname and user",
|
|
tty: true,
|
|
cli: "--hostname github.com --user monalisa",
|
|
wants: LogoutOptions{
|
|
Hostname: "github.com",
|
|
Username: "monalisa",
|
|
},
|
|
},
|
|
{
|
|
name: "nontty with hostname and user",
|
|
cli: "--hostname github.com --user monalisa",
|
|
wants: LogoutOptions{
|
|
Hostname: "github.com",
|
|
Username: "monalisa",
|
|
},
|
|
},
|
|
}
|
|
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)
|
|
|
|
argv, err := shlex.Split(tt.cli)
|
|
require.NoError(t, err)
|
|
|
|
var gotOpts *LogoutOptions
|
|
cmd := NewCmdLogout(f, func(opts *LogoutOptions) 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()
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tt.wants.Hostname, gotOpts.Hostname)
|
|
})
|
|
}
|
|
}
|
|
|
|
type host string
|
|
type user string
|
|
|
|
type hostUsers struct {
|
|
host host
|
|
users []user
|
|
}
|
|
|
|
func Test_logoutRun_tty(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
opts *LogoutOptions
|
|
prompterStubs func(*prompter.PrompterMock)
|
|
cfgHosts []hostUsers
|
|
secureStorage bool
|
|
wantHosts string
|
|
wantErrOut *regexp.Regexp
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "logs out prompted user when multiple known hosts with one user each",
|
|
opts: &LogoutOptions{},
|
|
cfgHosts: []hostUsers{
|
|
{"ghe.io", []user{"monalisa-ghe"}},
|
|
{"github.com", []user{"monalisa"}},
|
|
},
|
|
wantHosts: "ghe.io:\n users:\n monalisa-ghe:\n oauth_token: abc123\n git_protocol: ssh\n oauth_token: abc123\n git_protocol: ssh\n user: monalisa-ghe\n",
|
|
prompterStubs: func(pm *prompter.PrompterMock) {
|
|
pm.SelectFunc = func(_, _ string, opts []string) (int, error) {
|
|
return prompter.IndexFor(opts, "monalisa (github.com)")
|
|
}
|
|
},
|
|
wantErrOut: regexp.MustCompile(`Logged out of github.com account monalisa`),
|
|
},
|
|
{
|
|
name: "logs out prompted user when multiple known hosts with multiple users each",
|
|
opts: &LogoutOptions{},
|
|
cfgHosts: []hostUsers{
|
|
{"ghe.io", []user{"monalisa-ghe", "monalisa-ghe2"}},
|
|
{"github.com", []user{"monalisa", "monalisa2"}},
|
|
},
|
|
wantHosts: "ghe.io:\n users:\n monalisa-ghe:\n oauth_token: abc123\n git_protocol: ssh\n monalisa-ghe2:\n oauth_token: abc123\n git_protocol: ssh\n git_protocol: ssh\n user: monalisa-ghe2\n oauth_token: abc123\ngithub.com:\n users:\n monalisa2:\n oauth_token: abc123\n git_protocol: ssh\n git_protocol: ssh\n user: monalisa2\n oauth_token: abc123\n",
|
|
prompterStubs: func(pm *prompter.PrompterMock) {
|
|
pm.SelectFunc = func(_, _ string, opts []string) (int, error) {
|
|
return prompter.IndexFor(opts, "monalisa (github.com)")
|
|
}
|
|
},
|
|
wantErrOut: regexp.MustCompile(`Logged out of github.com account monalisa`),
|
|
},
|
|
{
|
|
name: "logs out only logged in user",
|
|
opts: &LogoutOptions{},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa"}},
|
|
},
|
|
wantHosts: "{}\n",
|
|
wantErrOut: regexp.MustCompile(`Logged out of github.com account monalisa`),
|
|
},
|
|
{
|
|
name: "logs out prompted user when one known host with multiple users",
|
|
opts: &LogoutOptions{},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa", "monalisa2"}},
|
|
},
|
|
prompterStubs: func(pm *prompter.PrompterMock) {
|
|
pm.SelectFunc = func(_, _ string, opts []string) (int, error) {
|
|
return prompter.IndexFor(opts, "monalisa (github.com)")
|
|
}
|
|
},
|
|
wantHosts: "github.com:\n users:\n monalisa2:\n oauth_token: abc123\n git_protocol: ssh\n git_protocol: ssh\n user: monalisa2\n oauth_token: abc123\n",
|
|
wantErrOut: regexp.MustCompile(`Logged out of github.com account monalisa`),
|
|
},
|
|
{
|
|
name: "logs out specified user when multiple known hosts with one user each",
|
|
opts: &LogoutOptions{
|
|
Hostname: "ghe.io",
|
|
Username: "monalisa-ghe",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"ghe.io", []user{"monalisa-ghe"}},
|
|
{"github.com", []user{"monalisa"}},
|
|
},
|
|
wantHosts: "github.com:\n users:\n monalisa:\n oauth_token: abc123\n git_protocol: ssh\n oauth_token: abc123\n git_protocol: ssh\n user: monalisa\n",
|
|
wantErrOut: regexp.MustCompile(`Logged out of ghe.io account monalisa-ghe`),
|
|
},
|
|
{
|
|
name: "logs out specified user that is using secure storage",
|
|
secureStorage: true,
|
|
opts: &LogoutOptions{
|
|
Hostname: "github.com",
|
|
Username: "monalisa",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa"}},
|
|
},
|
|
wantHosts: "{}\n",
|
|
wantErrOut: regexp.MustCompile(`Logged out of github.com account monalisa`),
|
|
},
|
|
{
|
|
name: "errors when no known hosts",
|
|
opts: &LogoutOptions{},
|
|
wantErr: `not logged in to any hosts`,
|
|
},
|
|
{
|
|
name: "errors when specified host is not a known host",
|
|
opts: &LogoutOptions{
|
|
Hostname: "ghe.io",
|
|
Username: "monalisa-ghe",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa"}},
|
|
},
|
|
wantErr: "not logged in to ghe.io",
|
|
},
|
|
{
|
|
name: "errors when specified user is not logged in on specified host",
|
|
opts: &LogoutOptions{
|
|
Hostname: "ghe.io",
|
|
Username: "unknown-user",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"ghe.io", []user{"monalisa-ghe"}},
|
|
},
|
|
wantErr: "not logged in to ghe.io account unknown-user",
|
|
},
|
|
{
|
|
name: "errors when user is specified but doesn't exist on any host",
|
|
opts: &LogoutOptions{
|
|
Username: "unknown-user",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa"}},
|
|
{"ghe.io", []user{"monalisa"}},
|
|
},
|
|
wantErr: "no accounts matched that criteria",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
keyring.MockInit()
|
|
readConfigs := config.StubWriteConfig(t)
|
|
cfg := config.NewFromString("")
|
|
for _, hostUsers := range tt.cfgHosts {
|
|
for _, user := range hostUsers.users {
|
|
_, _ = cfg.Authentication().Login(
|
|
string(hostUsers.host),
|
|
string(user),
|
|
"abc123", "ssh", tt.secureStorage,
|
|
)
|
|
}
|
|
}
|
|
|
|
tt.opts.Config = func() (config.Config, error) {
|
|
return cfg, nil
|
|
}
|
|
|
|
ios, _, _, stderr := iostreams.Test()
|
|
ios.SetStdinTTY(true)
|
|
ios.SetStdoutTTY(true)
|
|
tt.opts.IO = ios
|
|
|
|
pm := &prompter.PrompterMock{}
|
|
if tt.prompterStubs != nil {
|
|
tt.prompterStubs(pm)
|
|
}
|
|
tt.opts.Prompter = pm
|
|
|
|
err := logoutRun(tt.opts)
|
|
if tt.wantErr != "" {
|
|
require.EqualError(t, err, tt.wantErr)
|
|
return
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
if tt.wantErrOut == nil {
|
|
require.Equal(t, "", stderr.String())
|
|
} else {
|
|
require.True(t, tt.wantErrOut.MatchString(stderr.String()), stderr.String())
|
|
}
|
|
|
|
mainBuf := bytes.Buffer{}
|
|
hostsBuf := bytes.Buffer{}
|
|
readConfigs(&mainBuf, &hostsBuf)
|
|
secureToken, _ := cfg.Authentication().TokenFromKeyring(tt.opts.Hostname)
|
|
|
|
require.Equal(t, tt.wantHosts, hostsBuf.String())
|
|
require.Equal(t, "", secureToken)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_logoutRun_nontty(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
opts *LogoutOptions
|
|
cfgHosts []hostUsers
|
|
secureStorage bool
|
|
ghtoken string
|
|
wantHosts string
|
|
wantErrOut *regexp.Regexp
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "logs out specified user when one known host",
|
|
opts: &LogoutOptions{
|
|
Hostname: "github.com",
|
|
Username: "monalisa",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa"}},
|
|
},
|
|
wantHosts: "{}\n",
|
|
wantErrOut: regexp.MustCompile(`Logged out of github.com account monalisa`),
|
|
},
|
|
{
|
|
name: "logs out specified user when multiple known hosts",
|
|
opts: &LogoutOptions{
|
|
Hostname: "github.com",
|
|
Username: "monalisa",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa"}},
|
|
{"ghe.io", []user{"monalisa-ghe"}},
|
|
},
|
|
wantHosts: "ghe.io:\n users:\n monalisa-ghe:\n oauth_token: abc123\n git_protocol: ssh\n oauth_token: abc123\n git_protocol: ssh\n user: monalisa-ghe\n",
|
|
wantErrOut: regexp.MustCompile(`Logged out of github.com account monalisa`),
|
|
},
|
|
{
|
|
name: "logs out specified user that is using secure storage",
|
|
secureStorage: true,
|
|
opts: &LogoutOptions{
|
|
Hostname: "github.com",
|
|
Username: "monalisa",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa"}},
|
|
},
|
|
wantHosts: "{}\n",
|
|
wantErrOut: regexp.MustCompile(`Logged out of github.com account monalisa`),
|
|
},
|
|
{
|
|
name: "errors when no known hosts",
|
|
opts: &LogoutOptions{
|
|
Hostname: "github.com",
|
|
Username: "monalisa",
|
|
},
|
|
wantErr: `not logged in to any hosts`,
|
|
},
|
|
{
|
|
name: "errors when specified host is not a known host",
|
|
opts: &LogoutOptions{
|
|
Hostname: "ghe.io",
|
|
Username: "monalisa-ghe",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa"}},
|
|
},
|
|
wantErr: "not logged in to ghe.io",
|
|
},
|
|
{
|
|
name: "errors when specified user is not logged in on specified host",
|
|
opts: &LogoutOptions{
|
|
Hostname: "ghe.io",
|
|
Username: "unknown-user",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"ghe.io", []user{"monalisa-ghe"}},
|
|
},
|
|
wantErr: "not logged in to ghe.io account unknown-user",
|
|
},
|
|
{
|
|
name: "errors when host is specified but user is ambiguous",
|
|
opts: &LogoutOptions{
|
|
Hostname: "ghe.io",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"ghe.io", []user{"monalisa-ghe"}},
|
|
{"ghe.io", []user{"monalisa-ghe-2"}},
|
|
},
|
|
wantErr: "unable to determine which account to log out of, please specify `--hostname` and `--user`",
|
|
},
|
|
{
|
|
name: "errors when user is specified but host is ambiguous",
|
|
opts: &LogoutOptions{
|
|
Username: "monalisa",
|
|
},
|
|
cfgHosts: []hostUsers{
|
|
{"github.com", []user{"monalisa"}},
|
|
{"ghe.io", []user{"monalisa"}},
|
|
},
|
|
wantErr: "unable to determine which account to log out of, please specify `--hostname` and `--user`",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
keyring.MockInit()
|
|
readConfigs := config.StubWriteConfig(t)
|
|
cfg := config.NewFromString("")
|
|
for _, hostUsers := range tt.cfgHosts {
|
|
for _, user := range hostUsers.users {
|
|
_, _ = cfg.Authentication().Login(
|
|
string(hostUsers.host),
|
|
string(user),
|
|
"abc123", "ssh", tt.secureStorage,
|
|
)
|
|
}
|
|
}
|
|
tt.opts.Config = func() (config.Config, error) {
|
|
return cfg, nil
|
|
}
|
|
|
|
ios, _, _, stderr := iostreams.Test()
|
|
ios.SetStdinTTY(false)
|
|
ios.SetStdoutTTY(false)
|
|
tt.opts.IO = ios
|
|
|
|
err := logoutRun(tt.opts)
|
|
if tt.wantErr != "" {
|
|
require.EqualError(t, err, tt.wantErr)
|
|
return
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
if tt.wantErrOut == nil {
|
|
require.Equal(t, "", stderr.String())
|
|
} else {
|
|
require.True(t, tt.wantErrOut.MatchString(stderr.String()), stderr.String())
|
|
}
|
|
|
|
mainBuf := bytes.Buffer{}
|
|
hostsBuf := bytes.Buffer{}
|
|
readConfigs(&mainBuf, &hostsBuf)
|
|
secureToken, _ := cfg.Authentication().TokenFromKeyring(tt.opts.Hostname)
|
|
|
|
require.Equal(t, tt.wantHosts, hostsBuf.String())
|
|
require.Equal(t, "", secureToken)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLogoutSwitchesUserNonTTY(t *testing.T) {
|
|
keyring.MockInit()
|
|
|
|
ios, _, _, stderr := iostreams.Test()
|
|
ios.SetStdinTTY(false)
|
|
ios.SetStdoutTTY(false)
|
|
|
|
readConfigs := config.StubWriteConfig(t)
|
|
cfg := config.NewFromString("")
|
|
_, err := cfg.Authentication().Login("github.com", "test-user-1", "test-token-1", "https", true)
|
|
require.NoError(t, err)
|
|
|
|
_, err = cfg.Authentication().Login("github.com", "test-user-2", "test-token-2", "ssh", true)
|
|
require.NoError(t, err)
|
|
|
|
opts := LogoutOptions{
|
|
IO: ios,
|
|
Config: func() (config.Config, error) {
|
|
return cfg, nil
|
|
},
|
|
Hostname: "github.com",
|
|
Username: "test-user-2",
|
|
}
|
|
|
|
require.NoError(t, logoutRun(&opts))
|
|
|
|
hostsBuf := bytes.Buffer{}
|
|
readConfigs(io.Discard, &hostsBuf)
|
|
|
|
secureToken, _ := cfg.Authentication().TokenFromKeyring("github.com")
|
|
require.Equal(t, "test-token-1", secureToken)
|
|
|
|
expectedHosts := heredoc.Doc(`
|
|
github.com:
|
|
users:
|
|
test-user-1:
|
|
git_protocol: https
|
|
git_protocol: https
|
|
user: test-user-1
|
|
`)
|
|
|
|
require.Equal(t, expectedHosts, hostsBuf.String())
|
|
|
|
require.Contains(t, stderr.String(), "✓ Switched active account for github.com to test-user-1")
|
|
}
|
|
|
|
func TestLogoutSwitchesUserTTY(t *testing.T) {
|
|
keyring.MockInit()
|
|
|
|
ios, _, _, stderr := iostreams.Test()
|
|
ios.SetStdinTTY(true)
|
|
ios.SetStdoutTTY(true)
|
|
|
|
readConfigs := config.StubWriteConfig(t)
|
|
cfg := config.NewFromString("")
|
|
_, err := cfg.Authentication().Login("github.com", "test-user-1", "test-token-1", "https", true)
|
|
require.NoError(t, err)
|
|
|
|
_, err = cfg.Authentication().Login("github.com", "test-user-2", "test-token-2", "ssh", true)
|
|
require.NoError(t, err)
|
|
|
|
pm := &prompter.PrompterMock{}
|
|
pm.SelectFunc = func(_, _ string, opts []string) (int, error) {
|
|
return prompter.IndexFor(opts, "test-user-2 (github.com)")
|
|
}
|
|
|
|
opts := LogoutOptions{
|
|
IO: ios,
|
|
Config: func() (config.Config, error) {
|
|
return cfg, nil
|
|
},
|
|
Prompter: pm,
|
|
}
|
|
|
|
require.NoError(t, logoutRun(&opts))
|
|
|
|
hostsBuf := bytes.Buffer{}
|
|
readConfigs(io.Discard, &hostsBuf)
|
|
|
|
secureToken, _ := cfg.Authentication().TokenFromKeyring("github.com")
|
|
require.Equal(t, "test-token-1", secureToken)
|
|
|
|
expectedHosts := heredoc.Doc(`
|
|
github.com:
|
|
users:
|
|
test-user-1:
|
|
git_protocol: https
|
|
git_protocol: https
|
|
user: test-user-1
|
|
`)
|
|
|
|
require.Equal(t, expectedHosts, hostsBuf.String())
|
|
|
|
require.Contains(t, stderr.String(), "✓ Switched active account for github.com to test-user-1")
|
|
}
|