github.com/larkox/mattermost-server@v5.11.1+incompatible/config/file_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package config_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/pkg/errors"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/mattermost/mattermost-server/config"
    19  	"github.com/mattermost/mattermost-server/model"
    20  	"github.com/mattermost/mattermost-server/utils"
    21  )
    22  
    23  func setupConfigFile(t *testing.T, cfg *model.Config) (string, func()) {
    24  	os.Clearenv()
    25  	t.Helper()
    26  
    27  	tempDir, err := ioutil.TempDir("", "setupConfigFile")
    28  	require.NoError(t, err)
    29  
    30  	err = os.Chdir(tempDir)
    31  	require.NoError(t, err)
    32  
    33  	var name string
    34  	if cfg != nil {
    35  		f, err := ioutil.TempFile(tempDir, "setupConfigFile")
    36  		require.NoError(t, err)
    37  
    38  		cfgData, err := config.MarshalConfig(cfg)
    39  		require.NoError(t, err)
    40  
    41  		ioutil.WriteFile(f.Name(), cfgData, 0644)
    42  
    43  		name = f.Name()
    44  	}
    45  
    46  	return name, func() {
    47  		os.RemoveAll(tempDir)
    48  	}
    49  }
    50  
    51  // getActualFileConfig returns the configuration present in the given file without relying a config store.
    52  func getActualFileConfig(t *testing.T, path string) *model.Config {
    53  	t.Helper()
    54  
    55  	f, err := os.Open(path)
    56  	require.Nil(t, err)
    57  	defer f.Close()
    58  
    59  	actualCfg, _, err := config.UnmarshalConfig(f, false)
    60  	require.Nil(t, err)
    61  
    62  	return actualCfg
    63  }
    64  
    65  // assertFileEqualsConfig verifies the on disk contents of the given path equal the given config.
    66  func assertFileEqualsConfig(t *testing.T, expectedCfg *model.Config, path string) {
    67  	t.Helper()
    68  
    69  	expectedCfg = prepareExpectedConfig(t, expectedCfg)
    70  	actualCfg := getActualFileConfig(t, path)
    71  
    72  	assert.Equal(t, expectedCfg, actualCfg)
    73  }
    74  
    75  // assertFileNotEqualsConfig verifies the on disk contents of the given path does not equal the given config.
    76  func assertFileNotEqualsConfig(t *testing.T, expectedCfg *model.Config, path string) {
    77  	t.Helper()
    78  
    79  	expectedCfg = prepareExpectedConfig(t, expectedCfg)
    80  	actualCfg := getActualFileConfig(t, path)
    81  
    82  	assert.NotEqual(t, expectedCfg, actualCfg)
    83  }
    84  
    85  func TestFileStoreNew(t *testing.T) {
    86  	utils.TranslationsPreInit()
    87  
    88  	t.Run("absolute path, initialization required", func(t *testing.T) {
    89  		path, tearDown := setupConfigFile(t, testConfig)
    90  		defer tearDown()
    91  
    92  		fs, err := config.NewFileStore(path, false)
    93  		require.NoError(t, err)
    94  		defer fs.Close()
    95  
    96  		assert.Equal(t, "http://TestStoreNew", *fs.Get().ServiceSettings.SiteURL)
    97  		assertFileNotEqualsConfig(t, testConfig, path)
    98  	})
    99  
   100  	t.Run("absolute path, already minimally configured", func(t *testing.T) {
   101  		path, tearDown := setupConfigFile(t, minimalConfig)
   102  		defer tearDown()
   103  
   104  		fs, err := config.NewFileStore(path, false)
   105  		require.NoError(t, err)
   106  		defer fs.Close()
   107  
   108  		assert.Equal(t, "http://minimal", *fs.Get().ServiceSettings.SiteURL)
   109  		assertFileEqualsConfig(t, minimalConfig, path)
   110  	})
   111  
   112  	t.Run("absolute path, file does not exist", func(t *testing.T) {
   113  		_, tearDown := setupConfigFile(t, nil)
   114  		defer tearDown()
   115  
   116  		tempDir, err := ioutil.TempDir("", "TestFileStoreNew")
   117  		require.NoError(t, err)
   118  		defer os.RemoveAll(tempDir)
   119  
   120  		path := filepath.Join(tempDir, "does_not_exist")
   121  		fs, err := config.NewFileStore(path, false)
   122  		require.NoError(t, err)
   123  		defer fs.Close()
   124  
   125  		assert.Equal(t, model.SERVICE_SETTINGS_DEFAULT_SITE_URL, *fs.Get().ServiceSettings.SiteURL)
   126  		assertFileNotEqualsConfig(t, testConfig, path)
   127  	})
   128  
   129  	t.Run("absolute path, path to file does not exist", func(t *testing.T) {
   130  		_, tearDown := setupConfigFile(t, nil)
   131  		defer tearDown()
   132  
   133  		tempDir, err := ioutil.TempDir("", "TestFileStoreNew")
   134  		require.NoError(t, err)
   135  		defer os.RemoveAll(tempDir)
   136  
   137  		path := filepath.Join(tempDir, "does/not/exist")
   138  		_, err = config.NewFileStore(path, false)
   139  		require.Error(t, err)
   140  	})
   141  
   142  	t.Run("relative path, file exists", func(t *testing.T) {
   143  		_, tearDown := setupConfigFile(t, nil)
   144  		defer tearDown()
   145  
   146  		err := os.MkdirAll("TestFileStoreNew/a/b/c", 0700)
   147  		require.NoError(t, err)
   148  		defer os.RemoveAll("TestFileStoreNew")
   149  
   150  		path := "TestFileStoreNew/a/b/c/config.json"
   151  
   152  		cfgData, err := config.MarshalConfig(testConfig)
   153  		require.NoError(t, err)
   154  
   155  		ioutil.WriteFile(path, cfgData, 0644)
   156  
   157  		fs, err := config.NewFileStore(path, false)
   158  		require.NoError(t, err)
   159  		defer fs.Close()
   160  
   161  		assert.Equal(t, "http://TestStoreNew", *fs.Get().ServiceSettings.SiteURL)
   162  		assertFileNotEqualsConfig(t, testConfig, path)
   163  	})
   164  
   165  	t.Run("relative path, file does not exist", func(t *testing.T) {
   166  		_, tearDown := setupConfigFile(t, nil)
   167  		defer tearDown()
   168  
   169  		err := os.MkdirAll("config/TestFileStoreNew/a/b/c", 0700)
   170  		require.NoError(t, err)
   171  		defer os.RemoveAll("config/TestFileStoreNew")
   172  
   173  		path := "TestFileStoreNew/a/b/c/config.json"
   174  		fs, err := config.NewFileStore(path, false)
   175  		require.NoError(t, err)
   176  		defer fs.Close()
   177  
   178  		assert.Equal(t, model.SERVICE_SETTINGS_DEFAULT_SITE_URL, *fs.Get().ServiceSettings.SiteURL)
   179  		assertFileNotEqualsConfig(t, testConfig, filepath.Join("config", path))
   180  	})
   181  }
   182  
   183  func TestFileStoreGet(t *testing.T) {
   184  	path, tearDown := setupConfigFile(t, testConfig)
   185  	defer tearDown()
   186  
   187  	fs, err := config.NewFileStore(path, false)
   188  	require.NoError(t, err)
   189  	defer fs.Close()
   190  
   191  	cfg := fs.Get()
   192  	assert.Equal(t, "http://TestStoreNew", *cfg.ServiceSettings.SiteURL)
   193  
   194  	cfg2 := fs.Get()
   195  	assert.Equal(t, "http://TestStoreNew", *cfg.ServiceSettings.SiteURL)
   196  
   197  	assert.True(t, cfg == cfg2, "Get() returned different configuration instances")
   198  
   199  	newCfg := &model.Config{}
   200  	oldCfg, err := fs.Set(newCfg)
   201  
   202  	assert.True(t, oldCfg == cfg, "returned config after set() changed original")
   203  	assert.False(t, newCfg == cfg, "returned config should have been different from original")
   204  }
   205  
   206  func TestFileStoreGetEnivironmentOverrides(t *testing.T) {
   207  	path, tearDown := setupConfigFile(t, testConfig)
   208  	defer tearDown()
   209  
   210  	fs, err := config.NewFileStore(path, false)
   211  	require.NoError(t, err)
   212  	defer fs.Close()
   213  
   214  	assert.Equal(t, "http://TestStoreNew", *fs.Get().ServiceSettings.SiteURL)
   215  	assert.Empty(t, fs.GetEnvironmentOverrides())
   216  
   217  	os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://override")
   218  
   219  	fs, err = config.NewFileStore(path, false)
   220  	require.NoError(t, err)
   221  	defer fs.Close()
   222  
   223  	assert.Equal(t, "http://override", *fs.Get().ServiceSettings.SiteURL)
   224  	assert.Equal(t, map[string]interface{}{"ServiceSettings": map[string]interface{}{"SiteURL": true}}, fs.GetEnvironmentOverrides())
   225  }
   226  
   227  func TestFileStoreSet(t *testing.T) {
   228  	t.Run("set same pointer value", func(t *testing.T) {
   229  		t.Skip("not yet implemented")
   230  
   231  		path, tearDown := setupConfigFile(t, emptyConfig)
   232  		defer tearDown()
   233  
   234  		fs, err := config.NewFileStore(path, false)
   235  		require.NoError(t, err)
   236  		defer fs.Close()
   237  
   238  		_, err = fs.Set(fs.Get())
   239  		if assert.Error(t, err) {
   240  			assert.EqualError(t, err, "old configuration modified instead of cloning")
   241  		}
   242  	})
   243  
   244  	t.Run("defaults required", func(t *testing.T) {
   245  		path, tearDown := setupConfigFile(t, minimalConfig)
   246  		defer tearDown()
   247  
   248  		fs, err := config.NewFileStore(path, false)
   249  		require.NoError(t, err)
   250  		defer fs.Close()
   251  
   252  		oldCfg := fs.Get()
   253  
   254  		newCfg := &model.Config{}
   255  
   256  		retCfg, err := fs.Set(newCfg)
   257  		require.NoError(t, err)
   258  		assert.Equal(t, oldCfg, retCfg)
   259  
   260  		assert.Equal(t, model.SERVICE_SETTINGS_DEFAULT_SITE_URL, *fs.Get().ServiceSettings.SiteURL)
   261  	})
   262  
   263  	t.Run("desanitization required", func(t *testing.T) {
   264  		path, tearDown := setupConfigFile(t, ldapConfig)
   265  		defer tearDown()
   266  
   267  		fs, err := config.NewFileStore(path, false)
   268  		require.NoError(t, err)
   269  		defer fs.Close()
   270  
   271  		oldCfg := fs.Get()
   272  
   273  		newCfg := &model.Config{}
   274  		newCfg.LdapSettings.BindPassword = sToP(model.FAKE_SETTING)
   275  
   276  		retCfg, err := fs.Set(newCfg)
   277  		require.NoError(t, err)
   278  		assert.Equal(t, oldCfg, retCfg)
   279  
   280  		assert.Equal(t, "password", *fs.Get().LdapSettings.BindPassword)
   281  	})
   282  
   283  	t.Run("invalid", func(t *testing.T) {
   284  		path, tearDown := setupConfigFile(t, emptyConfig)
   285  		defer tearDown()
   286  
   287  		fs, err := config.NewFileStore(path, false)
   288  		require.NoError(t, err)
   289  		defer fs.Close()
   290  
   291  		newCfg := &model.Config{}
   292  		newCfg.ServiceSettings.SiteURL = sToP("invalid")
   293  
   294  		_, err = fs.Set(newCfg)
   295  		if assert.Error(t, err) {
   296  			assert.EqualError(t, err, "new configuration is invalid: Config.IsValid: model.config.is_valid.site_url.app_error, ")
   297  		}
   298  
   299  		assert.Equal(t, model.SERVICE_SETTINGS_DEFAULT_SITE_URL, *fs.Get().ServiceSettings.SiteURL)
   300  	})
   301  
   302  	t.Run("read-only", func(t *testing.T) {
   303  		path, tearDown := setupConfigFile(t, readOnlyConfig)
   304  		defer tearDown()
   305  
   306  		fs, err := config.NewFileStore(path, false)
   307  		require.NoError(t, err)
   308  		defer fs.Close()
   309  
   310  		newCfg := &model.Config{}
   311  
   312  		_, err = fs.Set(newCfg)
   313  		if assert.Error(t, err) {
   314  			assert.Equal(t, config.ErrReadOnlyConfiguration, errors.Cause(err))
   315  		}
   316  
   317  		assert.Equal(t, model.SERVICE_SETTINGS_DEFAULT_SITE_URL, *fs.Get().ServiceSettings.SiteURL)
   318  	})
   319  
   320  	t.Run("persist failed", func(t *testing.T) {
   321  		t.Skip("skipping persistence test inside Set")
   322  		path, tearDown := setupConfigFile(t, emptyConfig)
   323  		defer tearDown()
   324  
   325  		fs, err := config.NewFileStore(path, false)
   326  		require.NoError(t, err)
   327  		defer fs.Close()
   328  
   329  		os.Chmod(path, 0500)
   330  
   331  		newCfg := &model.Config{}
   332  
   333  		_, err = fs.Set(newCfg)
   334  		if assert.Error(t, err) {
   335  			assert.True(t, strings.HasPrefix(err.Error(), "failed to persist: failed to write file"))
   336  		}
   337  
   338  		assert.Equal(t, model.SERVICE_SETTINGS_DEFAULT_SITE_URL, *fs.Get().ServiceSettings.SiteURL)
   339  	})
   340  
   341  	t.Run("listeners notified", func(t *testing.T) {
   342  		path, tearDown := setupConfigFile(t, emptyConfig)
   343  		defer tearDown()
   344  
   345  		fs, err := config.NewFileStore(path, false)
   346  		require.NoError(t, err)
   347  		defer fs.Close()
   348  
   349  		oldCfg := fs.Get()
   350  
   351  		called := make(chan bool, 1)
   352  		callback := func(oldfg, newCfg *model.Config) {
   353  			called <- true
   354  		}
   355  		fs.AddListener(callback)
   356  
   357  		newCfg := &model.Config{}
   358  
   359  		retCfg, err := fs.Set(newCfg)
   360  		require.NoError(t, err)
   361  		assert.Equal(t, oldCfg, retCfg)
   362  
   363  		select {
   364  		case <-called:
   365  		case <-time.After(5 * time.Second):
   366  			t.Fatal("callback should have been called when config written")
   367  		}
   368  	})
   369  
   370  	t.Run("watcher restarted", func(t *testing.T) {
   371  		if testing.Short() {
   372  			t.Skip("skipping watcher test in short mode")
   373  		}
   374  
   375  		path, tearDown := setupConfigFile(t, emptyConfig)
   376  		defer tearDown()
   377  
   378  		fs, err := config.NewFileStore(path, true)
   379  		require.NoError(t, err)
   380  		defer fs.Close()
   381  
   382  		_, err = fs.Set(&model.Config{})
   383  		require.NoError(t, err)
   384  
   385  		// Let the initial call to invokeConfigListeners finish.
   386  		time.Sleep(1 * time.Second)
   387  
   388  		called := make(chan bool, 1)
   389  		callback := func(oldfg, newCfg *model.Config) {
   390  			called <- true
   391  		}
   392  		fs.AddListener(callback)
   393  
   394  		// Rewrite the config to the file on disk
   395  		cfgData, err := config.MarshalConfig(emptyConfig)
   396  		require.NoError(t, err)
   397  
   398  		ioutil.WriteFile(path, cfgData, 0644)
   399  		select {
   400  		case <-called:
   401  		case <-time.After(5 * time.Second):
   402  			t.Fatal("callback should have been called when config written")
   403  		}
   404  	})
   405  }
   406  
   407  func TestFileStoreLoad(t *testing.T) {
   408  	t.Run("file no longer exists", func(t *testing.T) {
   409  		path, tearDown := setupConfigFile(t, emptyConfig)
   410  		defer tearDown()
   411  
   412  		fs, err := config.NewFileStore(path, false)
   413  		require.NoError(t, err)
   414  		defer fs.Close()
   415  
   416  		os.Remove(path)
   417  
   418  		err = fs.Load()
   419  		require.NoError(t, err)
   420  		assertFileNotEqualsConfig(t, emptyConfig, path)
   421  	})
   422  
   423  	t.Run("honour environment", func(t *testing.T) {
   424  		path, tearDown := setupConfigFile(t, minimalConfig)
   425  		defer tearDown()
   426  
   427  		fs, err := config.NewFileStore(path, false)
   428  		require.NoError(t, err)
   429  		defer fs.Close()
   430  
   431  		os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://override")
   432  
   433  		err = fs.Load()
   434  		require.NoError(t, err)
   435  		assert.Equal(t, "http://override", *fs.Get().ServiceSettings.SiteURL)
   436  		assert.Equal(t, map[string]interface{}{"ServiceSettings": map[string]interface{}{"SiteURL": true}}, fs.GetEnvironmentOverrides())
   437  	})
   438  
   439  	t.Run("invalid", func(t *testing.T) {
   440  		path, tearDown := setupConfigFile(t, emptyConfig)
   441  		defer tearDown()
   442  
   443  		fs, err := config.NewFileStore(path, false)
   444  		require.NoError(t, err)
   445  		defer fs.Close()
   446  
   447  		cfgData, err := config.MarshalConfig(invalidConfig)
   448  		require.NoError(t, err)
   449  
   450  		ioutil.WriteFile(path, cfgData, 0644)
   451  
   452  		err = fs.Load()
   453  		if assert.Error(t, err) {
   454  			assert.EqualError(t, err, "invalid config: Config.IsValid: model.config.is_valid.site_url.app_error, ")
   455  		}
   456  	})
   457  
   458  	t.Run("fixes required", func(t *testing.T) {
   459  		path, tearDown := setupConfigFile(t, fixesRequiredConfig)
   460  		defer tearDown()
   461  
   462  		fs, err := config.NewFileStore(path, false)
   463  		require.NoError(t, err)
   464  		defer fs.Close()
   465  
   466  		err = fs.Load()
   467  		require.NoError(t, err)
   468  		assertFileNotEqualsConfig(t, fixesRequiredConfig, path)
   469  		assert.Equal(t, "http://trailingslash", *fs.Get().ServiceSettings.SiteURL)
   470  		assert.Equal(t, "/path/to/directory/", *fs.Get().FileSettings.Directory)
   471  		assert.Equal(t, "en", *fs.Get().LocalizationSettings.DefaultServerLocale)
   472  		assert.Equal(t, "en", *fs.Get().LocalizationSettings.DefaultClientLocale)
   473  	})
   474  
   475  	t.Run("listeners notifed", func(t *testing.T) {
   476  		path, tearDown := setupConfigFile(t, emptyConfig)
   477  		defer tearDown()
   478  
   479  		fs, err := config.NewFileStore(path, false)
   480  		require.NoError(t, err)
   481  		defer fs.Close()
   482  
   483  		called := make(chan bool, 1)
   484  		callback := func(oldfg, newCfg *model.Config) {
   485  			called <- true
   486  		}
   487  		fs.AddListener(callback)
   488  
   489  		err = fs.Load()
   490  		require.NoError(t, err)
   491  
   492  		select {
   493  		case <-called:
   494  		case <-time.After(5 * time.Second):
   495  			t.Fatal("callback should have been called when config loaded")
   496  		}
   497  	})
   498  }
   499  
   500  func TestFileStoreWatcherEmitter(t *testing.T) {
   501  	if testing.Short() {
   502  		t.Skip("skipping watcher test in short mode")
   503  	}
   504  
   505  	t.Parallel()
   506  
   507  	path, tearDown := setupConfigFile(t, emptyConfig)
   508  	defer tearDown()
   509  
   510  	t.Run("disabled", func(t *testing.T) {
   511  		fs, err := config.NewFileStore(path, false)
   512  		require.NoError(t, err)
   513  		defer fs.Close()
   514  
   515  		// Let the initial call to invokeConfigListeners finish.
   516  		time.Sleep(1 * time.Second)
   517  
   518  		called := make(chan bool, 1)
   519  		callback := func(oldfg, newCfg *model.Config) {
   520  			called <- true
   521  		}
   522  		fs.AddListener(callback)
   523  
   524  		// Rewrite the config to the file on disk
   525  		cfgData, err := config.MarshalConfig(emptyConfig)
   526  		require.NoError(t, err)
   527  
   528  		ioutil.WriteFile(path, cfgData, 0644)
   529  		select {
   530  		case <-called:
   531  			t.Fatal("callback should not have been called since watching disabled")
   532  		case <-time.After(1 * time.Second):
   533  		}
   534  	})
   535  
   536  	t.Run("enabled", func(t *testing.T) {
   537  		fs, err := config.NewFileStore(path, true)
   538  		require.NoError(t, err)
   539  		defer fs.Close()
   540  
   541  		called := make(chan bool, 1)
   542  		callback := func(oldfg, newCfg *model.Config) {
   543  			called <- true
   544  		}
   545  		fs.AddListener(callback)
   546  
   547  		// Rewrite the config to the file on disk
   548  		cfgData, err := config.MarshalConfig(emptyConfig)
   549  		require.NoError(t, err)
   550  
   551  		ioutil.WriteFile(path, cfgData, 0644)
   552  		select {
   553  		case <-called:
   554  		case <-time.After(5 * time.Second):
   555  			t.Fatal("callback should have been called when config written")
   556  		}
   557  	})
   558  }
   559  
   560  func TestFileStoreSave(t *testing.T) {
   561  	path, tearDown := setupConfigFile(t, minimalConfig)
   562  	defer tearDown()
   563  
   564  	fs, err := config.NewFileStore(path, true)
   565  	require.NoError(t, err)
   566  	defer fs.Close()
   567  
   568  	newCfg := &model.Config{
   569  		ServiceSettings: model.ServiceSettings{
   570  			SiteURL: sToP("http://new"),
   571  		},
   572  	}
   573  
   574  	t.Run("set with automatic save", func(t *testing.T) {
   575  		_, err = fs.Set(newCfg)
   576  		require.NoError(t, err)
   577  
   578  		err = fs.Load()
   579  		require.NoError(t, err)
   580  
   581  		assert.Equal(t, "http://new", *fs.Get().ServiceSettings.SiteURL)
   582  	})
   583  }
   584  
   585  func TestFileGetFile(t *testing.T) {
   586  	path, tearDown := setupConfigFile(t, minimalConfig)
   587  	defer tearDown()
   588  
   589  	fs, err := config.NewFileStore(path, true)
   590  	require.NoError(t, err)
   591  	defer fs.Close()
   592  
   593  	t.Run("get empty filename", func(t *testing.T) {
   594  		_, err := fs.GetFile("")
   595  		require.Error(t, err)
   596  	})
   597  
   598  	t.Run("get non-existent file", func(t *testing.T) {
   599  		_, err := fs.GetFile("unknown")
   600  		require.Error(t, err)
   601  	})
   602  
   603  	t.Run("get empty file", func(t *testing.T) {
   604  		err := os.MkdirAll("config", 0700)
   605  		require.NoError(t, err)
   606  
   607  		f, err := ioutil.TempFile("config", "empty-file")
   608  		require.NoError(t, err)
   609  		defer os.Remove(f.Name())
   610  
   611  		err = ioutil.WriteFile(f.Name(), nil, 0777)
   612  		require.NoError(t, err)
   613  
   614  		data, err := fs.GetFile(f.Name())
   615  		require.NoError(t, err)
   616  		require.Empty(t, data)
   617  	})
   618  
   619  	t.Run("get non-empty file", func(t *testing.T) {
   620  		err := os.MkdirAll("config", 0700)
   621  		require.NoError(t, err)
   622  
   623  		f, err := ioutil.TempFile("config", "test-file")
   624  		require.NoError(t, err)
   625  		defer os.Remove(f.Name())
   626  
   627  		err = ioutil.WriteFile(f.Name(), []byte("test"), 0777)
   628  		require.NoError(t, err)
   629  
   630  		data, err := fs.GetFile(f.Name())
   631  		require.NoError(t, err)
   632  		require.Equal(t, []byte("test"), data)
   633  	})
   634  }
   635  
   636  func TestFileSetFile(t *testing.T) {
   637  	path, tearDown := setupConfigFile(t, minimalConfig)
   638  	defer tearDown()
   639  
   640  	fs, err := config.NewFileStore(path, true)
   641  	require.NoError(t, err)
   642  	defer fs.Close()
   643  
   644  	t.Run("set new file", func(t *testing.T) {
   645  		err := fs.SetFile("new", []byte("new file"))
   646  		require.NoError(t, err)
   647  
   648  		data, err := fs.GetFile("new")
   649  		require.NoError(t, err)
   650  		require.Equal(t, []byte("new file"), data)
   651  	})
   652  
   653  	t.Run("overwrite existing file", func(t *testing.T) {
   654  		err := fs.SetFile("existing", []byte("existing file"))
   655  		require.NoError(t, err)
   656  
   657  		err = fs.SetFile("existing", []byte("overwritten file"))
   658  		require.NoError(t, err)
   659  
   660  		data, err := fs.GetFile("existing")
   661  		require.NoError(t, err)
   662  		require.Equal(t, []byte("overwritten file"), data)
   663  	})
   664  }
   665  
   666  func TestFileHasFile(t *testing.T) {
   667  
   668  	t.Run("has non-existent", func(t *testing.T) {
   669  		path, tearDown := setupConfigFile(t, minimalConfig)
   670  		defer tearDown()
   671  
   672  		fs, err := config.NewFileStore(path, true)
   673  		require.NoError(t, err)
   674  		defer fs.Close()
   675  
   676  		has, err := fs.HasFile("non-existent")
   677  		require.NoError(t, err)
   678  		require.False(t, has)
   679  	})
   680  
   681  	t.Run("has existing", func(t *testing.T) {
   682  		path, tearDown := setupConfigFile(t, minimalConfig)
   683  		defer tearDown()
   684  
   685  		fs, err := config.NewFileStore(path, true)
   686  		require.NoError(t, err)
   687  		defer fs.Close()
   688  
   689  		err = fs.SetFile("existing", []byte("existing file"))
   690  		require.NoError(t, err)
   691  
   692  		has, err := fs.HasFile("existing")
   693  		require.NoError(t, err)
   694  		require.True(t, has)
   695  	})
   696  
   697  	t.Run("has manually created file", func(t *testing.T) {
   698  		path, tearDown := setupConfigFile(t, minimalConfig)
   699  		defer tearDown()
   700  
   701  		fs, err := config.NewFileStore(path, true)
   702  		require.NoError(t, err)
   703  		defer fs.Close()
   704  
   705  		err = os.MkdirAll("config", 0700)
   706  		require.NoError(t, err)
   707  
   708  		f, err := ioutil.TempFile("config", "test-file")
   709  		require.NoError(t, err)
   710  		defer os.Remove(f.Name())
   711  
   712  		err = ioutil.WriteFile(f.Name(), []byte("test"), 0777)
   713  		require.NoError(t, err)
   714  
   715  		has, err := fs.HasFile(f.Name())
   716  		require.NoError(t, err)
   717  		require.True(t, has)
   718  	})
   719  }
   720  
   721  func TestFileRemoveFile(t *testing.T) {
   722  	t.Run("remove non-existent", func(t *testing.T) {
   723  		path, tearDown := setupConfigFile(t, minimalConfig)
   724  		defer tearDown()
   725  
   726  		fs, err := config.NewFileStore(path, true)
   727  		require.NoError(t, err)
   728  		defer fs.Close()
   729  
   730  		err = fs.RemoveFile("non-existent")
   731  		require.NoError(t, err)
   732  	})
   733  
   734  	t.Run("remove existing", func(t *testing.T) {
   735  		path, tearDown := setupConfigFile(t, minimalConfig)
   736  		defer tearDown()
   737  
   738  		fs, err := config.NewFileStore(path, true)
   739  		require.NoError(t, err)
   740  		defer fs.Close()
   741  
   742  		err = fs.SetFile("existing", []byte("existing file"))
   743  		require.NoError(t, err)
   744  
   745  		err = fs.RemoveFile("existing")
   746  		require.NoError(t, err)
   747  
   748  		has, err := fs.HasFile("existing")
   749  		require.NoError(t, err)
   750  		require.False(t, has)
   751  
   752  		_, err = fs.GetFile("existing")
   753  		require.Error(t, err)
   754  	})
   755  
   756  	t.Run("remove manually created file", func(t *testing.T) {
   757  		path, tearDown := setupConfigFile(t, minimalConfig)
   758  		defer tearDown()
   759  
   760  		fs, err := config.NewFileStore(path, true)
   761  		require.NoError(t, err)
   762  		defer fs.Close()
   763  
   764  		err = os.MkdirAll("config", 0700)
   765  		require.NoError(t, err)
   766  
   767  		f, err := ioutil.TempFile("config", "test-file")
   768  		require.NoError(t, err)
   769  		defer os.Remove(f.Name())
   770  
   771  		err = ioutil.WriteFile(f.Name(), []byte("test"), 0777)
   772  		require.NoError(t, err)
   773  
   774  		err = fs.RemoveFile(f.Name())
   775  		require.NoError(t, err)
   776  
   777  		has, err := fs.HasFile("existing")
   778  		require.NoError(t, err)
   779  		require.False(t, has)
   780  
   781  		_, err = fs.GetFile("existing")
   782  		require.Error(t, err)
   783  	})
   784  }
   785  
   786  func TestFileStoreString(t *testing.T) {
   787  	path, tearDown := setupConfigFile(t, emptyConfig)
   788  	defer tearDown()
   789  
   790  	fs, err := config.NewFileStore(path, false)
   791  	require.NoError(t, err)
   792  	defer fs.Close()
   793  
   794  	assert.Equal(t, "file://"+path, fs.String())
   795  }