Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/gen-docs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func run(args []string) error {
IOStreams: ios,
Browser: &browser{},
Config: func() (gh.Config, error) {
return config.NewFromString(""), nil
return config.NewMockConfigFromString(""), nil
},
ExtensionManager: &em{},
}, &telemetry.NoOpService{}, "", "")
Expand Down
4 changes: 4 additions & 0 deletions git/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func TestClientAuthenticatedCommand(t *testing.T) {
}

func TestClientRemotes(t *testing.T) {
IsolateConfig(t)
tempDir := t.TempDir()
initRepo(t, tempDir)
gitDir := filepath.Join(tempDir, ".git")
Expand Down Expand Up @@ -149,6 +150,7 @@ func TestClientRemotes(t *testing.T) {
}

func TestClientRemotes_no_resolved_remote(t *testing.T) {
IsolateConfig(t)
tempDir := t.TempDir()
initRepo(t, tempDir)
gitDir := filepath.Join(tempDir, ".git")
Expand Down Expand Up @@ -708,6 +710,7 @@ func createCommitsCommandContext(t *testing.T, testData stubbedCommitsCommandDat
}

func TestClientLastCommit(t *testing.T) {
IsolateConfig(t)
client := Client{
RepoDir: "./fixtures/simple.git",
}
Expand All @@ -718,6 +721,7 @@ func TestClientLastCommit(t *testing.T) {
}

func TestClientCommitBody(t *testing.T) {
IsolateConfig(t)
client := Client{
RepoDir: "./fixtures/simple.git",
}
Expand Down
25 changes: 25 additions & 0 deletions git/test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package git

import (
"path/filepath"
"testing"
)

// IsolateConfig prevents the ambient git configuration from reaching tests that shell
// out to real git.
//
// https://git-scm.com/docs/git-config#ENVIRONMENT
func IsolateConfig(t *testing.T) {
Comment thread
BagToad marked this conversation as resolved.
t.Helper()

// Point the global config at an empty file and ignore the system one.
t.Setenv("GIT_CONFIG_GLOBAL", filepath.Join(t.TempDir(), ".gitconfig"))
t.Setenv("GIT_CONFIG_NOSYSTEM", "true")

// Config from these vars is command line scope, which outranks the global and
// system files, so redirecting those files alone leaves it in place. Tools that
// wrap git inject config this way, and an inherited safe.bareRepository=explicit
// makes git refuse to open a bare repository at all.
t.Setenv("GIT_CONFIG_COUNT", "")
t.Setenv("GIT_CONFIG_PARAMETERS", "")
}
2 changes: 1 addition & 1 deletion internal/config/auth_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

// Note that NewIsolatedTestConfig sets up a Mock keyring as well
func newTestAuthConfig(t *testing.T) *AuthConfig {
cfg, _ := NewIsolatedTestConfig(t)
cfg, _ := NewIsolatedTestConfig(t, "")
return &AuthConfig{cfg: cfg.cfg}
}

Expand Down
57 changes: 46 additions & 11 deletions internal/config/stub.go → internal/config/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,23 @@ import (
ghConfig "github.com/cli/go-gh/v2/pkg/config"
)

func NewBlankConfig() *ghmock.ConfigMock {
return NewFromString(defaultConfigStr)
// NewMockConfig returns a mock config populated with gh's default config file.
// See NewMockConfigFromString for when to prefer a mock over NewIsolatedTestConfig.
func NewMockConfig() *ghmock.ConfigMock {
return NewMockConfigFromString(defaultConfigStr)
}

func NewFromString(cfgStr string) *ghmock.ConfigMock {
c := ghConfig.ReadFromString(cfgStr)
// NewMockConfigFromString returns a mock config populated from cfgString, for tests
// that need to stub config behaviour by assigning to the mock's function fields.
//
// The mock answers host, token, and default host lookups from cfgString alone, so it
// ignores both the config files on disk and the environment. It never writes anything.
//
// Prefer NewIsolatedTestConfig when the code under test exercises the real config
// implementation, writes config, or reads the auth environment variables directly,
// since none of those go through the mock.
func NewMockConfigFromString(cfgString string) *ghmock.ConfigMock {
c := ghConfig.ReadFromString(cfgString)
cfg := cfg{c}
mock := &ghmock.ConfigMock{}
mock.GetOrDefaultFunc = func(host, key string) o.Option[gh.ConfigEntry] {
Expand Down Expand Up @@ -97,15 +108,39 @@ func NewFromString(cfgStr string) *ghmock.ConfigMock {
return mock
}

// NewIsolatedTestConfig sets up a Mock keyring, creates a blank config
// overwrites the ghConfig.Read function that returns a singleton config
// in the real implementation, sets the GH_CONFIG_DIR env var so that
// any call to Write goes to a different location on disk, and then returns
// the blank config and a function that reads any data written to disk.
func NewIsolatedTestConfig(t *testing.T) (*cfg, func(io.Writer, io.Writer)) {
// NewIsolatedTestConfig returns the real config implementation, built from cfgString
// and isolated from the machine running the tests. Pass "" for a config with no
// content. It also returns a function that reads back anything written to disk.
//
// Use it when the code under test exercises real config behaviour: writing config,
// logging in and out, or reading the auth environment variables directly. Prefer
// NewMockConfigFromString when the test only needs to stub config lookups.
//
// Isolation covers all three places config comes from. It mocks the keyring, replaces
// the ghConfig.Read singleton so each test gets its own config, points GH_CONFIG_DIR at
// a temp dir so writes stay off the real config, and clears the environment variables
// that go-gh consults for authentication and host resolution.
//
// Callers that want one of the auth env vars set should set it after calling this,
// otherwise the value is cleared along with the ambient environment.
func NewIsolatedTestConfig(t *testing.T, cfgString string) (*cfg, func(io.Writer, io.Writer)) {
keyring.MockInit()

c := ghConfig.ReadFromString("")
// go-gh reads these ahead of any stored config, so isolating the config file is
// not enough on its own. A developer with GH_TOKEN exported, or any CI image that
// provides one, would otherwise see an authenticated config here and fail tests
// that assert on the logged out state.
for _, key := range []string{
"GH_TOKEN",
"GITHUB_TOKEN",
"GH_ENTERPRISE_TOKEN",
"GITHUB_ENTERPRISE_TOKEN",
"GH_HOST",
} {
t.Setenv(key, "")
}

c := ghConfig.ReadFromString(cfgString)
cfg := cfg{c}

// The real implementation of config.Read uses a sync.Once
Expand Down
106 changes: 52 additions & 54 deletions internal/ghcmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func Test_newIOStreams_pager(t *testing.T) {
if tt.config != nil {
cfg = tt.config
} else {
cfg = config.NewBlankConfig()
cfg = config.NewMockConfig()
}
io := newIOStreams(cfg)
assert.Equal(t, tt.wantPager, io.GetPager())
Expand Down Expand Up @@ -183,7 +183,7 @@ func Test_newIOStreams_prompt(t *testing.T) {
if tt.config != nil {
cfg = tt.config
} else {
cfg = config.NewBlankConfig()
cfg = config.NewMockConfig()
}
io := newIOStreams(cfg)
assert.Equal(t, tt.promptDisabled, io.GetNeverPrompt())
Expand Down Expand Up @@ -259,7 +259,7 @@ func Test_newIOStreams_spinnerDisabled(t *testing.T) {
if tt.config != nil {
cfg = tt.config
} else {
cfg = config.NewBlankConfig()
cfg = config.NewMockConfig()
}
io := newIOStreams(cfg)
assert.Equal(t, tt.spinnerDisabled, io.GetSpinnerDisabled())
Expand Down Expand Up @@ -325,7 +325,7 @@ func Test_newIOStreams_accessiblePrompterEnabled(t *testing.T) {
if tt.config != nil {
cfg = tt.config
} else {
cfg = config.NewBlankConfig()
cfg = config.NewMockConfig()
}
io := newIOStreams(cfg)
assert.Equal(t, tt.accessiblePrompterEnabled, io.AccessiblePrompterEnabled())
Expand Down Expand Up @@ -401,7 +401,7 @@ func Test_newIOStreams_colorLabels(t *testing.T) {
if tt.config != nil {
cfg = tt.config
} else {
cfg = config.NewBlankConfig()
cfg = config.NewMockConfig()
}
io := newIOStreams(cfg)
assert.Equal(t, tt.colorLabelsEnabled, io.ColorLabels())
Expand All @@ -411,109 +411,107 @@ func Test_newIOStreams_colorLabels(t *testing.T) {

func Test_mightBeGHESUser(t *testing.T) {
tests := []struct {
name string
env map[string]string
config gh.Config
want bool
name string
env map[string]string
cfgString string
want bool
}{
{
name: "GH_ENTERPRISE_TOKEN set",
env: map[string]string{"GH_ENTERPRISE_TOKEN": "some-token"},
config: config.NewBlankConfig(),
want: true,
name: "GH_ENTERPRISE_TOKEN set",
env: map[string]string{"GH_ENTERPRISE_TOKEN": "some-token"},
want: true,
},
{
name: "GITHUB_ENTERPRISE_TOKEN set",
env: map[string]string{"GITHUB_ENTERPRISE_TOKEN": "some-token"},
config: config.NewBlankConfig(),
want: true,
name: "GITHUB_ENTERPRISE_TOKEN set",
env: map[string]string{"GITHUB_ENTERPRISE_TOKEN": "some-token"},
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 enterprise host",
cfgString: "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 only github.com",
cfgString: "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 no hosts",
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 github.com and enterprise host",
cfgString: "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: "no env vars, config has tenancy host",
cfgString: "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 enterprise host",
env: map[string]string{"GH_HOST": "ghes.example.com"},
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 github.com",
env: map[string]string{"GH_HOST": "github.com"},
want: false,
},
{
name: "GH_HOST set to tenancy host",
env: map[string]string{"GH_HOST": "my-company.ghe.com"},
config: config.NewBlankConfig(),
want: false,
name: "GH_HOST set to tenancy host",
env: map[string]string{"GH_HOST": "my-company.ghe.com"},
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg, _ := config.NewIsolatedTestConfig(t, tt.cfgString)

// Set after isolating the config, which clears the auth env vars.
for k, v := range tt.env {
t.Setenv(k, v)
}
got := mightBeGHESUser(tt.config)

got := mightBeGHESUser(cfg)
assert.Equal(t, tt.want, got)
})
}
}

func pagerConfig() gh.Config {
return config.NewFromString("pager: CONFIG_PAGER")
return config.NewMockConfigFromString("pager: CONFIG_PAGER")
}

func disablePromptConfig() gh.Config {
return config.NewFromString("prompt: disabled")
return config.NewMockConfigFromString("prompt: disabled")
}

func enableAccessiblePrompterConfig() gh.Config {
return config.NewFromString("accessible_prompter: enabled")
return config.NewMockConfigFromString("accessible_prompter: enabled")
}

func disableAccessiblePrompterConfig() gh.Config {
return config.NewFromString("accessible_prompter: disabled")
return config.NewMockConfigFromString("accessible_prompter: disabled")
}

func disableSpinnersConfig() gh.Config {
return config.NewFromString("spinner: disabled")
return config.NewMockConfigFromString("spinner: disabled")
}

func enableSpinnersConfig() gh.Config {
return config.NewFromString("spinner: enabled")
return config.NewMockConfigFromString("spinner: enabled")
}

func disableColorLabelsConfig() gh.Config {
return config.NewFromString("color_labels: disabled")
return config.NewMockConfigFromString("color_labels: disabled")
}

func enableColorLabelsConfig() gh.Config {
return config.NewFromString("color_labels: enabled")
return config.NewMockConfigFromString("color_labels: enabled")
}

func Test_authRecoveryCommand(t *testing.T) {
Expand Down Expand Up @@ -555,7 +553,7 @@ func Test_authRecoveryCommand(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
authCfg := config.NewBlankConfig().Authentication()
authCfg := config.NewMockConfig().Authentication()
authCfg.SetActiveToken(tt.token, tt.source)
cfg := &ghmock.ConfigMock{
AuthenticationFunc: func() gh.AuthConfig {
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/agent-task/agent_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// setupMockOAuthConfig configures a blank config with a default host and optional token behavior.
func setupMockOAuthConfig(t *testing.T, tokenSource string) gh.Config {
t.Helper()
c := config.NewBlankConfig()
c := config.NewMockConfig()
switch tokenSource {
case "oauth_token":
// valid OAuth device flow token stored in config
Expand Down Expand Up @@ -67,7 +67,7 @@ func TestNewCmdAgentTask(t *testing.T) {
{
name: "github.com oauth is accepted and enterprise token ignored",
customConfig: func() (gh.Config, error) {
c := config.NewBlankConfig()
c := config.NewMockConfig()
c.Set("something.ghes.com", "oauth_token", "ghe_ENTERPRISE123")
c.Set("github.com", "oauth_token", "gho_OAUTH123")
return c, nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/alias/delete/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func TestDeleteRun(t *testing.T) {
ios.SetStderrTTY(tt.isTTY)
tt.opts.IO = ios

cfg := config.NewFromString(tt.config)
cfg := config.NewMockConfigFromString(tt.config)
cfg.WriteFunc = func() error {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/alias/imports/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ func TestImportRun(t *testing.T) {
tt.opts.IO = ios

readConfigs := config.StubWriteConfig(t)
cfg := config.NewFromString(tt.initConfig)
cfg := config.NewMockConfigFromString(tt.initConfig)
tt.opts.Config = func() (gh.Config, error) {
return cfg, nil
}
Expand Down
Loading