github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/internal/config/config_file_test.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"gopkg.in/yaml.v3"
    14  )
    15  
    16  func Test_parseConfig(t *testing.T) {
    17  	defer stubConfig(`---
    18  hosts:
    19    github.com:
    20      user: monalisa
    21      oauth_token: OTOKEN
    22  `, "")()
    23  	config, err := parseConfig("config.yml")
    24  	assert.NoError(t, err)
    25  	user, err := config.Get("github.com", "user")
    26  	assert.NoError(t, err)
    27  	assert.Equal(t, "monalisa", user)
    28  	token, err := config.Get("github.com", "oauth_token")
    29  	assert.NoError(t, err)
    30  	assert.Equal(t, "OTOKEN", token)
    31  }
    32  
    33  func Test_parseConfig_multipleHosts(t *testing.T) {
    34  	defer stubConfig(`---
    35  hosts:
    36    example.com:
    37      user: wronguser
    38      oauth_token: NOTTHIS
    39    github.com:
    40      user: monalisa
    41      oauth_token: OTOKEN
    42  `, "")()
    43  	config, err := parseConfig("config.yml")
    44  	assert.NoError(t, err)
    45  	user, err := config.Get("github.com", "user")
    46  	assert.NoError(t, err)
    47  	assert.Equal(t, "monalisa", user)
    48  	token, err := config.Get("github.com", "oauth_token")
    49  	assert.NoError(t, err)
    50  	assert.Equal(t, "OTOKEN", token)
    51  }
    52  
    53  func Test_parseConfig_hostsFile(t *testing.T) {
    54  	defer stubConfig("", `---
    55  github.com:
    56    user: monalisa
    57    oauth_token: OTOKEN
    58  `)()
    59  	config, err := parseConfig("config.yml")
    60  	assert.NoError(t, err)
    61  	user, err := config.Get("github.com", "user")
    62  	assert.NoError(t, err)
    63  	assert.Equal(t, "monalisa", user)
    64  	token, err := config.Get("github.com", "oauth_token")
    65  	assert.NoError(t, err)
    66  	assert.Equal(t, "OTOKEN", token)
    67  }
    68  
    69  func Test_parseConfig_hostFallback(t *testing.T) {
    70  	defer stubConfig(`---
    71  git_protocol: ssh
    72  `, `---
    73  github.com:
    74      user: monalisa
    75      oauth_token: OTOKEN
    76  example.com:
    77      user: wronguser
    78      oauth_token: NOTTHIS
    79      git_protocol: https
    80  `)()
    81  	config, err := parseConfig("config.yml")
    82  	assert.NoError(t, err)
    83  	val, err := config.Get("example.com", "git_protocol")
    84  	assert.NoError(t, err)
    85  	assert.Equal(t, "https", val)
    86  	val, err = config.Get("github.com", "git_protocol")
    87  	assert.NoError(t, err)
    88  	assert.Equal(t, "ssh", val)
    89  	val, err = config.Get("nonexistent.io", "git_protocol")
    90  	assert.NoError(t, err)
    91  	assert.Equal(t, "ssh", val)
    92  }
    93  
    94  func Test_parseConfig_migrateConfig(t *testing.T) {
    95  	defer stubConfig(`---
    96  github.com:
    97    - user: keiyuri
    98      oauth_token: 123456
    99  `, "")()
   100  
   101  	mainBuf := bytes.Buffer{}
   102  	hostsBuf := bytes.Buffer{}
   103  	defer StubWriteConfig(&mainBuf, &hostsBuf)()
   104  	defer StubBackupConfig()()
   105  
   106  	_, err := parseConfig("config.yml")
   107  	assert.NoError(t, err)
   108  
   109  	expectedHosts := `github.com:
   110      user: keiyuri
   111      oauth_token: "123456"
   112  `
   113  
   114  	assert.Equal(t, expectedHosts, hostsBuf.String())
   115  	assert.NotContains(t, mainBuf.String(), "github.com")
   116  	assert.NotContains(t, mainBuf.String(), "oauth_token")
   117  }
   118  
   119  func Test_parseConfigFile(t *testing.T) {
   120  	tests := []struct {
   121  		contents string
   122  		wantsErr bool
   123  	}{
   124  		{
   125  			contents: "",
   126  			wantsErr: true,
   127  		},
   128  		{
   129  			contents: " ",
   130  			wantsErr: false,
   131  		},
   132  		{
   133  			contents: "\n",
   134  			wantsErr: false,
   135  		},
   136  	}
   137  
   138  	for _, tt := range tests {
   139  		t.Run(fmt.Sprintf("contents: %q", tt.contents), func(t *testing.T) {
   140  			defer stubConfig(tt.contents, "")()
   141  			_, yamlRoot, err := parseConfigFile("config.yml")
   142  			if tt.wantsErr != (err != nil) {
   143  				t.Fatalf("got error: %v", err)
   144  			}
   145  			if tt.wantsErr {
   146  				return
   147  			}
   148  			assert.Equal(t, yaml.MappingNode, yamlRoot.Content[0].Kind)
   149  			assert.Equal(t, 0, len(yamlRoot.Content[0].Content))
   150  		})
   151  	}
   152  }
   153  
   154  func Test_ConfigDir(t *testing.T) {
   155  	tempDir := t.TempDir()
   156  
   157  	tests := []struct {
   158  		name        string
   159  		onlyWindows bool
   160  		env         map[string]string
   161  		output      string
   162  	}{
   163  		{
   164  			name: "HOME/USERPROFILE specified",
   165  			env: map[string]string{
   166  				"GH_CONFIG_DIR":   "",
   167  				"XDG_CONFIG_HOME": "",
   168  				"AppData":         "",
   169  				"USERPROFILE":     tempDir,
   170  				"HOME":            tempDir,
   171  			},
   172  			output: filepath.Join(tempDir, ".config", "gh"),
   173  		},
   174  		{
   175  			name: "GH_CONFIG_DIR specified",
   176  			env: map[string]string{
   177  				"GH_CONFIG_DIR": filepath.Join(tempDir, "gh_config_dir"),
   178  			},
   179  			output: filepath.Join(tempDir, "gh_config_dir"),
   180  		},
   181  		{
   182  			name: "XDG_CONFIG_HOME specified",
   183  			env: map[string]string{
   184  				"XDG_CONFIG_HOME": tempDir,
   185  			},
   186  			output: filepath.Join(tempDir, "gh"),
   187  		},
   188  		{
   189  			name: "GH_CONFIG_DIR and XDG_CONFIG_HOME specified",
   190  			env: map[string]string{
   191  				"GH_CONFIG_DIR":   filepath.Join(tempDir, "gh_config_dir"),
   192  				"XDG_CONFIG_HOME": tempDir,
   193  			},
   194  			output: filepath.Join(tempDir, "gh_config_dir"),
   195  		},
   196  		{
   197  			name:        "AppData specified",
   198  			onlyWindows: true,
   199  			env: map[string]string{
   200  				"AppData": tempDir,
   201  			},
   202  			output: filepath.Join(tempDir, "GitHub CLI"),
   203  		},
   204  		{
   205  			name:        "GH_CONFIG_DIR and AppData specified",
   206  			onlyWindows: true,
   207  			env: map[string]string{
   208  				"GH_CONFIG_DIR": filepath.Join(tempDir, "gh_config_dir"),
   209  				"AppData":       tempDir,
   210  			},
   211  			output: filepath.Join(tempDir, "gh_config_dir"),
   212  		},
   213  		{
   214  			name:        "XDG_CONFIG_HOME and AppData specified",
   215  			onlyWindows: true,
   216  			env: map[string]string{
   217  				"XDG_CONFIG_HOME": tempDir,
   218  				"AppData":         tempDir,
   219  			},
   220  			output: filepath.Join(tempDir, "gh"),
   221  		},
   222  	}
   223  
   224  	for _, tt := range tests {
   225  		if tt.onlyWindows && runtime.GOOS != "windows" {
   226  			continue
   227  		}
   228  		t.Run(tt.name, func(t *testing.T) {
   229  			if tt.env != nil {
   230  				for k, v := range tt.env {
   231  					old := os.Getenv(k)
   232  					os.Setenv(k, v)
   233  					defer os.Setenv(k, old)
   234  				}
   235  			}
   236  
   237  			// Create directory to skip auto migration code
   238  			// which gets run when target directory does not exist
   239  			_ = os.MkdirAll(tt.output, 0755)
   240  
   241  			assert.Equal(t, tt.output, ConfigDir())
   242  		})
   243  	}
   244  }
   245  
   246  func Test_configFile_Write_toDisk(t *testing.T) {
   247  	configDir := filepath.Join(t.TempDir(), ".config", "gh")
   248  	_ = os.MkdirAll(configDir, 0755)
   249  	os.Setenv(GH_CONFIG_DIR, configDir)
   250  	defer os.Unsetenv(GH_CONFIG_DIR)
   251  
   252  	cfg := NewFromString(`pager: less`)
   253  	err := cfg.Write()
   254  	if err != nil {
   255  		t.Fatal(err)
   256  	}
   257  
   258  	expectedConfig := "pager: less\n"
   259  	if configBytes, err := ioutil.ReadFile(filepath.Join(configDir, "config.yml")); err != nil {
   260  		t.Error(err)
   261  	} else if string(configBytes) != expectedConfig {
   262  		t.Errorf("expected config.yml %q, got %q", expectedConfig, string(configBytes))
   263  	}
   264  
   265  	if configBytes, err := ioutil.ReadFile(filepath.Join(configDir, "hosts.yml")); err != nil {
   266  		t.Error(err)
   267  	} else if string(configBytes) != "" {
   268  		t.Errorf("unexpected hosts.yml: %q", string(configBytes))
   269  	}
   270  }
   271  
   272  func Test_autoMigrateConfigDir_noMigration_notExist(t *testing.T) {
   273  	homeDir := t.TempDir()
   274  	migrateDir := t.TempDir()
   275  
   276  	homeEnvVar := "HOME"
   277  	if runtime.GOOS == "windows" {
   278  		homeEnvVar = "USERPROFILE"
   279  	}
   280  	old := os.Getenv(homeEnvVar)
   281  	os.Setenv(homeEnvVar, homeDir)
   282  	defer os.Setenv(homeEnvVar, old)
   283  
   284  	err := autoMigrateConfigDir(migrateDir)
   285  	assert.Equal(t, errNotExist, err)
   286  
   287  	files, err := ioutil.ReadDir(migrateDir)
   288  	assert.NoError(t, err)
   289  	assert.Equal(t, 0, len(files))
   290  }
   291  
   292  func Test_autoMigrateConfigDir_noMigration_samePath(t *testing.T) {
   293  	homeDir := t.TempDir()
   294  	migrateDir := filepath.Join(homeDir, ".config", "gh")
   295  	err := os.MkdirAll(migrateDir, 0755)
   296  	assert.NoError(t, err)
   297  
   298  	homeEnvVar := "HOME"
   299  	if runtime.GOOS == "windows" {
   300  		homeEnvVar = "USERPROFILE"
   301  	}
   302  	old := os.Getenv(homeEnvVar)
   303  	os.Setenv(homeEnvVar, homeDir)
   304  	defer os.Setenv(homeEnvVar, old)
   305  
   306  	err = autoMigrateConfigDir(migrateDir)
   307  	assert.Equal(t, errSamePath, err)
   308  
   309  	files, err := ioutil.ReadDir(migrateDir)
   310  	assert.NoError(t, err)
   311  	assert.Equal(t, 0, len(files))
   312  }
   313  
   314  func Test_autoMigrateConfigDir_migration(t *testing.T) {
   315  	homeDir := t.TempDir()
   316  	migrateDir := t.TempDir()
   317  	homeConfigDir := filepath.Join(homeDir, ".config", "gh")
   318  	migrateConfigDir := filepath.Join(migrateDir, ".config", "gh")
   319  
   320  	homeEnvVar := "HOME"
   321  	if runtime.GOOS == "windows" {
   322  		homeEnvVar = "USERPROFILE"
   323  	}
   324  	old := os.Getenv(homeEnvVar)
   325  	os.Setenv(homeEnvVar, homeDir)
   326  	defer os.Setenv(homeEnvVar, old)
   327  
   328  	err := os.MkdirAll(homeConfigDir, 0755)
   329  	assert.NoError(t, err)
   330  	f, err := ioutil.TempFile(homeConfigDir, "")
   331  	assert.NoError(t, err)
   332  	f.Close()
   333  
   334  	err = autoMigrateConfigDir(migrateConfigDir)
   335  	assert.NoError(t, err)
   336  
   337  	_, err = ioutil.ReadDir(homeConfigDir)
   338  	assert.True(t, os.IsNotExist(err))
   339  
   340  	files, err := ioutil.ReadDir(migrateConfigDir)
   341  	assert.NoError(t, err)
   342  	assert.Equal(t, 1, len(files))
   343  }
   344  
   345  func Test_StateDir(t *testing.T) {
   346  	tempDir := t.TempDir()
   347  
   348  	tests := []struct {
   349  		name        string
   350  		onlyWindows bool
   351  		env         map[string]string
   352  		output      string
   353  	}{
   354  		{
   355  			name: "HOME/USERPROFILE specified",
   356  			env: map[string]string{
   357  				"XDG_STATE_HOME":  "",
   358  				"GH_CONFIG_DIR":   "",
   359  				"XDG_CONFIG_HOME": "",
   360  				"LocalAppData":    "",
   361  				"USERPROFILE":     tempDir,
   362  				"HOME":            tempDir,
   363  			},
   364  			output: filepath.Join(tempDir, ".local", "state", "gh"),
   365  		},
   366  		{
   367  			name: "XDG_STATE_HOME specified",
   368  			env: map[string]string{
   369  				"XDG_STATE_HOME": tempDir,
   370  			},
   371  			output: filepath.Join(tempDir, "gh"),
   372  		},
   373  		{
   374  			name:        "LocalAppData specified",
   375  			onlyWindows: true,
   376  			env: map[string]string{
   377  				"LocalAppData": tempDir,
   378  			},
   379  			output: filepath.Join(tempDir, "GitHub CLI"),
   380  		},
   381  		{
   382  			name:        "XDG_STATE_HOME and LocalAppData specified",
   383  			onlyWindows: true,
   384  			env: map[string]string{
   385  				"XDG_STATE_HOME": tempDir,
   386  				"LocalAppData":   tempDir,
   387  			},
   388  			output: filepath.Join(tempDir, "gh"),
   389  		},
   390  	}
   391  
   392  	for _, tt := range tests {
   393  		if tt.onlyWindows && runtime.GOOS != "windows" {
   394  			continue
   395  		}
   396  		t.Run(tt.name, func(t *testing.T) {
   397  			if tt.env != nil {
   398  				for k, v := range tt.env {
   399  					old := os.Getenv(k)
   400  					os.Setenv(k, v)
   401  					defer os.Setenv(k, old)
   402  				}
   403  			}
   404  
   405  			// Create directory to skip auto migration code
   406  			// which gets run when target directory does not exist
   407  			_ = os.MkdirAll(tt.output, 0755)
   408  
   409  			assert.Equal(t, tt.output, StateDir())
   410  		})
   411  	}
   412  }
   413  
   414  func Test_autoMigrateStateDir_noMigration_notExist(t *testing.T) {
   415  	homeDir := t.TempDir()
   416  	migrateDir := t.TempDir()
   417  
   418  	homeEnvVar := "HOME"
   419  	if runtime.GOOS == "windows" {
   420  		homeEnvVar = "USERPROFILE"
   421  	}
   422  	old := os.Getenv(homeEnvVar)
   423  	os.Setenv(homeEnvVar, homeDir)
   424  	defer os.Setenv(homeEnvVar, old)
   425  
   426  	err := autoMigrateStateDir(migrateDir)
   427  	assert.Equal(t, errNotExist, err)
   428  
   429  	files, err := ioutil.ReadDir(migrateDir)
   430  	assert.NoError(t, err)
   431  	assert.Equal(t, 0, len(files))
   432  }
   433  
   434  func Test_autoMigrateStateDir_noMigration_samePath(t *testing.T) {
   435  	homeDir := t.TempDir()
   436  	migrateDir := filepath.Join(homeDir, ".config", "gh")
   437  	err := os.MkdirAll(migrateDir, 0755)
   438  	assert.NoError(t, err)
   439  
   440  	homeEnvVar := "HOME"
   441  	if runtime.GOOS == "windows" {
   442  		homeEnvVar = "USERPROFILE"
   443  	}
   444  	old := os.Getenv(homeEnvVar)
   445  	os.Setenv(homeEnvVar, homeDir)
   446  	defer os.Setenv(homeEnvVar, old)
   447  
   448  	err = autoMigrateStateDir(migrateDir)
   449  	assert.Equal(t, errSamePath, err)
   450  
   451  	files, err := ioutil.ReadDir(migrateDir)
   452  	assert.NoError(t, err)
   453  	assert.Equal(t, 0, len(files))
   454  }
   455  
   456  func Test_autoMigrateStateDir_migration(t *testing.T) {
   457  	homeDir := t.TempDir()
   458  	migrateDir := t.TempDir()
   459  	homeConfigDir := filepath.Join(homeDir, ".config", "gh")
   460  	migrateStateDir := filepath.Join(migrateDir, ".local", "state", "gh")
   461  
   462  	homeEnvVar := "HOME"
   463  	if runtime.GOOS == "windows" {
   464  		homeEnvVar = "USERPROFILE"
   465  	}
   466  	old := os.Getenv(homeEnvVar)
   467  	os.Setenv(homeEnvVar, homeDir)
   468  	defer os.Setenv(homeEnvVar, old)
   469  
   470  	err := os.MkdirAll(homeConfigDir, 0755)
   471  	assert.NoError(t, err)
   472  	err = ioutil.WriteFile(filepath.Join(homeConfigDir, "state.yml"), nil, 0755)
   473  	assert.NoError(t, err)
   474  
   475  	err = autoMigrateStateDir(migrateStateDir)
   476  	assert.NoError(t, err)
   477  
   478  	files, err := ioutil.ReadDir(homeConfigDir)
   479  	assert.NoError(t, err)
   480  	assert.Equal(t, 0, len(files))
   481  
   482  	files, err = ioutil.ReadDir(migrateStateDir)
   483  	assert.NoError(t, err)
   484  	assert.Equal(t, 1, len(files))
   485  	assert.Equal(t, "state.yml", files[0].Name())
   486  }
   487  
   488  func Test_DataDir(t *testing.T) {
   489  	tempDir := t.TempDir()
   490  
   491  	tests := []struct {
   492  		name        string
   493  		onlyWindows bool
   494  		env         map[string]string
   495  		output      string
   496  	}{
   497  		{
   498  			name: "HOME/USERPROFILE specified",
   499  			env: map[string]string{
   500  				"XDG_DATA_HOME":   "",
   501  				"GH_CONFIG_DIR":   "",
   502  				"XDG_CONFIG_HOME": "",
   503  				"LocalAppData":    "",
   504  				"USERPROFILE":     tempDir,
   505  				"HOME":            tempDir,
   506  			},
   507  			output: filepath.Join(tempDir, ".local", "share", "gh"),
   508  		},
   509  		{
   510  			name: "XDG_DATA_HOME specified",
   511  			env: map[string]string{
   512  				"XDG_DATA_HOME": tempDir,
   513  			},
   514  			output: filepath.Join(tempDir, "gh"),
   515  		},
   516  		{
   517  			name:        "LocalAppData specified",
   518  			onlyWindows: true,
   519  			env: map[string]string{
   520  				"LocalAppData": tempDir,
   521  			},
   522  			output: filepath.Join(tempDir, "GitHub CLI"),
   523  		},
   524  		{
   525  			name:        "XDG_DATA_HOME and LocalAppData specified",
   526  			onlyWindows: true,
   527  			env: map[string]string{
   528  				"XDG_DATA_HOME": tempDir,
   529  				"LocalAppData":  tempDir,
   530  			},
   531  			output: filepath.Join(tempDir, "gh"),
   532  		},
   533  	}
   534  
   535  	for _, tt := range tests {
   536  		if tt.onlyWindows && runtime.GOOS != "windows" {
   537  			continue
   538  		}
   539  		t.Run(tt.name, func(t *testing.T) {
   540  			if tt.env != nil {
   541  				for k, v := range tt.env {
   542  					old := os.Getenv(k)
   543  					os.Setenv(k, v)
   544  					defer os.Setenv(k, old)
   545  				}
   546  			}
   547  
   548  			assert.Equal(t, tt.output, DataDir())
   549  		})
   550  	}
   551  }