github.com/rudderlabs/rudder-go-kit@v0.30.0/config/config_test.go (about) 1 package config 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/require" 12 "golang.org/x/sync/errgroup" 13 ) 14 15 func Test_Getters_Existing_and_Default(t *testing.T) { 16 tc := New() 17 tc.Set("string", "string") 18 require.Equal(t, "string", tc.GetString("string", "default"), "it should return the key value") 19 require.Equal(t, "default", tc.GetString("other", "default"), "it should return the default value") 20 21 tc.Set("bool", false) 22 require.Equal(t, false, tc.GetBool("bool", true), "it should return the key value") 23 require.Equal(t, true, tc.GetBool("other", true), "it should return the default value") 24 25 tc.Set("int", 0) 26 require.Equal(t, 0, tc.GetInt("int", 1), "it should return the key value") 27 require.Equal(t, 1, tc.GetInt("other", 1), "it should return the default value") 28 require.EqualValues(t, 0, tc.GetInt64("int", 1), "it should return the key value") 29 require.EqualValues(t, 1, tc.GetInt64("other", 1), "it should return the default value") 30 31 tc.Set("float", 0.0) 32 require.EqualValues(t, 0, tc.GetFloat64("float", 1), "it should return the key value") 33 require.EqualValues(t, 1, tc.GetFloat64("other", 1), "it should return the default value") 34 35 tc.Set("stringslice", []string{"string", "string"}) 36 require.Equal(t, []string{"string", "string"}, tc.GetStringSlice("stringslice", []string{"default"}), "it should return the key value") 37 require.Equal(t, []string{"default"}, tc.GetStringSlice("other", []string{"default"}), "it should return the default value") 38 39 tc.Set("duration", "2ms") 40 require.Equal(t, 2*time.Millisecond, tc.GetDuration("duration", 1, time.Second), "it should return the key value") 41 require.Equal(t, time.Second, tc.GetDuration("other", 1, time.Second), "it should return the default value") 42 43 tc.Set("duration", "2") 44 require.Equal(t, 2*time.Second, tc.GetDuration("duration", 1, time.Second), "it should return the key value") 45 require.Equal(t, time.Second, tc.GetDuration("other", 1, time.Second), "it should return the default value") 46 47 tc.Set("stringmap", map[string]interface{}{"string": "any"}) 48 require.Equal(t, map[string]interface{}{"string": "any"}, tc.GetStringMap("stringmap", map[string]interface{}{"default": "value"}), "it should return the key value") 49 require.Equal(t, map[string]interface{}{"default": "value"}, tc.GetStringMap("other", map[string]interface{}{"default": "value"}), "it should return the default value") 50 } 51 52 func Test_MustGet(t *testing.T) { 53 tc := New() 54 tc.Set("string", "string") 55 require.Equal(t, "string", tc.MustGetString("string"), "it should return the key value") 56 require.Panics(t, func() { tc.MustGetString("other") }) 57 58 tc.Set("int", 0) 59 require.Equal(t, 0, tc.MustGetInt("int"), "it should return the key value") 60 require.Panics(t, func() { tc.MustGetInt("other") }) 61 } 62 63 func Test_Register_Existing_and_Default(t *testing.T) { 64 tc := New() 65 tc.Set("string", "string") 66 var stringValue string 67 var otherStringValue string 68 tc.RegisterStringConfigVariable("default", &stringValue, false, "string") 69 require.Equal(t, "string", stringValue, "it should return the key value") 70 tc.RegisterStringConfigVariable("default", &otherStringValue, false, "other") 71 require.Equal(t, "default", otherStringValue, "it should return the default value") 72 73 tc.Set("bool", false) 74 var boolValue bool 75 var otherBoolValue bool 76 tc.RegisterBoolConfigVariable(true, &boolValue, false, "bool") 77 require.Equal(t, false, boolValue, "it should return the key value") 78 tc.RegisterBoolConfigVariable(true, &otherBoolValue, false, "other") 79 require.Equal(t, true, otherBoolValue, "it should return the default value") 80 81 tc.Set("int", 0) 82 var intValue int 83 var otherIntValue int 84 var int64Value int64 85 var otherInt64Value int64 86 tc.RegisterIntConfigVariable(1, &intValue, false, 1, "int") 87 require.Equal(t, 0, intValue, "it should return the key value") 88 tc.RegisterIntConfigVariable(1, &otherIntValue, false, 1, "other") 89 require.Equal(t, 1, otherIntValue, "it should return the default value") 90 tc.RegisterInt64ConfigVariable(1, &int64Value, false, 1, "int") 91 require.EqualValues(t, 0, int64Value, "it should return the key value") 92 tc.RegisterInt64ConfigVariable(1, &otherInt64Value, false, 1, "other") 93 require.EqualValues(t, 1, otherInt64Value, "it should return the default value") 94 95 tc.Set("float", 0.0) 96 var floatValue float64 97 var otherFloatValue float64 98 tc.RegisterFloat64ConfigVariable(1, &floatValue, false, "float") 99 require.EqualValues(t, 0, floatValue, "it should return the key value") 100 tc.RegisterFloat64ConfigVariable(1, &otherFloatValue, false, "other") 101 require.EqualValues(t, 1, otherFloatValue, "it should return the default value") 102 103 tc.Set("stringslice", []string{"string", "string"}) 104 var stringSliceValue []string 105 var otherStringSliceValue []string 106 tc.RegisterStringSliceConfigVariable([]string{"default"}, &stringSliceValue, false, "stringslice") 107 require.Equal(t, []string{"string", "string"}, stringSliceValue, "it should return the key value") 108 tc.RegisterStringSliceConfigVariable([]string{"default"}, &otherStringSliceValue, false, "other") 109 require.Equal(t, []string{"default"}, otherStringSliceValue, "it should return the default value") 110 111 tc.Set("duration", "2ms") 112 var durationValue time.Duration 113 var otherDurationValue time.Duration 114 tc.RegisterDurationConfigVariable(1, &durationValue, false, time.Second, "duration") 115 require.Equal(t, 2*time.Millisecond, durationValue, "it should return the key value") 116 tc.RegisterDurationConfigVariable(1, &otherDurationValue, false, time.Second, "other") 117 require.Equal(t, time.Second, otherDurationValue, "it should return the default value") 118 119 tc.Set("stringmap", map[string]interface{}{"string": "any"}) 120 var stringMapValue map[string]interface{} 121 var otherStringMapValue map[string]interface{} 122 tc.RegisterStringMapConfigVariable(map[string]interface{}{"default": "value"}, &stringMapValue, false, "stringmap") 123 require.Equal(t, map[string]interface{}{"string": "any"}, stringMapValue, "it should return the key value") 124 tc.RegisterStringMapConfigVariable(map[string]interface{}{"default": "value"}, &otherStringMapValue, false, "other") 125 require.Equal(t, map[string]interface{}{"default": "value"}, otherStringMapValue, "it should return the default value") 126 } 127 128 func TestStatic_checkAndHotReloadConfig(t *testing.T) { 129 configMap := make(map[string][]*configValue) 130 131 var var1 string 132 var var2 string 133 configVar1 := newConfigValue(&var1, 1, "var1", []string{"keyVar"}) 134 configVar2 := newConfigValue(&var2, 1, "var2", []string{"keyVar"}) 135 136 configMap["keyVar"] = []*configValue{configVar1, configVar2} 137 t.Setenv("RSERVER_KEY_VAR", "value_changed") 138 139 Default.checkAndHotReloadConfig(configMap) 140 141 varptr1 := configVar1.value.(*string) 142 varptr2 := configVar2.value.(*string) 143 require.Equal(t, *varptr1, "value_changed") 144 require.Equal(t, *varptr2, "value_changed") 145 } 146 147 func TestCheckAndHotReloadConfig(t *testing.T) { 148 var ( 149 stringValue string 150 stringConfigValue = newConfigValue(&stringValue, nil, "default", []string{"string"}) 151 boolValue bool 152 boolConfigValue = newConfigValue(&boolValue, nil, false, []string{"bool"}) 153 intValue int 154 intConfigValue = newConfigValue(&intValue, 1, 0, []string{"int"}) 155 int64Value int64 156 int64ConfigValue = newConfigValue(&int64Value, int64(1), int64(0), []string{"int64"}) 157 float64Value float64 158 float64ConfigValue = newConfigValue(&float64Value, 1.0, 0.0, []string{"float64"}) 159 stringSliceValue []string 160 stringSliceConfigValue = newConfigValue(&stringSliceValue, nil, []string{"default"}, []string{"stringslice"}) 161 durationValue time.Duration 162 durationConfigValue = newConfigValue(&durationValue, time.Second, int64(1), []string{"duration"}) 163 stringMapValue map[string]interface{} 164 stringMapConfigValue = newConfigValue(&stringMapValue, nil, map[string]interface{}{"default": "value"}, []string{"stringmap"}) 165 ) 166 167 t.Run("with envs", func(t *testing.T) { 168 t.Setenv("RSERVER_INT", "1") 169 t.Setenv("RSERVER_INT64", "1") 170 t.Setenv("RSERVER_STRING", "string") 171 t.Setenv("RSERVER_DURATION", "2s") 172 t.Setenv("RSERVER_BOOL", "true") 173 t.Setenv("RSERVER_FLOAT64", "1.0") 174 t.Setenv("RSERVER_STRINGSLICE", "string string") 175 t.Setenv("RSERVER_STRINGMAP", "{\"string\":\"any\"}") 176 177 Default.checkAndHotReloadConfig(map[string][]*configValue{ 178 "string": {stringConfigValue}, 179 "bool": {boolConfigValue}, 180 "int": {intConfigValue}, 181 "int64": {int64ConfigValue}, 182 "float64": {float64ConfigValue}, 183 "stringslice": {stringSliceConfigValue}, 184 "duration": {durationConfigValue}, 185 "stringmap": {stringMapConfigValue}, 186 }) 187 188 require.Equal(t, *stringConfigValue.value.(*string), "string") 189 require.Equal(t, *boolConfigValue.value.(*bool), true) 190 require.Equal(t, *intConfigValue.value.(*int), 1) 191 require.Equal(t, *int64ConfigValue.value.(*int64), int64(1)) 192 require.Equal(t, *float64ConfigValue.value.(*float64), 1.0) 193 require.Equal(t, *durationConfigValue.value.(*time.Duration), 2*time.Second) 194 require.Equal(t, *stringSliceConfigValue.value.(*[]string), []string{"string", "string"}) 195 require.Equal(t, *stringMapConfigValue.value.(*map[string]any), map[string]any{"string": "any"}) 196 }) 197 198 t.Run("without envs", func(t *testing.T) { 199 Default.checkAndHotReloadConfig(map[string][]*configValue{ 200 "string": {stringConfigValue}, 201 "bool": {boolConfigValue}, 202 "int": {intConfigValue}, 203 "int64": {int64ConfigValue}, 204 "float64": {float64ConfigValue}, 205 "stringslice": {stringSliceConfigValue}, 206 "duration": {durationConfigValue}, 207 "stringmap": {stringMapConfigValue}, 208 }) 209 210 require.Equal(t, *stringConfigValue.value.(*string), "default") 211 require.Equal(t, *boolConfigValue.value.(*bool), false) 212 require.Equal(t, *intConfigValue.value.(*int), 0) 213 require.Equal(t, *int64ConfigValue.value.(*int64), int64(0)) 214 require.Equal(t, *float64ConfigValue.value.(*float64), 0.0) 215 require.Equal(t, *durationConfigValue.value.(*time.Duration), 1*time.Second) 216 require.Equal(t, *stringSliceConfigValue.value.(*[]string), []string{"default"}) 217 require.Equal(t, *stringMapConfigValue.value.(*map[string]any), map[string]any{"default": "value"}) 218 }) 219 } 220 221 func TestNewReloadableAPI(t *testing.T) { 222 t.Run("non reloadable", func(t *testing.T) { 223 t.Run("int", func(t *testing.T) { 224 c := New() 225 v := c.GetIntVar(5, 1, t.Name()) 226 require.Equal(t, 5, v) 227 }) 228 t.Run("int64", func(t *testing.T) { 229 c := New() 230 v := c.GetInt64Var(5, 1, t.Name()) 231 require.EqualValues(t, 5, v) 232 }) 233 t.Run("bool", func(t *testing.T) { 234 c := New() 235 v := c.GetBoolVar(true, t.Name()) 236 require.True(t, v) 237 }) 238 t.Run("float64", func(t *testing.T) { 239 c := New() 240 v := c.GetFloat64Var(0.123, t.Name()) 241 require.EqualValues(t, 0.123, v) 242 }) 243 t.Run("string", func(t *testing.T) { 244 c := New() 245 v := c.GetStringVar("foo", t.Name()) 246 require.Equal(t, "foo", v) 247 }) 248 t.Run("duration", func(t *testing.T) { 249 c := New() 250 v := c.GetDurationVar(123, time.Second, t.Name()) 251 require.Equal(t, 123*time.Second, v) 252 }) 253 t.Run("[]string", func(t *testing.T) { 254 c := New() 255 v := c.GetStringSliceVar([]string{"a", "b"}, t.Name()) 256 require.NotNil(t, v) 257 require.Equal(t, []string{"a", "b"}, v) 258 259 c.Set(t.Name(), []string{"c", "d"}) 260 require.Equal(t, []string{"a", "b"}, v, "variable is not reloadable") 261 }) 262 t.Run("map[string]interface{}", func(t *testing.T) { 263 c := New() 264 v := c.GetStringMapVar(map[string]interface{}{"a": 1, "b": 2}, t.Name()) 265 require.NotNil(t, v) 266 require.Equal(t, map[string]interface{}{"a": 1, "b": 2}, v) 267 268 c.Set(t.Name(), map[string]interface{}{"c": 3, "d": 4}) 269 require.Equal(t, map[string]interface{}{"a": 1, "b": 2}, v, "variable is not reloadable") 270 }) 271 }) 272 t.Run("reloadable", func(t *testing.T) { 273 t.Run("int", func(t *testing.T) { 274 c := New() 275 v := c.GetReloadableIntVar(5, 1, t.Name()) 276 require.Equal(t, 5, v.Load()) 277 278 c.Set(t.Name(), 10) 279 require.Equal(t, 10, v.Load()) 280 281 c.Set(t.Name(), 10) 282 require.Equal(t, 10, v.Load(), "value should not change") 283 284 require.PanicsWithError(t, 285 "Detected misuse of config variable registered with different default values "+ 286 "int:TestNewReloadableAPI/reloadable/int:5 - "+ 287 "int:TestNewReloadableAPI/reloadable/int:10\n", 288 func() { 289 // changing just the valueScale also changes the default value 290 _ = c.GetReloadableIntVar(5, 2, t.Name()) 291 }, 292 ) 293 }) 294 t.Run("int64", func(t *testing.T) { 295 c := New() 296 v := c.GetReloadableInt64Var(5, 1, t.Name()) 297 require.EqualValues(t, 5, v.Load()) 298 299 c.Set(t.Name(), 10) 300 require.EqualValues(t, 10, v.Load()) 301 302 c.Set(t.Name(), 10) 303 require.EqualValues(t, 10, v.Load(), "value should not change") 304 305 require.PanicsWithError(t, 306 "Detected misuse of config variable registered with different default values "+ 307 "int64:TestNewReloadableAPI/reloadable/int64:5 - "+ 308 "int64:TestNewReloadableAPI/reloadable/int64:10\n", 309 func() { 310 // changing just the valueScale also changes the default value 311 _ = c.GetReloadableInt64Var(5, 2, t.Name()) 312 }, 313 ) 314 }) 315 t.Run("bool", func(t *testing.T) { 316 c := New() 317 v := c.GetReloadableBoolVar(true, t.Name()) 318 require.True(t, v.Load()) 319 320 c.Set(t.Name(), false) 321 require.False(t, v.Load()) 322 323 c.Set(t.Name(), false) 324 require.False(t, v.Load(), "value should not change") 325 326 require.PanicsWithError(t, 327 "Detected misuse of config variable registered with different default values "+ 328 "bool:TestNewReloadableAPI/reloadable/bool:true - "+ 329 "bool:TestNewReloadableAPI/reloadable/bool:false\n", 330 func() { 331 _ = c.GetReloadableBoolVar(false, t.Name()) 332 }, 333 ) 334 }) 335 t.Run("float64", func(t *testing.T) { 336 c := New() 337 v := c.GetReloadableFloat64Var(0.123, t.Name()) 338 require.EqualValues(t, 0.123, v.Load()) 339 340 c.Set(t.Name(), 4.567) 341 require.EqualValues(t, 4.567, v.Load()) 342 343 c.Set(t.Name(), 4.567) 344 require.EqualValues(t, 4.567, v.Load(), "value should not change") 345 346 require.PanicsWithError(t, 347 "Detected misuse of config variable registered with different default values "+ 348 "float64:TestNewReloadableAPI/reloadable/float64:0.123 - "+ 349 "float64:TestNewReloadableAPI/reloadable/float64:0.1234\n", 350 func() { 351 _ = c.GetReloadableFloat64Var(0.1234, t.Name()) 352 }, 353 ) 354 }) 355 t.Run("string", func(t *testing.T) { 356 c := New() 357 v := c.GetReloadableStringVar("foo", t.Name()) 358 require.Equal(t, "foo", v.Load()) 359 360 c.Set(t.Name(), "bar") 361 require.EqualValues(t, "bar", v.Load()) 362 363 c.Set(t.Name(), "bar") 364 require.EqualValues(t, "bar", v.Load(), "value should not change") 365 366 require.PanicsWithError(t, 367 "Detected misuse of config variable registered with different default values "+ 368 "string:TestNewReloadableAPI/reloadable/string:foo - "+ 369 "string:TestNewReloadableAPI/reloadable/string:qux\n", 370 func() { 371 _ = c.GetReloadableStringVar("qux", t.Name()) 372 }, 373 ) 374 }) 375 t.Run("duration", func(t *testing.T) { 376 c := New() 377 v := c.GetReloadableDurationVar(123, time.Nanosecond, t.Name()) 378 require.Equal(t, 123*time.Nanosecond, v.Load()) 379 380 c.Set(t.Name(), 456*time.Millisecond) 381 require.Equal(t, 456*time.Millisecond, v.Load()) 382 383 c.Set(t.Name(), 456*time.Millisecond) 384 require.Equal(t, 456*time.Millisecond, v.Load(), "value should not change") 385 386 require.PanicsWithError(t, 387 "Detected misuse of config variable registered with different default values "+ 388 "time.Duration:TestNewReloadableAPI/reloadable/duration:123ns - "+ 389 "time.Duration:TestNewReloadableAPI/reloadable/duration:2m3s\n", 390 func() { 391 _ = c.GetReloadableDurationVar(123, time.Second, t.Name()) 392 }, 393 ) 394 }) 395 t.Run("[]string", func(t *testing.T) { 396 c := New() 397 v := c.GetReloadableStringSliceVar([]string{"a", "b"}, t.Name()) 398 require.Equal(t, []string{"a", "b"}, v.Load()) 399 400 c.Set(t.Name(), []string{"c", "d"}) 401 require.Equal(t, []string{"c", "d"}, v.Load()) 402 403 c.Set(t.Name(), []string{"c", "d"}) 404 require.Equal(t, []string{"c", "d"}, v.Load(), "value should not change") 405 406 require.PanicsWithError(t, 407 "Detected misuse of config variable registered with different default values "+ 408 "[]string:TestNewReloadableAPI/reloadable/[]string:[a b] - "+ 409 "[]string:TestNewReloadableAPI/reloadable/[]string:[a b c]\n", 410 func() { 411 _ = c.GetReloadableStringSliceVar([]string{"a", "b", "c"}, t.Name()) 412 }, 413 ) 414 }) 415 t.Run("map[string]interface{}", func(t *testing.T) { 416 c := New() 417 v := c.GetReloadableStringMapVar(map[string]interface{}{"a": 1, "b": 2}, t.Name()) 418 require.Equal(t, map[string]interface{}{"a": 1, "b": 2}, v.Load()) 419 420 c.Set(t.Name(), map[string]interface{}{"c": 3, "d": 4}) 421 require.Equal(t, map[string]interface{}{"c": 3, "d": 4}, v.Load()) 422 423 c.Set(t.Name(), map[string]interface{}{"c": 3, "d": 4}) 424 require.Equal(t, map[string]interface{}{"c": 3, "d": 4}, v.Load(), "value should not change") 425 426 require.PanicsWithError(t, 427 "Detected misuse of config variable registered with different default values "+ 428 "map[string]interface {}:TestNewReloadableAPI/reloadable/map[string]interface{}:map[a:1 b:2] - "+ 429 "map[string]interface {}:TestNewReloadableAPI/reloadable/map[string]interface{}:map[a:2 b:1]\n", 430 func() { 431 _ = c.GetReloadableStringMapVar(map[string]interface{}{"a": 2, "b": 1}, t.Name()) 432 }, 433 ) 434 }) 435 }) 436 } 437 438 func TestGetOrCreatePointer(t *testing.T) { 439 var ( 440 m = make(map[string]any) 441 dvs = make(map[string]string) 442 rwm sync.RWMutex 443 ) 444 p1, exists := getOrCreatePointer(m, dvs, &rwm, 123, "foo", "bar") 445 require.NotNil(t, p1) 446 require.False(t, exists) 447 448 p2, exists := getOrCreatePointer(m, dvs, &rwm, 123, "foo", "bar") 449 require.True(t, p1 == p2) 450 require.True(t, exists) 451 452 p3, exists := getOrCreatePointer(m, dvs, &rwm, 123, "bar", "foo") 453 require.True(t, p1 != p3) 454 require.False(t, exists) 455 456 p4, exists := getOrCreatePointer(m, dvs, &rwm, 123, "bar", "foo", "qux") 457 require.True(t, p1 != p4) 458 require.False(t, exists) 459 460 require.PanicsWithError(t, 461 "Detected misuse of config variable registered with different default values "+ 462 "int:bar,foo,qux:123 - int:bar,foo,qux:456\n", 463 func() { 464 getOrCreatePointer(m, dvs, &rwm, 456, "bar", "foo", "qux") 465 }, 466 ) 467 } 468 469 func TestReloadable(t *testing.T) { 470 t.Run("scalar", func(t *testing.T) { 471 var v Reloadable[int] 472 v.store(123) 473 require.Equal(t, 123, v.Load()) 474 }) 475 t.Run("nullable", func(t *testing.T) { 476 var v Reloadable[[]string] 477 require.Nil(t, v.Load()) 478 479 v.store(nil) 480 require.Nil(t, v.Load()) 481 482 v.store([]string{"foo", "bar"}) 483 require.Equal(t, v.Load(), []string{"foo", "bar"}) 484 485 v.store(nil) 486 require.Nil(t, v.Load()) 487 }) 488 } 489 490 func TestConfigKeyToEnv(t *testing.T) { 491 expected := "RSERVER_KEY_VAR1_VAR2" 492 require.Equal(t, expected, ConfigKeyToEnv(DefaultEnvPrefix, "Key.Var1.Var2")) 493 require.Equal(t, expected, ConfigKeyToEnv(DefaultEnvPrefix, "key.var1.var2")) 494 require.Equal(t, expected, ConfigKeyToEnv(DefaultEnvPrefix, "KeyVar1Var2")) 495 require.Equal(t, expected, ConfigKeyToEnv(DefaultEnvPrefix, "RSERVER_KEY_VAR1_VAR2")) 496 require.Equal(t, "KEY_VAR1_VAR2", ConfigKeyToEnv(DefaultEnvPrefix, "KEY_VAR1_VAR2")) 497 } 498 499 func TestGetEnvThroughViper(t *testing.T) { 500 expectedValue := "VALUE" 501 502 t.Run("detects dots", func(t *testing.T) { 503 t.Setenv("RSERVER_KEY_VAR1_VAR2", expectedValue) 504 tc := New() 505 require.Equal(t, expectedValue, tc.GetString("Key.Var1.Var2", "")) 506 }) 507 508 t.Run("detects camelcase", func(t *testing.T) { 509 t.Setenv("RSERVER_KEY_VAR1_VAR2", expectedValue) 510 tc := New() 511 require.Equal(t, expectedValue, tc.GetString("KeyVar1Var2", "")) 512 }) 513 514 t.Run("detects dots with camelcase", func(t *testing.T) { 515 t.Setenv("RSERVER_KEY_VAR1_VAR_VAR", expectedValue) 516 tc := New() 517 require.Equal(t, expectedValue, tc.GetString("Key.Var1VarVar", "")) 518 }) 519 520 t.Run("with env prefix", func(t *testing.T) { 521 envPrefix := "ANOTHER_PREFIX" 522 523 tc := New(WithEnvPrefix(envPrefix)) 524 t.Setenv(envPrefix+"_KEY_VAR1_VAR_VAR", expectedValue) 525 526 require.Equal(t, expectedValue, tc.GetString("Key.Var1VarVar", "")) 527 }) 528 529 t.Run("detects uppercase env variables", func(t *testing.T) { 530 t.Setenv("SOMEENVVARIABLE", expectedValue) 531 tc := New() 532 require.Equal(t, expectedValue, tc.GetString("SOMEENVVARIABLE", "")) 533 534 t.Setenv("SOME_ENV_VARIABLE", expectedValue) 535 require.Equal(t, expectedValue, tc.GetString("SOME_ENV_VARIABLE", "")) 536 537 t.Setenv("SOME_ENV_VARIABLE12", expectedValue) 538 require.Equal(t, expectedValue, tc.GetString("SOME_ENV_VARIABLE12", "")) 539 }) 540 541 t.Run("doesn't use viper's default env var matcher (uppercase)", func(t *testing.T) { 542 t.Setenv("KEYVAR1VARVAR", expectedValue) 543 tc := New() 544 require.Equal(t, "", tc.GetString("KeyVar1VarVar", "")) 545 }) 546 547 t.Run("can retrieve legacy env", func(t *testing.T) { 548 t.Setenv("JOBS_DB_HOST", expectedValue) 549 tc := New() 550 require.Equal(t, expectedValue, tc.GetString("DB.host", "")) 551 }) 552 } 553 554 func TestRegisterEnvThroughViper(t *testing.T) { 555 expectedValue := "VALUE" 556 557 t.Run("detects dots", func(t *testing.T) { 558 t.Setenv("RSERVER_KEY_VAR1_VAR2", expectedValue) 559 tc := New() 560 var v string 561 tc.RegisterStringConfigVariable("", &v, true, "Key.Var1.Var2") 562 require.Equal(t, expectedValue, v) 563 }) 564 565 t.Run("detects camelcase", func(t *testing.T) { 566 t.Setenv("RSERVER_KEY_VAR_VAR", expectedValue) 567 tc := New() 568 var v string 569 tc.RegisterStringConfigVariable("", &v, true, "KeyVarVar") 570 require.Equal(t, expectedValue, v) 571 }) 572 573 t.Run("detects dots with camelcase", func(t *testing.T) { 574 t.Setenv("RSERVER_KEY_VAR1_VAR_VAR", expectedValue) 575 tc := New() 576 var v string 577 tc.RegisterStringConfigVariable("", &v, true, "Key.Var1VarVar") 578 require.Equal(t, expectedValue, v) 579 }) 580 } 581 582 func Test_Set_CaseInsensitive(t *testing.T) { 583 tc := New() 584 tc.Set("sTrIng.One", "string") 585 require.Equal(t, "string", tc.GetString("String.one", "default"), "it should return the key value") 586 } 587 588 func Test_Misc(t *testing.T) { 589 t.Setenv("KUBE_NAMESPACE", "value") 590 require.Equal(t, "value", GetKubeNamespace()) 591 592 t.Setenv("KUBE_NAMESPACE", "") 593 require.Equal(t, "none", GetNamespaceIdentifier()) 594 595 t.Setenv("WORKSPACE_TOKEN", "value1") 596 t.Setenv("CONFIG_BACKEND_TOKEN", "value2") 597 require.Equal(t, "value1", GetWorkspaceToken()) 598 599 t.Setenv("WORKSPACE_TOKEN", "") 600 t.Setenv("CONFIG_BACKEND_TOKEN", "value2") 601 require.Equal(t, "value2", GetWorkspaceToken()) 602 603 t.Setenv("RELEASE_NAME", "value") 604 require.Equal(t, "value", GetReleaseName()) 605 } 606 607 func TestConfigLocking(t *testing.T) { 608 const ( 609 timeout = 2 * time.Second 610 configKey = "test" 611 ) 612 c := New() 613 ctx, cancel := context.WithCancel(context.Background()) 614 defer cancel() 615 g, ctx := errgroup.WithContext(ctx) 616 617 doWithTimeout := func(f func(), timeout time.Duration) error { 618 var err error 619 var closed bool 620 var closedMutex sync.Mutex 621 wait := make(chan struct{}) 622 t := time.NewTimer(timeout) 623 624 go func() { 625 <-t.C 626 err = fmt.Errorf("timeout after %s", timeout) 627 closedMutex.Lock() 628 if !closed { 629 closed = true 630 close(wait) 631 } 632 closedMutex.Unlock() 633 }() 634 635 go func() { 636 f() 637 t.Stop() 638 closedMutex.Lock() 639 if !closed { 640 closed = true 641 close(wait) 642 } 643 closedMutex.Unlock() 644 }() 645 <-wait 646 return err 647 } 648 649 startOperation := func(name string, op func()) { 650 g.Go(func() error { 651 for { 652 select { 653 case <-ctx.Done(): 654 return nil 655 default: 656 if err := doWithTimeout(op, timeout); err != nil { 657 return fmt.Errorf("%s: %w", name, err) 658 } 659 } 660 } 661 }) 662 } 663 664 startOperation("set the config value", func() { c.Set(configKey, "value1") }) 665 startOperation("try to read the config value using GetString", func() { _ = c.GetString(configKey, "") }) 666 startOperation("try to read the config value using GetStringVar", func() { _ = c.GetStringVar(configKey, "") }) 667 startOperation("try to read the config value using GetReloadableStringVar", func() { 668 r := c.GetReloadableStringVar("", configKey) 669 _ = r.Load() 670 }) 671 672 g.Go(func() error { 673 select { 674 case <-ctx.Done(): 675 return nil 676 case <-time.After(5 * time.Second): 677 cancel() 678 return nil 679 } 680 }) 681 682 require.NoError(t, g.Wait()) 683 } 684 685 func TestConfigLoad(t *testing.T) { 686 // create a temporary file: 687 f, err := os.CreateTemp("", "*config.yaml") 688 require.NoError(t, err) 689 defer os.Remove(f.Name()) 690 691 t.Setenv("CONFIG_PATH", f.Name()) 692 c := New() 693 require.NoError(t, err) 694 695 t.Run("successfully loaded config file", func(t *testing.T) { 696 configFile, err := c.ConfigFileUsed() 697 require.NoError(t, err) 698 require.Equal(t, f.Name(), configFile) 699 }) 700 701 err = os.Remove(f.Name()) 702 require.NoError(t, err) 703 704 t.Run("attempt to load non-existent config file", func(t *testing.T) { 705 c := New() 706 configFile, err := c.ConfigFileUsed() 707 require.Error(t, err) 708 require.Equal(t, f.Name(), configFile) 709 }) 710 711 t.Run("dot env error", func(t *testing.T) { 712 c := New() 713 err := c.DotEnvLoaded() 714 require.Error(t, err) 715 }) 716 717 t.Run("dot env found", func(t *testing.T) { 718 c := New() 719 f, err := os.Create(".env") 720 require.NoError(t, err) 721 defer os.Remove(f.Name()) 722 723 err = c.DotEnvLoaded() 724 require.Error(t, err) 725 }) 726 }