github.com/jxskiss/gopkg@v0.17.3/confr/loader_test.go (about)

     1  package confr
     2  
     3  import (
     4  	"flag"
     5  	"os"
     6  	"reflect"
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  
    12  	"github.com/jxskiss/gopkg/easy"
    13  )
    14  
    15  type DBConfig struct {
    16  	MySQL string `json:"mysql" toml:"mysql" yaml:"mysql" flag:"mysql"`
    17  	Redis string `json:"redis" toml:"redis" yaml:"redis"`
    18  }
    19  
    20  type MQConfig struct {
    21  	Cluster string `json:"cluster" toml:"cluster" yaml:"cluster" default:"some_default_cluster"`
    22  	Topic   string `json:"topic" toml:"topic" yaml:"topic"`
    23  	Group   string `json:"group" toml:"group" yaml:"group"`
    24  }
    25  
    26  type TestConfig struct {
    27  	Some1    string  `json:"some_1" toml:"some_1" yaml:"some_1" flag:"some1"`
    28  	Some1Ptr *string `json:"some_1_ptr" toml:"some_1_ptr" yaml:"some_1_ptr" flag:"some1_ptr"`
    29  	Some2    int     `json:"some_2" toml:"some_2" yaml:"some_2" flag:"some2"`
    30  	Some2Ptr *int    `json:"some_2_ptr" toml:"some_2_ptr" yaml:"some_2_ptr"`
    31  	Some3    int64   `json:"some_3" toml:"some_3" yaml:"some_3"`
    32  	Some3Ptr *int64  `json:"some_3_ptr" toml:"some_3_ptr" yaml:"some_3_ptr"`
    33  	Some4    int32   `json:"some_4" toml:"some_4" yaml:"some_4"`
    34  	Some4Ptr *int32  `json:"some_4_ptr" toml:"some_4_ptr" yaml:"some_4_ptr"`
    35  
    36  	Some5 []int    `json:"some_5" toml:"some_5" yaml:"some_5"`
    37  	Some6 []int64  `json:"some_6" toml:"some_6" yaml:"some_6"`
    38  	Some7 []int32  `json:"some_7" toml:"some_7" yaml:"some_7"`
    39  	Some8 []string `json:"some_8" toml:"some_8" yaml:"some_8"`
    40  	Some9 []string `json:"some_9" toml:"some_9" yaml:"some_9"`
    41  
    42  	SomeBool     bool  `json:"some_bool" toml:"some_bool" yaml:"some_bool" flag:"some-bool"`
    43  	SomeBoolPtr1 *bool `json:"some_bool_ptr1" toml:"some_bool_ptr1" yaml:"some_bool_ptr1" flag:"some-bool-ptr1"`
    44  	SomeBoolPtr2 *bool `json:"some_bool_ptr2" toml:"some_bool_ptr2" yaml:"some_bool_ptr2" flag:"some-bool-ptr2"`
    45  
    46  	OverrideEnvVar  int64    `json:"override_env_var" toml:"override_env_var" yaml:"override_env_var" env:"OVERRIDE_ENV_VAR"`
    47  	ExplicitEnvVar1 string   `env:"EXPLICIT_ENV_VAR1"`
    48  	ExplicitEnvVar2 *float64 `env:"EXPLICIT_ENV_VAR2"`
    49  
    50  	ImplicitEnvVar1        *string // test with Implicit_Env_Var1
    51  	ImplicitEnvVar2        string  // test with IMPLICIT_ENV_VAR2
    52  	ImplicitEnvVarOverride string  `json:"implicit_env_var_override" toml:"implicit_env_var_override" yaml:"implicit_env_var_override"`
    53  
    54  	SomeRemoteValue1    string    `custom:"some_remote_value_1"`
    55  	SomeRemoteValue1Ptr *string   `custom:"some_remote_value_1_ptr"`
    56  	SomeRemoteValue2    []string  `custom:"some_remote_value_2"`
    57  	SomeRemoteStruct    *MQConfig `custom:"some_remote_struct"`
    58  
    59  	DB    DBConfig  `json:"db" toml:"db" yaml:"db"`
    60  	DBPtr *DBConfig `json:"db_ptr" toml:"db_ptr" yaml:"db_ptr"`
    61  
    62  	DefaultMQ    MQConfig  `json:"default_mq" toml:"default_mq" yaml:"default_mq"`
    63  	DefaultMQPtr *MQConfig `json:"default_mq_ptr" toml:"default_mq_ptr" yaml:"default_mq_ptr"`
    64  
    65  	MQList []*MQConfig `json:"mq_list" toml:"mq_list" yaml:"mq_list"`
    66  }
    67  
    68  func TestLoad_SingleFile_JSON(t *testing.T) {
    69  	configFiles := []string{
    70  		"./testdata/config.test.json",
    71  	}
    72  	testLoad_SingleFile(t, configFiles...)
    73  }
    74  
    75  func TestLoad_SingleFile_TOML(t *testing.T) {
    76  	configFiles := []string{
    77  		"./testdata/config.test.toml",
    78  	}
    79  	testLoad_SingleFile(t, configFiles...)
    80  }
    81  
    82  func TestLoad_SingleFile_YAML(t *testing.T) {
    83  	configFiles := []string{
    84  		"./testdata/config.test.yml",
    85  	}
    86  	testLoad_SingleFile(t, configFiles...)
    87  }
    88  
    89  func testLoad_SingleFile(t *testing.T, files ...string) {
    90  	cfg := &TestConfig{}
    91  	err := New(&Config{Verbose: true}).loadFiles(cfg, files...)
    92  	assert.Nil(t, err)
    93  	assertSingleFileConfig(t, cfg)
    94  
    95  	assert.Empty(t, cfg.Some4)                   // not set in test config
    96  	assert.Equal(t, []int{5, 6, 7}, cfg.Some5)   // from test config
    97  	assert.Equal(t, []int64{6, 7, 8}, cfg.Some6) // from test config
    98  	assert.Empty(t, cfg.Some7)                   // not set in test config
    99  }
   100  
   101  func assertSingleFileConfig(t *testing.T, cfg *TestConfig, exclude ...string) {
   102  	testFields := []struct {
   103  		Key      string
   104  		Expected interface{}
   105  		Actual   interface{}
   106  	}{
   107  		{"some_1", "some_1", cfg.Some1},
   108  		{"some_1_ptr", "some_1_ptr", *cfg.Some1Ptr},
   109  		{"some_2", 2345, cfg.Some2},
   110  		{"some_2_ptr", 23456, *cfg.Some2Ptr},
   111  		{"some_8", []string{"a", "b", "c"}, cfg.Some8},
   112  		{"mysql_dsn", "mysql_dsn", cfg.DBPtr.MySQL},
   113  		{"redis_dsn", "redis_dsn", cfg.DBPtr.Redis},
   114  		{"override_env_var", int64(12345), cfg.OverrideEnvVar},
   115  	}
   116  	for _, tf := range testFields {
   117  		if !easy.InStrings(exclude, tf.Key) {
   118  			assert.Equal(t, tf.Expected, tf.Actual)
   119  		}
   120  	}
   121  	assert.NotEmpty(t, cfg.MQList)
   122  }
   123  
   124  func TestLoad_MultipleFiles_JSON(t *testing.T) {
   125  	configFiles := []string{
   126  		"./testdata/config.test.json",
   127  		"./testdata/config.common.json",
   128  	}
   129  	testLoad_MultipleFiles(t, configFiles...)
   130  }
   131  
   132  func TestLoad_MultipleFiles_TOML(t *testing.T) {
   133  	configFiles := []string{
   134  		"./testdata/config.test.toml",
   135  		"./testdata/config.common.toml",
   136  	}
   137  	testLoad_MultipleFiles(t, configFiles...)
   138  }
   139  
   140  func TestLoad_MultipleFiles_YAML(t *testing.T) {
   141  	configFiles := []string{
   142  		"./testdata/config.test.yml",
   143  		"./testdata/config.common.yml",
   144  	}
   145  	testLoad_MultipleFiles(t, configFiles...)
   146  }
   147  
   148  func testLoad_MultipleFiles(t *testing.T, files ...string) {
   149  	cfg := &TestConfig{}
   150  	loader := New(&Config{Verbose: true})
   151  	err := loader.loadFiles(cfg, files...)
   152  	assert.Nil(t, err)
   153  	assertSingleFileConfig(t, cfg)
   154  	assertCommonConfig(t, cfg)
   155  
   156  	assert.Equal(t, 2345, cfg.Some2) // config.test override config.common
   157  }
   158  
   159  func assertCommonConfig(t *testing.T, cfg *TestConfig) {
   160  	assert.Equal(t, int64(-3456), cfg.Some3)
   161  	assert.Equal(t, int64(-34567), *cfg.Some3Ptr)
   162  	assert.Equal(t, "common_mysql_dsn", cfg.DB.MySQL)
   163  	assert.Equal(t, "common_redis_dsn", cfg.DB.Redis)
   164  	assert.Equal(t, "common_default_mq_topic", cfg.DefaultMQ.Topic)
   165  	assert.Empty(t, cfg.DefaultMQ.Cluster) // processDefaults not called
   166  	assert.Empty(t, cfg.DefaultMQ.Group)   // neither set in config.test.yml or config.common.yml
   167  
   168  	assert.Empty(t, cfg.Some4)                      // not set in either test config or common config
   169  	assert.Equal(t, []int{5, 6, 7}, cfg.Some5)      // from test config
   170  	assert.Equal(t, []int64{6, 7, 8}, cfg.Some6)    // from test config
   171  	assert.Equal(t, []int32{-7, -8, -9}, cfg.Some7) // from common config
   172  	assert.Equal(t, "env_var_override", cfg.ImplicitEnvVarOverride)
   173  }
   174  
   175  func TestLoad_DefaultValues_JSON(t *testing.T) {
   176  	configFiles := []string{
   177  		"./testdata/config.test.json",
   178  		"./testdata/config.common.json",
   179  	}
   180  	testLoad_DefaultValues(t, configFiles...)
   181  }
   182  
   183  func TestLoad_DefaultValues_TOML(t *testing.T) {
   184  	configFiles := []string{
   185  		"./testdata/config.test.toml",
   186  		"./testdata/config.common.toml",
   187  	}
   188  	testLoad_DefaultValues(t, configFiles...)
   189  }
   190  
   191  func TestLoad_DefaultValues_YAML(t *testing.T) {
   192  	configFiles := []string{
   193  		"./testdata/config.test.yml",
   194  		"./testdata/config.common.yml",
   195  	}
   196  	testLoad_DefaultValues(t, configFiles...)
   197  }
   198  
   199  func testLoad_DefaultValues(t *testing.T, files ...string) {
   200  	cfg := &TestConfig{}
   201  	loader := New(&Config{Verbose: true})
   202  	err := loader.loadFiles(cfg, files...)
   203  	assert.Nil(t, err)
   204  	err = loader.processDefaults(cfg)
   205  	assert.Nil(t, err)
   206  	assertSingleFileConfig(t, cfg)
   207  
   208  	assert.Empty(t, cfg.DefaultMQPtr) // pointer value not set
   209  	assert.Equal(t, "some_default_cluster", cfg.DefaultMQ.Cluster)
   210  	assert.Equal(t, "some_default_cluster", cfg.MQList[0].Cluster)
   211  	assert.Equal(t, "topic_0", cfg.MQList[0].Topic)
   212  	assert.Equal(t, "group_0", cfg.MQList[0].Group)
   213  	assert.Equal(t, "some_default_cluster", cfg.MQList[1].Cluster)
   214  	assert.Equal(t, "topic_1", cfg.MQList[1].Topic)
   215  	assert.Equal(t, "group_1", cfg.MQList[1].Group)
   216  	assert.Equal(t, "cluster_2", cfg.MQList[2].Cluster)
   217  	assert.Equal(t, "topic_2", cfg.MQList[2].Topic)
   218  	assert.Equal(t, "group_2", cfg.MQList[2].Group)
   219  }
   220  
   221  func TestLoad_CommandLineFlag_JSON(t *testing.T) {
   222  	configFiles := []string{
   223  		"./testdata/config.test.json",
   224  		"./testdata/config.common.json",
   225  	}
   226  	testLoad_CommandLineFlag(t, configFiles...)
   227  }
   228  
   229  func TestLoad_CommandLineFlag_TOML(t *testing.T) {
   230  	configFiles := []string{
   231  		"./testdata/config.test.toml",
   232  		"./testdata/config.common.toml",
   233  	}
   234  	testLoad_CommandLineFlag(t, configFiles...)
   235  }
   236  
   237  func TestLoad_CommandLineFlag_YAML(t *testing.T) {
   238  	configFiles := []string{
   239  		"./testdata/config.test.yml",
   240  		"./testdata/config.common.yml",
   241  	}
   242  	testLoad_CommandLineFlag(t, configFiles...)
   243  }
   244  
   245  var defineFlagsOnce sync.Once
   246  
   247  func testLoad_CommandLineFlag(t *testing.T, files ...string) {
   248  	defineFlagsOnce.Do(func() {
   249  		flag.String("some1", "", "some string value 1")
   250  		flag.String("some1_ptr", "flag value some1_ptr", "some string ptr value 1")
   251  		flag.Int("some2", 0, "some int value 2")
   252  		flag.Bool("some-bool", false, "some bool value")
   253  		flag.Bool("some-bool-ptr1", false, "some bool ptr value 1")
   254  		flag.Bool("some-bool-ptr2", true, "some bool ptr value 2")
   255  	})
   256  
   257  	var err error
   258  	err = flag.Set("some1", "some1 flag.Set called")
   259  	assert.Nil(t, err)
   260  	err = flag.Set("some2", "5432")
   261  	assert.Nil(t, err)
   262  	err = flag.Set("some-bool", "true")
   263  	assert.Nil(t, err)
   264  	err = flag.Set("some-bool-ptr1", "false")
   265  	assert.Nil(t, err)
   266  
   267  	cfg := &TestConfig{}
   268  	loader := New(&Config{
   269  		Verbose: true,
   270  		FlagSet: flag.CommandLine,
   271  	})
   272  	err = loader.loadFiles(cfg, files...)
   273  	assert.Nil(t, err)
   274  	err = loader.processFlags(cfg)
   275  	assert.Nil(t, err)
   276  	assertSingleFileConfig(t, cfg, "some_1", "some_2")
   277  	assertCommonConfig(t, cfg)
   278  
   279  	// flags
   280  
   281  	assert.Equal(t, "some1 flag.Set called", cfg.Some1) // flag override config.test
   282  	assert.Equal(t, 5432, cfg.Some2)                    // flag override config.test and config.common
   283  
   284  	assert.True(t, cfg.SomeBool)       // set to true
   285  	assert.False(t, *cfg.SomeBoolPtr1) // set to false
   286  	assert.True(t, *cfg.SomeBoolPtr2)  // flag default value true
   287  }
   288  
   289  func TestLoad_CustomLoader_JSON(t *testing.T) {
   290  	configFiles := []string{
   291  		"./testdata/config.test.json",
   292  		"./testdata/config.common.json",
   293  	}
   294  	testLoad_CustomLoader(t, configFiles...)
   295  }
   296  
   297  func TestLoad_CustomLoader_TOML(t *testing.T) {
   298  	configFiles := []string{
   299  		"./testdata/config.test.toml",
   300  		"./testdata/config.common.toml",
   301  	}
   302  	testLoad_CustomLoader(t, configFiles...)
   303  }
   304  
   305  func TestLoad_CustomLoader_YAML(t *testing.T) {
   306  	configFiles := []string{
   307  		"./testdata/config.test.yml",
   308  		"./testdata/config.common.yml",
   309  	}
   310  	testLoad_CustomLoader(t, configFiles...)
   311  }
   312  
   313  func testLoad_CustomLoader(t *testing.T, files ...string) {
   314  	cfg := &TestConfig{}
   315  	loader := New(&Config{
   316  		Verbose:      true,
   317  		CustomLoader: testCustomLoader,
   318  	})
   319  	err := loader.loadFiles(cfg, files...)
   320  	assert.Nil(t, err)
   321  	assertSingleFileConfig(t, cfg)
   322  	err = loader.processCustom(cfg)
   323  	assert.Nil(t, err)
   324  
   325  	assert.Equal(t, "remote_value_1", cfg.SomeRemoteValue1)
   326  	assert.Equal(t, "remote_value_1_ptr", *cfg.SomeRemoteValue1Ptr)
   327  	assert.Equal(t, []string{"a", "b", "c"}, cfg.SomeRemoteValue2)
   328  	assert.Equal(t, MQConfig{
   329  		Cluster: "remote_struct_cluster",
   330  		Topic:   "remote_struct_topic",
   331  		Group:   "remote_struct_group",
   332  	}, *cfg.SomeRemoteStruct)
   333  }
   334  
   335  func testCustomLoader(typ reflect.Type, tag string) (interface{}, error) {
   336  	return testCustomLoaderData[tag], nil
   337  }
   338  
   339  var testCustomLoaderData = map[string]interface{}{
   340  	"some_remote_value_1":     "remote_value_1",
   341  	"some_remote_value_1_ptr": "remote_value_1_ptr",
   342  	"some_remote_value_2":     []string{"a", "b", "c"},
   343  	"some_remote_struct": &MQConfig{
   344  		Cluster: "remote_struct_cluster",
   345  		Topic:   "remote_struct_topic",
   346  		Group:   "remote_struct_group",
   347  	},
   348  }
   349  
   350  func Test_getEnvName(t *testing.T) {
   351  	testcases := [][]string{
   352  		{"ManualOverride1", "Manual_Override1"},
   353  		{"DefaultVar", "Default_Var"},
   354  		{"AutoSplitVar", "Auto_Split_Var"},
   355  		{"SomeID", "Some_ID"},
   356  		{"SomeHTMLWord", "Some_HTML_Word"},
   357  		{"Parent2_SomeID", "Parent2_Some_ID"},
   358  		{"Parent2_SomeHTMLWord", "Parent2_Some_HTML_Word"},
   359  	}
   360  	loader := New(&Config{})
   361  	for _, tc := range testcases {
   362  		name := tc[0]
   363  		want1 := "Confr_" + tc[1]
   364  		got1 := loader.getEnvName("", name)
   365  		assert.Equal(t, want1, got1)
   366  
   367  		want2 := "MyApp_" + tc[1]
   368  		got2 := loader.getEnvName("MyApp", name)
   369  		assert.Equal(t, want2, got2)
   370  	}
   371  }
   372  
   373  func TestLoad_ExplicitEnv(t *testing.T) {
   374  	testEnv := [][]string{
   375  		{"OVERRIDE_ENV_VAR", "54321"},
   376  		{"EXPLICIT_ENV_VAR1", "ExplicitEnvVar1"},
   377  	}
   378  	for _, te := range testEnv {
   379  		_ = os.Setenv(te[0], te[1])
   380  	}
   381  	defer func() {
   382  		for _, te := range testEnv {
   383  			_ = os.Unsetenv(te[0])
   384  		}
   385  	}()
   386  
   387  	configFiles := []string{
   388  		"./testdata/config.test.yml",
   389  		"./testdata/config.common.yml",
   390  	}
   391  	cfg := &TestConfig{}
   392  	loader := New(&Config{
   393  		Verbose:      true,
   394  		CustomLoader: testCustomLoader,
   395  	})
   396  	err := loader.loadFiles(cfg, configFiles...)
   397  	assert.Nil(t, err)
   398  	assertSingleFileConfig(t, cfg)
   399  	err = loader.processCustom(cfg)
   400  	assert.Nil(t, err)
   401  	err = loader.processEnv(cfg, "")
   402  	assert.Nil(t, err)
   403  
   404  	assert.Equal(t, int64(54321), cfg.OverrideEnvVar)
   405  	assert.Equal(t, "ExplicitEnvVar1", cfg.ExplicitEnvVar1)
   406  	assert.Nil(t, cfg.ExplicitEnvVar2)
   407  
   408  	assert.Empty(t, cfg.ImplicitEnvVar1)
   409  	assert.Empty(t, cfg.ImplicitEnvVar2)
   410  	assert.Equal(t, "env_var_override", cfg.ImplicitEnvVarOverride) // from config.common
   411  }
   412  
   413  func TestLoad_ImplicitEnv(t *testing.T) {
   414  	testEnv := [][]string{
   415  		{"Confr_Implicit_Env_Var1", "implicit env var1"},
   416  		{"CONFR_IMPLICIT_ENV_VAR2", "implicit env var2"},
   417  		{"Confr_Implicit_Env_Var_Override", "implicit env var override"},
   418  	}
   419  	for _, te := range testEnv {
   420  		_ = os.Setenv(te[0], te[1])
   421  	}
   422  	defer func() {
   423  		for _, te := range testEnv {
   424  			_ = os.Unsetenv(te[0])
   425  		}
   426  	}()
   427  
   428  	configFiles := []string{
   429  		"./testdata/config.test.yml",
   430  		"./testdata/config.common.yml",
   431  	}
   432  	cfg := &TestConfig{}
   433  	loader := New(&Config{
   434  		Verbose:           true,
   435  		EnableImplicitEnv: true,
   436  		CustomLoader:      testCustomLoader,
   437  	})
   438  	err := loader.loadFiles(cfg, configFiles...)
   439  	assert.Nil(t, err)
   440  	assertSingleFileConfig(t, cfg)
   441  	err = loader.processCustom(cfg)
   442  	assert.Nil(t, err)
   443  	err = loader.processEnv(cfg, "")
   444  	assert.Nil(t, err)
   445  
   446  	assert.Equal(t, "implicit env var1", *cfg.ImplicitEnvVar1)
   447  	assert.Equal(t, "implicit env var2", cfg.ImplicitEnvVar2)
   448  	assert.Equal(t, "implicit env var override", cfg.ImplicitEnvVarOverride)
   449  }
   450  
   451  func TestLoad_AllowUnknownFields_JSON(t *testing.T) {
   452  	configFiles := []string{"./testdata/config.unknown_fields.json"}
   453  	testLoad_AllowUnknownFields(t, configFiles...)
   454  }
   455  
   456  func TestLoad_AllowUnknownFields_TOML(t *testing.T) {
   457  	configFiles := []string{"./testdata/config.unknown_fields.toml"}
   458  	testLoad_AllowUnknownFields(t, configFiles...)
   459  }
   460  
   461  func TestLoad_AllowUnknownFields_YAML(t *testing.T) {
   462  	configFiles := []string{"./testdata/config.unknown_fields.yml"}
   463  	testLoad_AllowUnknownFields(t, configFiles...)
   464  }
   465  
   466  func testLoad_AllowUnknownFields(t *testing.T, files ...string) {
   467  	cfg := &TestConfig{}
   468  	loader := New(&Config{})
   469  	err := loader.Load(cfg, files...)
   470  	assert.Nil(t, err)
   471  }
   472  
   473  func TestLoad_DisallowUnknownFields_JSON(t *testing.T) {
   474  	configFiles := []string{"./testdata/config.unknown_fields.json"}
   475  	testLoad_DisallowUnknownFields(t, configFiles...)
   476  }
   477  
   478  func TestLoad_DisallowUnknownFields_TOML(t *testing.T) {
   479  	configFiles := []string{"./testdata/config.unknown_fields.toml"}
   480  	testLoad_DisallowUnknownFields(t, configFiles...)
   481  }
   482  
   483  func TestLoad_DisallowUnknownFields_YAML(t *testing.T) {
   484  	configFiles := []string{"./testdata/config.unknown_fields.yml"}
   485  	testLoad_DisallowUnknownFields(t, configFiles...)
   486  }
   487  
   488  func testLoad_DisallowUnknownFields(t *testing.T, files ...string) {
   489  	cfg := &TestConfig{}
   490  	loader := New(&Config{DisallowUnknownFields: true})
   491  	err := loader.Load(cfg, files...)
   492  	assert.NotNil(t, err)
   493  	assert.Contains(t, err.Error(), "unknown_field")
   494  }