Skip to content

Commit

Permalink
Tests for overriding nested values
Browse files Browse the repository at this point in the history
In function TestNestedOverrides(), in file overrides_test.go

More involved complement to TestOverrides() in viper_test.go,
as suggested in Issue #168 (#168)
  • Loading branch information
benoitmasson committed Jun 3, 2016
1 parent 86b56e8 commit f2dc934
Showing 1 changed file with 153 additions and 0 deletions.
153 changes: 153 additions & 0 deletions overrides_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package viper

import (
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func TestNestedOverrides(t *testing.T) {
assert := assert.New(t)
var v *Viper

// Case 0: value overridden by a value
overrideDefault(assert, "tom", 10, "tom", 20) // "tom" is first given 10 as default value, then overridden by 20
override(assert, "tom", 10, "tom", 20) // "tom" is first given value 10, then overridden by 20
overrideDefault(assert, "tom.age", 10, "tom.age", 20)
override(assert, "tom.age", 10, "tom.age", 20)
overrideDefault(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20)
override(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20)

// Case 1: key:value overridden by a value
v = overrideDefault(assert, "tom.age", 10, "tom", "boy") // "tom.age" is first given 10 as default value, then "tom" is overridden by "boy"
assert.Nil(v.Get("tom.age")) // "tom.age" should not exist anymore
v = override(assert, "tom.age", 10, "tom", "boy")
assert.Nil(v.Get("tom.age"))

// Case 2: value overridden by a key:value
overrideDefault(assert, "tom", "boy", "tom.age", 10) // "tom" is first given "boy" as default value, then "tom" is overridden by map{"age":10}
override(assert, "tom.age", 10, "tom", "boy")

// Case 3: key:value overridden by a key:value
v = overrideDefault(assert, "tom.size", 4, "tom.age", 10)
assert.Equal(4, v.Get("tom.size")) // value should still be reachable
deepCheckValue(assert, v, []string{"tom", "size"}, 4) // value should still be reachable
v = override(assert, "tom.size", 4, "tom.age", 10)
assert.Equal(4, v.Get("tom.size"))
deepCheckValue(assert, v, []string{"tom", "size"}, 4)

// Case 4: key:value overridden by a map
v = overrideDefault(assert, "tom.size", 4, "tom", map[string]interface{}{"age": 10}) // "tom.size" is first given "4" as default value, then "tom" is overridden by map{"age":10}
assert.Nil(v.Get("tom.size")) // "tom.size" should not exist anymore
assert.Equal(10, v.Get("tom.age")) // new value should be there
deepCheckValue(assert, v, []string{"tom", "age"}, 10) // new value should be there
v = override(assert, "tom.size", 4, "tom", map[string]interface{}{"age": 10})
assert.Nil(v.Get("tom.size"))
assert.Equal(10, v.Get("tom.age"))
deepCheckValue(assert, v, []string{"tom", "age"}, 10)

// Case 5: array overridden by a value
overrideDefault(assert, "tom", []int{10, 20}, "tom", 30)
override(assert, "tom", []int{10, 20}, "tom", 30)
overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", 30)
override(assert, "tom.age", []int{10, 20}, "tom.age", 30)

// Case 6: array overridden by an array
overrideDefault(assert, "tom", []int{10, 20}, "tom", []int{30, 40})
override(assert, "tom", []int{10, 20}, "tom", []int{30, 40})
overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40})
v = override(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40})
// explicit array merge:
s, ok := v.Get("tom.age").([]int)
if assert.True(ok, "tom[\"age\"] is not a slice") {
v.Set("tom.age", append(s, []int{50, 60}...))
assert.Equal([]int{30, 40, 50, 60}, v.Get("tom.age"))
deepCheckValue(assert, v, []string{"tom", "age"}, []int{30, 40, 50, 60})
}
}

type layer int

const (
defaultLayer layer = iota + 1
overrideLayer
)

func overrideDefault(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue)
}
func override(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue)
}

// overrideFromLayer performs the sequential override and low-level checks.
//
// First assignment is made on layer l for path firstPath with value firstValue,
// the second one on the override layer (i.e., with the Set() function)
// for path secondPath with value secondValue.
//
// firstPath and secondPath can include an arbitrary number of dots to indicate
// a nested element.
//
// After each assignment, the value is checked, retrieved both by its full path
// and by its key sequence (successive maps).
func overrideFromLayer(l layer, assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
v := New()
firstKeys := strings.Split(firstPath, v.keyDelim)
if assert == nil ||
len(firstKeys) == 0 || len(firstKeys[0]) == 0 {
return v
}

// Set and check first value
switch l {
case defaultLayer:
v.SetDefault(firstPath, firstValue)
case overrideLayer:
v.Set(firstPath, firstValue)
default:
return v
}
assert.Equal(firstValue, v.Get(firstPath))
deepCheckValue(assert, v, firstKeys, firstValue)

// Override and check new value
secondKeys := strings.Split(secondPath, v.keyDelim)
if len(secondKeys) == 0 || len(secondKeys[0]) == 0 {
return v
}
v.Set(secondPath, secondValue)
assert.Equal(secondValue, v.Get(secondPath))
deepCheckValue(assert, v, secondKeys, secondValue)

return v
}

// deepCheckValue checks that all given keys correspond to a valid path in the
// configuration map, and that the final value equals the one given
func deepCheckValue(assert *assert.Assertions, v *Viper, keys []string, value interface{}) {
var m map[string]interface{}
var ok bool
if assert == nil || v == nil ||
len(keys) == 0 || len(keys[0]) == 0 {
return
}

ms := keys[0]
val := v.Get(keys[0])
err := false
for i := 1; i < len(keys); i++ {
// deep scan of the map to get the final value
m, ok = val.(map[string]interface{})
if err = !assert.True(ok, fmt.Sprintf("%s is not a map[string]interface{}", ms)); err {
break
}
val = m[keys[i]]
ms = ms + "[\"" + keys[i] + "\"]"
}
if !err {
assert.Equal(value, val)
}
}

0 comments on commit f2dc934

Please sign in to comment.