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  }