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 }