gh auth status

This commit is contained in:
vilmibm 2020-08-06 12:09:25 -05:00
parent 9f5daeca19
commit ec25b735ab
9 changed files with 492 additions and 47 deletions

View file

@ -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

View file

@ -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()

View file

@ -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
//`),
}

View file

@ -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")

View file

@ -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
}

View file

@ -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"}}}`))

View 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
}

View 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)
})
}
}

View file

@ -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,