github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/config/config_test.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/docker/cli/cli/config/configfile"
    14  	"github.com/docker/cli/cli/config/credentials"
    15  	"github.com/docker/cli/cli/config/types"
    16  	"gotest.tools/v3/assert"
    17  	is "gotest.tools/v3/assert/cmp"
    18  	"gotest.tools/v3/env"
    19  	"gotest.tools/v3/fs"
    20  )
    21  
    22  var homeKey = "HOME"
    23  
    24  func init() {
    25  	if runtime.GOOS == "windows" {
    26  		homeKey = "USERPROFILE"
    27  	}
    28  }
    29  
    30  func setupConfigDir(t *testing.T) (string, func()) {
    31  	tmpdir, err := ioutil.TempDir("", "config-test")
    32  	assert.NilError(t, err)
    33  	oldDir := Dir()
    34  	SetDir(tmpdir)
    35  
    36  	return tmpdir, func() {
    37  		SetDir(oldDir)
    38  		os.RemoveAll(tmpdir)
    39  	}
    40  }
    41  
    42  func TestEmptyConfigDir(t *testing.T) {
    43  	tmpHome, cleanup := setupConfigDir(t)
    44  	defer cleanup()
    45  
    46  	config, err := Load("")
    47  	assert.NilError(t, err)
    48  
    49  	expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName)
    50  	assert.Check(t, is.Equal(expectedConfigFilename, config.Filename))
    51  
    52  	// Now save it and make sure it shows up in new form
    53  	saveConfigAndValidateNewFormat(t, config, tmpHome)
    54  }
    55  
    56  func TestMissingFile(t *testing.T) {
    57  	tmpHome, err := ioutil.TempDir("", "config-test")
    58  	assert.NilError(t, err)
    59  	defer os.RemoveAll(tmpHome)
    60  
    61  	config, err := Load(tmpHome)
    62  	assert.NilError(t, err)
    63  
    64  	// Now save it and make sure it shows up in new form
    65  	saveConfigAndValidateNewFormat(t, config, tmpHome)
    66  }
    67  
    68  func TestSaveFileToDirs(t *testing.T) {
    69  	tmpHome, err := ioutil.TempDir("", "config-test")
    70  	assert.NilError(t, err)
    71  	defer os.RemoveAll(tmpHome)
    72  
    73  	tmpHome += "/.docker"
    74  
    75  	config, err := Load(tmpHome)
    76  	assert.NilError(t, err)
    77  
    78  	// Now save it and make sure it shows up in new form
    79  	saveConfigAndValidateNewFormat(t, config, tmpHome)
    80  }
    81  
    82  func TestEmptyFile(t *testing.T) {
    83  	tmpHome, err := ioutil.TempDir("", "config-test")
    84  	assert.NilError(t, err)
    85  	defer os.RemoveAll(tmpHome)
    86  
    87  	fn := filepath.Join(tmpHome, ConfigFileName)
    88  	err = ioutil.WriteFile(fn, []byte(""), 0600)
    89  	assert.NilError(t, err)
    90  
    91  	_, err = Load(tmpHome)
    92  	assert.NilError(t, err)
    93  }
    94  
    95  func TestEmptyJSON(t *testing.T) {
    96  	tmpHome, err := ioutil.TempDir("", "config-test")
    97  	assert.NilError(t, err)
    98  	defer os.RemoveAll(tmpHome)
    99  
   100  	fn := filepath.Join(tmpHome, ConfigFileName)
   101  	err = ioutil.WriteFile(fn, []byte("{}"), 0600)
   102  	assert.NilError(t, err)
   103  
   104  	config, err := Load(tmpHome)
   105  	assert.NilError(t, err)
   106  
   107  	// Now save it and make sure it shows up in new form
   108  	saveConfigAndValidateNewFormat(t, config, tmpHome)
   109  }
   110  
   111  func TestOldInvalidsAuth(t *testing.T) {
   112  	invalids := map[string]string{
   113  		`username = test`: "The Auth config file is empty",
   114  		`username
   115  password`: "Invalid Auth config file",
   116  		`username = test
   117  email`: "Invalid auth configuration file",
   118  	}
   119  
   120  	resetHomeDir()
   121  	tmpHome, err := ioutil.TempDir("", "config-test")
   122  	assert.NilError(t, err)
   123  	defer os.RemoveAll(tmpHome)
   124  	defer env.Patch(t, homeKey, tmpHome)()
   125  
   126  	for content, expectedError := range invalids {
   127  		fn := filepath.Join(tmpHome, oldConfigfile)
   128  		err := ioutil.WriteFile(fn, []byte(content), 0600)
   129  		assert.NilError(t, err)
   130  
   131  		_, err = Load(tmpHome)
   132  		assert.ErrorContains(t, err, expectedError)
   133  	}
   134  }
   135  
   136  func TestOldValidAuth(t *testing.T) {
   137  	resetHomeDir()
   138  	tmpHome, err := ioutil.TempDir("", "config-test")
   139  	assert.NilError(t, err)
   140  	defer os.RemoveAll(tmpHome)
   141  	defer env.Patch(t, homeKey, tmpHome)()
   142  
   143  	fn := filepath.Join(tmpHome, oldConfigfile)
   144  	js := `username = am9lam9lOmhlbGxv
   145  	email = user@example.com`
   146  	err = ioutil.WriteFile(fn, []byte(js), 0600)
   147  	assert.NilError(t, err)
   148  
   149  	config, err := Load(tmpHome)
   150  	assert.NilError(t, err)
   151  
   152  	// defaultIndexserver is https://index.docker.io/v1/
   153  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   154  	assert.Equal(t, ac.Username, "joejoe")
   155  	assert.Equal(t, ac.Password, "hello")
   156  
   157  	// Now save it and make sure it shows up in new form
   158  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   159  
   160  	expConfStr := `{
   161  	"auths": {
   162  		"https://index.docker.io/v1/": {
   163  			"auth": "am9lam9lOmhlbGxv"
   164  		}
   165  	}
   166  }`
   167  
   168  	assert.Check(t, is.Equal(expConfStr, configStr))
   169  }
   170  
   171  func TestOldJSONInvalid(t *testing.T) {
   172  	resetHomeDir()
   173  	tmpHome, err := ioutil.TempDir("", "config-test")
   174  	assert.NilError(t, err)
   175  	defer os.RemoveAll(tmpHome)
   176  	defer env.Patch(t, homeKey, tmpHome)()
   177  
   178  	fn := filepath.Join(tmpHome, oldConfigfile)
   179  	js := `{"https://index.docker.io/v1/":{"auth":"test","email":"user@example.com"}}`
   180  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   181  		t.Fatal(err)
   182  	}
   183  
   184  	config, err := Load(tmpHome)
   185  	// Use Contains instead of == since the file name will change each time
   186  	if err == nil || !strings.Contains(err.Error(), "Invalid auth configuration file") {
   187  		t.Fatalf("Expected an error got : %v, %v", config, err)
   188  	}
   189  }
   190  
   191  func TestOldJSON(t *testing.T) {
   192  	resetHomeDir()
   193  	tmpHome, err := ioutil.TempDir("", "config-test")
   194  	assert.NilError(t, err)
   195  	defer os.RemoveAll(tmpHome)
   196  	defer env.Patch(t, homeKey, tmpHome)()
   197  
   198  	fn := filepath.Join(tmpHome, oldConfigfile)
   199  	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
   200  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   201  		t.Fatal(err)
   202  	}
   203  
   204  	config, err := Load(tmpHome)
   205  	assert.NilError(t, err)
   206  
   207  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   208  	assert.Equal(t, ac.Username, "joejoe")
   209  	assert.Equal(t, ac.Password, "hello")
   210  
   211  	// Now save it and make sure it shows up in new form
   212  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   213  
   214  	expConfStr := `{
   215  	"auths": {
   216  		"https://index.docker.io/v1/": {
   217  			"auth": "am9lam9lOmhlbGxv",
   218  			"email": "user@example.com"
   219  		}
   220  	}
   221  }`
   222  
   223  	if configStr != expConfStr {
   224  		t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
   225  	}
   226  }
   227  
   228  func TestOldJSONFallbackDeprecationWarning(t *testing.T) {
   229  	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
   230  	tmpHome := fs.NewDir(t, t.Name(), fs.WithFile(oldConfigfile, js))
   231  	defer tmpHome.Remove()
   232  	defer env.PatchAll(t, map[string]string{homeKey: tmpHome.Path(), "DOCKER_CONFIG": ""})()
   233  
   234  	// reset the homeDir, configDir, and its sync.Once, to force them being resolved again
   235  	resetHomeDir()
   236  	resetConfigDir()
   237  
   238  	buffer := new(bytes.Buffer)
   239  	configFile := LoadDefaultConfigFile(buffer)
   240  	expected := configfile.New(tmpHome.Join(configFileDir, ConfigFileName))
   241  	expected.AuthConfigs = map[string]types.AuthConfig{
   242  		"https://index.docker.io/v1/": {
   243  			Username:      "joejoe",
   244  			Password:      "hello",
   245  			Email:         "user@example.com",
   246  			ServerAddress: "https://index.docker.io/v1/",
   247  		},
   248  	}
   249  	assert.Assert(t, strings.Contains(buffer.String(), "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format is deprecated and will be removed in an upcoming release"))
   250  	assert.Check(t, is.DeepEqual(expected, configFile))
   251  }
   252  
   253  func TestNewJSON(t *testing.T) {
   254  	tmpHome, err := ioutil.TempDir("", "config-test")
   255  	assert.NilError(t, err)
   256  	defer os.RemoveAll(tmpHome)
   257  
   258  	fn := filepath.Join(tmpHome, ConfigFileName)
   259  	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
   260  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   261  		t.Fatal(err)
   262  	}
   263  
   264  	config, err := Load(tmpHome)
   265  	assert.NilError(t, err)
   266  
   267  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   268  	assert.Equal(t, ac.Username, "joejoe")
   269  	assert.Equal(t, ac.Password, "hello")
   270  
   271  	// Now save it and make sure it shows up in new form
   272  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   273  
   274  	expConfStr := `{
   275  	"auths": {
   276  		"https://index.docker.io/v1/": {
   277  			"auth": "am9lam9lOmhlbGxv"
   278  		}
   279  	}
   280  }`
   281  
   282  	if configStr != expConfStr {
   283  		t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
   284  	}
   285  }
   286  
   287  func TestNewJSONNoEmail(t *testing.T) {
   288  	tmpHome, err := ioutil.TempDir("", "config-test")
   289  	assert.NilError(t, err)
   290  	defer os.RemoveAll(tmpHome)
   291  
   292  	fn := filepath.Join(tmpHome, ConfigFileName)
   293  	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
   294  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   295  		t.Fatal(err)
   296  	}
   297  
   298  	config, err := Load(tmpHome)
   299  	assert.NilError(t, err)
   300  
   301  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   302  	assert.Equal(t, ac.Username, "joejoe")
   303  	assert.Equal(t, ac.Password, "hello")
   304  
   305  	// Now save it and make sure it shows up in new form
   306  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   307  
   308  	expConfStr := `{
   309  	"auths": {
   310  		"https://index.docker.io/v1/": {
   311  			"auth": "am9lam9lOmhlbGxv"
   312  		}
   313  	}
   314  }`
   315  
   316  	if configStr != expConfStr {
   317  		t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
   318  	}
   319  }
   320  
   321  func TestJSONWithPsFormat(t *testing.T) {
   322  	tmpHome, err := ioutil.TempDir("", "config-test")
   323  	assert.NilError(t, err)
   324  	defer os.RemoveAll(tmpHome)
   325  
   326  	fn := filepath.Join(tmpHome, ConfigFileName)
   327  	js := `{
   328  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   329  		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   330  }`
   331  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   332  		t.Fatal(err)
   333  	}
   334  
   335  	config, err := Load(tmpHome)
   336  	assert.NilError(t, err)
   337  
   338  	if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
   339  		t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
   340  	}
   341  
   342  	// Now save it and make sure it shows up in new form
   343  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   344  	if !strings.Contains(configStr, `"psFormat":`) ||
   345  		!strings.Contains(configStr, "{{.ID}}") {
   346  		t.Fatalf("Should have save in new form: %s", configStr)
   347  	}
   348  }
   349  
   350  func TestJSONWithCredentialStore(t *testing.T) {
   351  	tmpHome, err := ioutil.TempDir("", "config-test")
   352  	assert.NilError(t, err)
   353  	defer os.RemoveAll(tmpHome)
   354  
   355  	fn := filepath.Join(tmpHome, ConfigFileName)
   356  	js := `{
   357  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   358  		"credsStore": "crazy-secure-storage"
   359  }`
   360  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   361  		t.Fatal(err)
   362  	}
   363  
   364  	config, err := Load(tmpHome)
   365  	assert.NilError(t, err)
   366  
   367  	if config.CredentialsStore != "crazy-secure-storage" {
   368  		t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore)
   369  	}
   370  
   371  	// Now save it and make sure it shows up in new form
   372  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   373  	if !strings.Contains(configStr, `"credsStore":`) ||
   374  		!strings.Contains(configStr, "crazy-secure-storage") {
   375  		t.Fatalf("Should have save in new form: %s", configStr)
   376  	}
   377  }
   378  
   379  func TestJSONWithCredentialHelpers(t *testing.T) {
   380  	tmpHome, err := ioutil.TempDir("", "config-test")
   381  	assert.NilError(t, err)
   382  	defer os.RemoveAll(tmpHome)
   383  
   384  	fn := filepath.Join(tmpHome, ConfigFileName)
   385  	js := `{
   386  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   387  		"credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" }
   388  }`
   389  	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
   390  		t.Fatal(err)
   391  	}
   392  
   393  	config, err := Load(tmpHome)
   394  	assert.NilError(t, err)
   395  
   396  	if config.CredentialHelpers == nil {
   397  		t.Fatal("config.CredentialHelpers was nil")
   398  	} else if config.CredentialHelpers["images.io"] != "images-io" ||
   399  		config.CredentialHelpers["containers.com"] != "crazy-secure-storage" {
   400  		t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers)
   401  	}
   402  
   403  	// Now save it and make sure it shows up in new form
   404  	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
   405  	if !strings.Contains(configStr, `"credHelpers":`) ||
   406  		!strings.Contains(configStr, "images.io") ||
   407  		!strings.Contains(configStr, "images-io") ||
   408  		!strings.Contains(configStr, "containers.com") ||
   409  		!strings.Contains(configStr, "crazy-secure-storage") {
   410  		t.Fatalf("Should have save in new form: %s", configStr)
   411  	}
   412  }
   413  
   414  // Save it and make sure it shows up in new form
   415  func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, configDir string) string {
   416  	t.Helper()
   417  	assert.NilError(t, config.Save())
   418  
   419  	buf, err := ioutil.ReadFile(filepath.Join(configDir, ConfigFileName))
   420  	assert.NilError(t, err)
   421  	assert.Check(t, is.Contains(string(buf), `"auths":`))
   422  	return string(buf)
   423  }
   424  
   425  func TestConfigDir(t *testing.T) {
   426  	tmpHome, err := ioutil.TempDir("", "config-test")
   427  	assert.NilError(t, err)
   428  	defer os.RemoveAll(tmpHome)
   429  
   430  	if Dir() == tmpHome {
   431  		t.Fatalf("Expected ConfigDir to be different than %s by default, but was the same", tmpHome)
   432  	}
   433  
   434  	// Update configDir
   435  	SetDir(tmpHome)
   436  
   437  	if Dir() != tmpHome {
   438  		t.Fatalf("Expected ConfigDir to %s, but was %s", tmpHome, Dir())
   439  	}
   440  }
   441  
   442  func TestJSONReaderNoFile(t *testing.T) {
   443  	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
   444  
   445  	config, err := LoadFromReader(strings.NewReader(js))
   446  	assert.NilError(t, err)
   447  
   448  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   449  	assert.Equal(t, ac.Username, "joejoe")
   450  	assert.Equal(t, ac.Password, "hello")
   451  }
   452  
   453  func TestOldJSONReaderNoFile(t *testing.T) {
   454  	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
   455  
   456  	config, err := LegacyLoadFromReader(strings.NewReader(js))
   457  	assert.NilError(t, err)
   458  
   459  	ac := config.AuthConfigs["https://index.docker.io/v1/"]
   460  	assert.Equal(t, ac.Username, "joejoe")
   461  	assert.Equal(t, ac.Password, "hello")
   462  }
   463  
   464  func TestJSONWithPsFormatNoFile(t *testing.T) {
   465  	js := `{
   466  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
   467  		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   468  }`
   469  	config, err := LoadFromReader(strings.NewReader(js))
   470  	assert.NilError(t, err)
   471  
   472  	if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
   473  		t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
   474  	}
   475  }
   476  
   477  func TestJSONSaveWithNoFile(t *testing.T) {
   478  	js := `{
   479  		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
   480  		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   481  }`
   482  	config, err := LoadFromReader(strings.NewReader(js))
   483  	assert.NilError(t, err)
   484  	err = config.Save()
   485  	assert.ErrorContains(t, err, "with empty filename")
   486  
   487  	tmpHome, err := ioutil.TempDir("", "config-test")
   488  	assert.NilError(t, err)
   489  	defer os.RemoveAll(tmpHome)
   490  
   491  	fn := filepath.Join(tmpHome, ConfigFileName)
   492  	f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   493  	defer f.Close()
   494  
   495  	assert.NilError(t, config.SaveToWriter(f))
   496  	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
   497  	assert.NilError(t, err)
   498  	expConfStr := `{
   499  	"auths": {
   500  		"https://index.docker.io/v1/": {
   501  			"auth": "am9lam9lOmhlbGxv"
   502  		}
   503  	},
   504  	"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
   505  }`
   506  	if string(buf) != expConfStr {
   507  		t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr)
   508  	}
   509  }
   510  
   511  func TestLegacyJSONSaveWithNoFile(t *testing.T) {
   512  	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
   513  	config, err := LegacyLoadFromReader(strings.NewReader(js))
   514  	assert.NilError(t, err)
   515  	err = config.Save()
   516  	assert.ErrorContains(t, err, "with empty filename")
   517  
   518  	tmpHome, err := ioutil.TempDir("", "config-test")
   519  	assert.NilError(t, err)
   520  	defer os.RemoveAll(tmpHome)
   521  
   522  	fn := filepath.Join(tmpHome, ConfigFileName)
   523  	f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   524  	defer f.Close()
   525  
   526  	assert.NilError(t, config.SaveToWriter(f))
   527  	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
   528  	assert.NilError(t, err)
   529  
   530  	expConfStr := `{
   531  	"auths": {
   532  		"https://index.docker.io/v1/": {
   533  			"auth": "am9lam9lOmhlbGxv",
   534  			"email": "user@example.com"
   535  		}
   536  	}
   537  }`
   538  
   539  	if string(buf) != expConfStr {
   540  		t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
   541  	}
   542  }
   543  
   544  func TestLoadDefaultConfigFile(t *testing.T) {
   545  	dir, cleanup := setupConfigDir(t)
   546  	defer cleanup()
   547  	buffer := new(bytes.Buffer)
   548  
   549  	filename := filepath.Join(dir, ConfigFileName)
   550  	content := []byte(`{"PsFormat": "format"}`)
   551  	err := ioutil.WriteFile(filename, content, 0644)
   552  	assert.NilError(t, err)
   553  
   554  	configFile := LoadDefaultConfigFile(buffer)
   555  	credStore := credentials.DetectDefaultStore("")
   556  	expected := configfile.New(filename)
   557  	expected.CredentialsStore = credStore
   558  	expected.PsFormat = "format"
   559  
   560  	assert.Check(t, is.DeepEqual(expected, configFile))
   561  }
   562  
   563  func TestConfigPath(t *testing.T) {
   564  	oldDir := Dir()
   565  
   566  	for _, tc := range []struct {
   567  		name        string
   568  		dir         string
   569  		path        []string
   570  		expected    string
   571  		expectedErr string
   572  	}{
   573  		{
   574  			name:     "valid_path",
   575  			dir:      "dummy",
   576  			path:     []string{"a", "b"},
   577  			expected: filepath.Join("dummy", "a", "b"),
   578  		},
   579  		{
   580  			name:     "valid_path_absolute_dir",
   581  			dir:      "/dummy",
   582  			path:     []string{"a", "b"},
   583  			expected: filepath.Join("/dummy", "a", "b"),
   584  		},
   585  		{
   586  			name:        "invalid_relative_path",
   587  			dir:         "dummy",
   588  			path:        []string{"e", "..", "..", "f"},
   589  			expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"),
   590  		},
   591  		{
   592  			name:        "invalid_absolute_path",
   593  			dir:         "dummy",
   594  			path:        []string{"/a", "..", ".."},
   595  			expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"),
   596  		},
   597  	} {
   598  		tc := tc
   599  		t.Run(tc.name, func(t *testing.T) {
   600  			SetDir(tc.dir)
   601  			f, err := Path(tc.path...)
   602  			assert.Equal(t, f, tc.expected)
   603  			if tc.expectedErr == "" {
   604  				assert.NilError(t, err)
   605  			} else {
   606  				assert.ErrorContains(t, err, tc.expectedErr)
   607  			}
   608  		})
   609  	}
   610  
   611  	SetDir(oldDir)
   612  }