github.com/fluffy-bunny/viperEx@v0.0.32/surgical-update_test.go (about) 1 // Copyright © 2020 Herb Stahl <ghstahl@gmail.com>. 2 // 3 // Use of this source code is governed by an MIT-style 4 // license that can be found in the LICENSE file. 5 6 // ViperEx adds some missing gap items from the awesome Viper project is a application configuration system. 7 8 package viperEx 9 10 import ( 11 "encoding/json" 12 "fmt" 13 "os" 14 "path" 15 "path/filepath" 16 "runtime" 17 "testing" 18 19 "github.com/rs/zerolog/log" 20 "github.com/spf13/viper" 21 "github.com/stretchr/testify/assert" 22 ) 23 24 type NestedMap struct { 25 Eggs map[string]Egg 26 } 27 type SettingsWithNestedMap struct { 28 Name string 29 NestedMap *NestedMap 30 MasterEgg Egg 31 } 32 type Nest struct { 33 Name string 34 CountInt int 35 CountInt16 int16 36 MasterEgg Egg 37 Eggs []Egg 38 Tags []string 39 } 40 type ValueContainer struct { 41 Value interface{} `json:"value"` 42 } 43 44 func (vc *ValueContainer) GetString() (string, bool) { 45 value, ok := vc.Value.(string) 46 return value, ok 47 } 48 49 type Egg struct { 50 Weight int32 `json:"weight"` 51 SomeValues []ValueContainer `json:"somevalues"` 52 SomeStrings []string `json:"somestrings"` 53 Name string `json:"name"` 54 } 55 type Settings struct { 56 Name string 57 Nest *Nest 58 SomeStrings []string 59 } 60 61 func init() { 62 chdirToTestFolder() 63 } 64 65 const keyDelim = "__" 66 67 func ReadAppsettings(rootPath string) (*viper.Viper, error) { 68 var err error 69 environment := os.Getenv("APPLICATION_ENVIRONMENT") 70 myViper := viper.NewWithOptions(viper.KeyDelimiter(keyDelim)) 71 // Environment Variables override everything. 72 myViper.AutomaticEnv() 73 myViper.SetConfigType("json") 74 configFile := "appsettings.json" 75 configPath := path.Join(rootPath, configFile) 76 viper.SetConfigFile(configPath) 77 err = myViper.ReadInConfig() 78 if err == nil { 79 return nil, err 80 } 81 configFile = "appsettings." + environment + ".json" 82 myViper.SetConfigFile(configPath) 83 err = myViper.MergeInConfig() 84 return myViper, err 85 } 86 87 func getConfigPath() string { 88 var configPath string 89 _, err := os.Stat("./settings") 90 if !os.IsNotExist(err) { 91 configPath, _ = filepath.Abs("./settings") 92 log.Info().Str("path", configPath).Msg("Configuration Root Folder") 93 } 94 return configPath 95 } 96 func chdirToTestFolder() { 97 _, filename, _, _ := runtime.Caller(0) 98 // The ".." may change depending on you folder structure 99 dir := path.Join(path.Dir(filename), ".") 100 fmt.Println(filename) 101 fmt.Println(dir) 102 err := os.Chdir(dir) 103 if err != nil { 104 panic(err) 105 } 106 } 107 108 func TestViperExEnvUpdate(t *testing.T) { 109 configPath := getConfigPath() 110 myViper, err := ReadAppsettings(configPath) 111 assert.NoError(t, err) 112 if err != nil { 113 panic(err) 114 } 115 allSettings := myViper.AllSettings() 116 fmt.Println(PrettyJSON(allSettings)) 117 118 myViperEx, err := New(allSettings, func(ve *ViperEx) error { 119 ve.KeyDelimiter = keyDelim 120 return nil 121 }) 122 expectedURI := "https://www.blah.com/?ssl=true&a=b&c=d" 123 envs := map[string]string{ 124 125 "APPLICATION_ENVIRONMENT": "Test", 126 "nest__Eggs__1__Weight": "5555", 127 "nest__Eggs__1__SomeValues__1__Value": "Heidi", 128 "nest__Eggs__1__SomeStrings__1": "Zep", 129 "nest__masteregg__name": expectedURI, 130 } 131 for k, v := range envs { 132 os.Setenv(k, v) 133 } 134 os.Setenv("APPLICATION_ENVIRONMENT", "Test") 135 136 err = myViperEx.UpdateFromEnv() 137 fmt.Println(PrettyJSON(allSettings)) 138 assert.NoError(t, err) 139 if err != nil { 140 panic(err) 141 } 142 for k := range envs { 143 os.Remove(k) 144 } 145 146 settings := Settings{} 147 err = myViperEx.Unmarshal(&settings) 148 fmt.Println(PrettyJSON(allSettings)) 149 assert.NoError(t, err) 150 if err != nil { 151 panic(err) 152 } 153 154 assert.Equal(t, expectedURI, settings.Nest.MasterEgg.Name) 155 assert.Equal(t, "straw", settings.Nest.Name) 156 assert.Equal(t, "Heidi", settings.Nest.Eggs[1].SomeValues[1].Value) 157 assert.Equal(t, "Zep", settings.Nest.Eggs[1].SomeStrings[1]) 158 assert.Equal(t, int32(5555), settings.Nest.Eggs[1].Weight) 159 } 160 func TestViperSurgicalUpdate_URIWithArgs(t *testing.T) { 161 configPath := getConfigPath() 162 myViper, err := ReadAppsettings(configPath) 163 assert.NoError(t, err) 164 if err != nil { 165 panic(err) 166 } 167 allSettings := myViper.AllSettings() 168 fmt.Println(PrettyJSON(allSettings)) 169 myViperEx, err := New(allSettings, func(ve *ViperEx) error { 170 ve.KeyDelimiter = "__" 171 return nil 172 }) 173 expectedURI := "https://www.blah.com/?ssl=true&a=b&c=d" 174 envs := map[string]interface{}{ 175 "name": expectedURI, 176 "nestedMap__Eggs__bob__SomeValues__1__Value": expectedURI, 177 "MasterEgg__name": expectedURI, 178 } 179 for k, v := range envs { 180 myViperEx.UpdateDeepPath(k, v) 181 } 182 fmt.Println(PrettyJSON(allSettings)) 183 184 settings := SettingsWithNestedMap{} 185 err = myViperEx.Unmarshal(&settings) 186 assert.NoError(t, err) 187 if err != nil { 188 panic(err) 189 } 190 assert.Equal(t, expectedURI, settings.Name) 191 assert.Equal(t, expectedURI, settings.NestedMap.Eggs["bob"].SomeValues[1].Value) 192 assert.Equal(t, expectedURI, settings.MasterEgg.Name) 193 } 194 195 func TestViperSurgicalUpdate_NestedMap(t *testing.T) { 196 configPath := getConfigPath() 197 myViper, err := ReadAppsettings(configPath) 198 assert.NoError(t, err) 199 if err != nil { 200 panic(err) 201 } 202 allSettings := myViper.AllSettings() 203 fmt.Println(PrettyJSON(allSettings)) 204 205 myViperEx, err := New(allSettings, func(ve *ViperEx) error { 206 ve.KeyDelimiter = "__" 207 return nil 208 }) 209 210 envs := map[string]interface{}{ 211 "name": "bowie", 212 "nestedMap__Eggs__bob__Weight": 1234, 213 "nestedMap__Eggs__bob__Weight__": 1234, 214 "nestedMap__Eggs__bob__SomeValues__1__Value": "abcd", 215 "nestedMap__Eggs__bob__SomeStrings__1": "abcd", 216 "nestedMap__Eggs__bob__SomeStrings__1__": "abcd", 217 "nestedMap__Eggs__junk__SomeStrings__1__": "abcd", 218 "nestedMap__Eggs__junk__SomeStrings__1__Value": "abcd", 219 } 220 for k, v := range envs { 221 myViperEx.UpdateDeepPath(k, v) 222 } 223 224 fmt.Println(PrettyJSON(allSettings)) 225 226 settings := SettingsWithNestedMap{} 227 err = myViperEx.Unmarshal(&settings) 228 assert.NoError(t, err) 229 if err != nil { 230 panic(err) 231 } 232 assert.Equal(t, "bowie", settings.Name) 233 assert.Equal(t, int32(1234), settings.NestedMap.Eggs["bob"].Weight) 234 assert.Equal(t, "abcd", settings.NestedMap.Eggs["bob"].SomeValues[1].Value) 235 assert.Equal(t, "abcd", settings.NestedMap.Eggs["bob"].SomeStrings[1]) 236 _, ok := allSettings["junk"] 237 assert.False(t, ok) 238 239 name := myViperEx.Find("name") 240 assert.NotNil(t, name) 241 242 nestJunk := myViperEx.Find("nestedMap__Eggs__junk") 243 assert.Nil(t, nestJunk) 244 245 nestJunk = myViperEx.Find("nestedMap__Eggs__junk__SomeStrings__1__Value") 246 assert.Nil(t, nestJunk) 247 } 248 func TestViperSurgicalUpdate(t *testing.T) { 249 configPath := getConfigPath() 250 myViper, err := ReadAppsettings(configPath) 251 assert.NoError(t, err) 252 if err != nil { 253 panic(err) 254 } 255 allSettings := myViper.AllSettings() 256 fmt.Println(PrettyJSON(allSettings)) 257 258 myViperEx, err := New(allSettings, func(ve *ViperEx) error { 259 ve.KeyDelimiter = "__" 260 return nil 261 }) 262 envs := map[string]interface{}{ 263 "nest__MasterEgg__Weight": 1, 264 "nest__Eggs__0__Weight": 1234, 265 "nest__Eggs__0__Weight__": 1234, 266 "nest__Eggs__0__SomeValues__1__Value": "abcd", 267 "nest__Eggs__0__SomeStrings__1": "abcd", 268 "nest__Eggs__0__SomeStrings__1__": "abcd", 269 "junk__A": "abcd", 270 "nest__junk": "abcd", 271 "nest__tags__0": "abcd", 272 "nest__tags__1": "abcd", 273 "somestrings__0": "abcd", 274 } 275 for k, v := range envs { 276 myViperEx.UpdateDeepPath(k, v) 277 } 278 279 fmt.Println(PrettyJSON(allSettings)) 280 281 settings := Settings{} 282 err = myViperEx.Unmarshal(&settings) 283 assert.NoError(t, err) 284 if err != nil { 285 panic(err) 286 } 287 assert.Equal(t, "abcd", settings.SomeStrings[0]) 288 assert.Equal(t, "bob", settings.Name) 289 assert.Equal(t, int32(1), settings.Nest.MasterEgg.Weight) 290 assert.Equal(t, int32(1234), settings.Nest.Eggs[0].Weight) 291 assert.Equal(t, "abcd", settings.Nest.Eggs[0].SomeValues[1].Value) 292 assert.Equal(t, "abcd", settings.Nest.Eggs[0].SomeStrings[1]) 293 assert.Equal(t, "abcd", settings.Nest.Tags[0]) 294 _, ok := allSettings["junk"] 295 assert.False(t, ok) 296 297 name := myViperEx.Find("name") 298 assert.NotNil(t, name) 299 300 nestJunk := myViperEx.Find("nest__junk") 301 assert.Nil(t, nestJunk) 302 303 var item interface{} 304 305 item = myViperEx.Find("nest__Eggs") 306 assert.NotNil(t, item) 307 308 item = myViperEx.Find("nest__Eggs") 309 assert.NotNil(t, item) 310 311 item = myViperEx.Find("nest__Eggs__") 312 assert.Nil(t, item) 313 314 item = myViperEx.Find("nest__Eggs__0__SomeStrings__1") 315 assert.NotNil(t, item) 316 317 item = myViperEx.Find("nest__Eggs__0__SomeStrings__1__") 318 assert.Nil(t, item) 319 320 item = myViperEx.Find("nest__Eggs__0__Junk__1") 321 assert.Nil(t, item) 322 323 item = myViperEx.Find("nest__junk__0__Junk__1") 324 assert.Nil(t, item) 325 326 item = myViperEx.Find("nest__junk__0") 327 assert.Nil(t, item) 328 } 329 330 // PrettyJSON to string 331 func PrettyJSON(obj interface{}) string { 332 jsonBytes, err := json.MarshalIndent(obj, "", " ") 333 if err != nil { 334 panic(err) 335 } 336 return string(jsonBytes) 337 } 338 339 // JSON from object 340 func JSON(obj interface{}) string { 341 jsonBytes, err := json.Marshal(obj) 342 if err != nil { 343 panic(err) 344 } 345 return string(jsonBytes) 346 }