github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/auth/shared/login_flow_test.go (about)

     1  package shared
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/MakeNowJust/heredoc"
    11  	"github.com/ungtb10d/cli/v2/internal/prompter"
    12  	"github.com/ungtb10d/cli/v2/internal/run"
    13  	"github.com/ungtb10d/cli/v2/pkg/httpmock"
    14  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    15  	"github.com/ungtb10d/cli/v2/pkg/ssh"
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  type tinyConfig map[string]string
    20  
    21  func (c tinyConfig) Get(host, key string) (string, error) {
    22  	return c[fmt.Sprintf("%s:%s", host, key)], nil
    23  }
    24  
    25  func (c tinyConfig) Set(host string, key string, value string) {
    26  	c[fmt.Sprintf("%s:%s", host, key)] = value
    27  }
    28  
    29  func (c tinyConfig) Write() error {
    30  	return nil
    31  }
    32  
    33  func TestLogin_ssh(t *testing.T) {
    34  	dir := t.TempDir()
    35  	ios, _, stdout, stderr := iostreams.Test()
    36  
    37  	tr := httpmock.Registry{}
    38  	defer tr.Verify(t)
    39  
    40  	tr.Register(
    41  		httpmock.REST("GET", "api/v3/"),
    42  		httpmock.ScopesResponder("repo,read:org"))
    43  	tr.Register(
    44  		httpmock.GraphQL(`query UserCurrent\b`),
    45  		httpmock.StringResponse(`{"data":{"viewer":{ "login": "monalisa" }}}`))
    46  	tr.Register(
    47  		httpmock.REST("POST", "api/v3/user/keys"),
    48  		httpmock.StringResponse(`{}`))
    49  
    50  	pm := &prompter.PrompterMock{}
    51  	pm.SelectFunc = func(prompt, _ string, opts []string) (int, error) {
    52  		switch prompt {
    53  		case "What is your preferred protocol for Git operations?":
    54  			return prompter.IndexFor(opts, "SSH")
    55  		case "How would you like to authenticate GitHub CLI?":
    56  			return prompter.IndexFor(opts, "Paste an authentication token")
    57  		}
    58  		return -1, prompter.NoSuchPromptErr(prompt)
    59  	}
    60  	pm.PasswordFunc = func(_ string) (string, error) {
    61  		return "monkey", nil
    62  	}
    63  	pm.ConfirmFunc = func(prompt string, _ bool) (bool, error) {
    64  		return true, nil
    65  	}
    66  	pm.AuthTokenFunc = func() (string, error) {
    67  		return "ATOKEN", nil
    68  	}
    69  	pm.InputFunc = func(_, _ string) (string, error) {
    70  		return "Test Key", nil
    71  	}
    72  
    73  	rs, runRestore := run.Stub()
    74  	defer runRestore(t)
    75  
    76  	keyFile := filepath.Join(dir, "id_ed25519")
    77  	rs.Register(`ssh-keygen`, 0, "", func(args []string) {
    78  		expected := []string{
    79  			"ssh-keygen", "-t", "ed25519",
    80  			"-C", "",
    81  			"-N", "monkey",
    82  			"-f", keyFile,
    83  		}
    84  		assert.Equal(t, expected, args)
    85  		// simulate that the public key file has been generated
    86  		_ = os.WriteFile(keyFile+".pub", []byte("PUBKEY"), 0600)
    87  	})
    88  
    89  	cfg := tinyConfig{}
    90  
    91  	err := Login(&LoginOptions{
    92  		IO:          ios,
    93  		Config:      &cfg,
    94  		Prompter:    pm,
    95  		HTTPClient:  &http.Client{Transport: &tr},
    96  		Hostname:    "example.com",
    97  		Interactive: true,
    98  		sshContext: ssh.Context{
    99  			ConfigDir: dir,
   100  			KeygenExe: "ssh-keygen",
   101  		},
   102  	})
   103  	assert.NoError(t, err)
   104  
   105  	assert.Equal(t, "", stdout.String())
   106  	assert.Equal(t, heredoc.Docf(`
   107  		Tip: you can generate a Personal Access Token here https://example.com/settings/tokens
   108  		The minimum required scopes are 'repo', 'read:org', 'admin:public_key'.
   109  		- gh config set -h example.com git_protocol ssh
   110  		✓ Configured git protocol
   111  		✓ Uploaded the SSH key to your GitHub account: %s.pub
   112  		✓ Logged in as monalisa
   113  	`, keyFile), stderr.String())
   114  
   115  	assert.Equal(t, "monalisa", cfg["example.com:user"])
   116  	assert.Equal(t, "ATOKEN", cfg["example.com:oauth_token"])
   117  	assert.Equal(t, "ssh", cfg["example.com:git_protocol"])
   118  }
   119  
   120  func Test_scopesSentence(t *testing.T) {
   121  	type args struct {
   122  		scopes       []string
   123  		isEnterprise bool
   124  	}
   125  	tests := []struct {
   126  		name string
   127  		args args
   128  		want string
   129  	}{
   130  		{
   131  			name: "basic scopes",
   132  			args: args{
   133  				scopes:       []string{"repo", "read:org"},
   134  				isEnterprise: false,
   135  			},
   136  			want: "'repo', 'read:org'",
   137  		},
   138  		{
   139  			name: "empty",
   140  			args: args{
   141  				scopes:       []string(nil),
   142  				isEnterprise: false,
   143  			},
   144  			want: "",
   145  		},
   146  		{
   147  			name: "workflow scope for dotcom",
   148  			args: args{
   149  				scopes:       []string{"repo", "workflow"},
   150  				isEnterprise: false,
   151  			},
   152  			want: "'repo', 'workflow'",
   153  		},
   154  		{
   155  			name: "workflow scope for GHE",
   156  			args: args{
   157  				scopes:       []string{"repo", "workflow"},
   158  				isEnterprise: true,
   159  			},
   160  			want: "'repo', 'workflow' (GHE 3.0+)",
   161  		},
   162  	}
   163  	for _, tt := range tests {
   164  		t.Run(tt.name, func(t *testing.T) {
   165  			if got := scopesSentence(tt.args.scopes, tt.args.isEnterprise); got != tt.want {
   166  				t.Errorf("scopesSentence() = %q, want %q", got, tt.want)
   167  			}
   168  		})
   169  	}
   170  }