package ldvalue import ( "encoding/json" "errors" "testing" helpers "github.com/launchdarkly/go-test-helpers/v3" "github.com/stretchr/testify/assert" ) func TestValueTypes(t *testing.T) { assert.Equal(t, nullAsJSON, NullType.String()) assert.Equal(t, "bool", BoolType.String()) assert.Equal(t, "number", NumberType.String()) assert.Equal(t, "string", StringType.String()) assert.Equal(t, "array", ArrayType.String()) assert.Equal(t, "object", ObjectType.String()) assert.Equal(t, "raw", RawType.String()) assert.Equal(t, "unknown", ValueType(99).String()) } func TestNullValue(t *testing.T) { v := Null() assert.Equal(t, NullType, v.Type()) assert.False(t, v.IsDefined()) assert.True(t, v.IsNull()) assert.False(t, v.IsBool()) assert.False(t, v.IsNumber()) assert.False(t, v.IsInt()) assert.False(t, v.IsString()) assert.Equal(t, Null(), v) assert.Equal(t, Value{}, v) // treating a null as a non-null produces empty values assert.False(t, v.BoolValue()) assert.Equal(t, 0, v.IntValue()) assert.Equal(t, float64(0), v.Float64Value()) assert.Equal(t, "", v.StringValue()) assert.Equal(t, OptionalString{}, v.AsOptionalString()) assert.Equal(t, 0, v.Count()) assert.Equal(t, Null(), v.GetByIndex(0)) assert.Equal(t, Null(), v.GetByKey("x")) } func TestBoolValue(t *testing.T) { tv := Bool(true) assert.Equal(t, BoolType, tv.Type()) assert.True(t, tv.BoolValue()) assert.True(t, tv.IsDefined()) assert.False(t, tv.IsNull()) assert.True(t, tv.IsBool()) assert.False(t, tv.IsNumber()) assert.False(t, tv.IsInt()) assert.False(t, tv.IsString()) assert.Equal(t, Bool(true), tv) assert.NotEqual(t, Bool(false), tv) // treating a bool as a non-bool produces empty values assert.Equal(t, 0, tv.IntValue()) assert.Equal(t, float64(0), tv.Float64Value()) assert.Equal(t, "", tv.StringValue()) assert.Equal(t, OptionalString{}, tv.AsOptionalString()) assert.Equal(t, 0, tv.Count()) assert.Equal(t, Null(), tv.GetByIndex(0)) assert.Equal(t, Null(), tv.GetByKey("x")) fv := Bool(false) assert.Equal(t, BoolType, fv.Type()) assert.False(t, fv.BoolValue()) assert.True(t, fv.IsDefined()) assert.False(t, fv.IsNull()) assert.False(t, fv.IsNumber()) assert.False(t, fv.IsInt()) assert.Equal(t, Bool(false), fv) assert.NotEqual(t, Bool(true), fv) } func TestIntValue(t *testing.T) { v := Int(2) assert.Equal(t, NumberType, v.Type()) assert.Equal(t, 2, v.IntValue()) assert.Equal(t, float64(2), v.Float64Value()) assert.True(t, v.IsDefined()) assert.False(t, v.IsNull()) assert.False(t, v.IsBool()) assert.True(t, v.IsNumber()) assert.True(t, v.IsInt()) assert.False(t, v.IsString()) assert.Equal(t, Int(2), v) assert.Equal(t, Float64(2), v) assert.NotEqual(t, Float64(2.5), v) // treating a number as a non-number produces empty values assert.False(t, v.BoolValue()) assert.Equal(t, "", v.StringValue()) assert.Equal(t, OptionalString{}, v.AsOptionalString()) assert.Equal(t, 0, v.Count()) assert.Equal(t, Null(), v.GetByIndex(0)) assert.Equal(t, Null(), v.GetByKey("x")) } func TestFloat64Value(t *testing.T) { v := Float64(2.75) assert.Equal(t, NumberType, v.Type()) assert.Equal(t, 2, v.IntValue()) assert.Equal(t, 2.75, v.Float64Value()) assert.True(t, v.IsDefined()) assert.False(t, v.IsNull()) assert.False(t, v.IsBool()) assert.True(t, v.IsNumber()) assert.False(t, v.IsInt()) assert.False(t, v.IsString()) floatButReallyInt := Float64(2.0) assert.Equal(t, NumberType, floatButReallyInt.Type()) assert.Equal(t, 2, floatButReallyInt.IntValue()) assert.Equal(t, 2.0, floatButReallyInt.Float64Value()) assert.False(t, floatButReallyInt.IsNull()) assert.True(t, floatButReallyInt.IsNumber()) assert.True(t, floatButReallyInt.IsInt()) assert.Equal(t, Float64(2.75), v) assert.NotEqual(t, Float64(2.5), v) // treating a number as a non-number produces empty values assert.False(t, v.BoolValue()) assert.Equal(t, "", v.StringValue()) assert.Equal(t, OptionalString{}, v.AsOptionalString()) assert.Equal(t, 0, v.Count()) assert.Equal(t, Null(), v.GetByIndex(0)) assert.Equal(t, Null(), v.GetByKey("x")) } func TestStringValue(t *testing.T) { v := String("abc") assert.Equal(t, StringType, v.Type()) assert.Equal(t, "abc", v.StringValue()) assert.Equal(t, NewOptionalString("abc"), v.AsOptionalString()) assert.True(t, v.IsDefined()) assert.False(t, v.IsNull()) assert.False(t, v.IsBool()) assert.False(t, v.IsNumber()) assert.False(t, v.IsInt()) assert.True(t, v.IsString()) assert.Equal(t, v, String("abc")) assert.Equal(t, String("abc"), v) assert.NotEqual(t, String("def"), v) // treating a string as a non-string produces empty values assert.False(t, v.BoolValue()) assert.Equal(t, 0, v.IntValue()) assert.Equal(t, float64(0), v.Float64Value()) assert.Equal(t, 0, v.Count()) assert.Equal(t, Null(), v.GetByIndex(0)) assert.Equal(t, Null(), v.GetByKey("x")) } func TestRawValue(t *testing.T) { rawJSON := json.RawMessage(`[1]`) v := Raw(rawJSON) assert.Equal(t, RawType, v.Type()) assert.Equal(t, rawJSON, v.AsRaw()) assert.Equal(t, string(rawJSON), v.JSONString()) // inspecting the value in any way causes it to be parsed and treated as a regular value assert.True(t, Raw(json.RawMessage(`null`)).IsNull()) assert.True(t, Raw(json.RawMessage(`true`)).IsBool()) assert.True(t, Raw(json.RawMessage(`"a"`)).IsString()) assert.True(t, Raw(json.RawMessage(`1.5`)).IsNumber()) assert.True(t, Raw(json.RawMessage(`1`)).IsInt()) assert.True(t, Raw(json.RawMessage(`true`)).BoolValue()) assert.Equal(t, "a", Raw(json.RawMessage(`"a"`)).StringValue()) assert.Equal(t, NewOptionalString("a"), Raw(json.RawMessage(`"a"`)).AsOptionalString()) assert.Equal(t, 1.5, Raw(json.RawMessage(`1.5`)).Float64Value()) assert.Equal(t, 1, Raw(json.RawMessage(`1.5`)).IntValue()) } func TestConvertPrimitivesFromArbitraryValue(t *testing.T) { t.Run("nil", func(t *testing.T) { assert.Equal(t, Null(), CopyArbitraryValue(nil)) }) t.Run("Value", func(t *testing.T) { originalValue := Int(1) assert.Equal(t, originalValue, CopyArbitraryValue(originalValue)) assert.Equal(t, originalValue, CopyArbitraryValue(&originalValue)) assert.Equal(t, Null(), CopyArbitraryValue((*Value)(nil))) }) t.Run("OptionalString", func(t *testing.T) { s := NewOptionalString("value") sv := String("value") assert.Equal(t, sv, CopyArbitraryValue(s)) assert.Equal(t, sv, CopyArbitraryValue(&s)) assert.Equal(t, Null(), CopyArbitraryValue(OptionalString{})) assert.Equal(t, Null(), CopyArbitraryValue(&OptionalString{})) assert.Equal(t, Null(), CopyArbitraryValue((*OptionalString)(nil))) }) t.Run("bool", func(t *testing.T) { assert.Equal(t, Bool(true), CopyArbitraryValue(true)) assert.Equal(t, Bool(false), CopyArbitraryValue(false)) assert.Equal(t, Bool(true), CopyArbitraryValue(helpers.AsPointer(true))) assert.Equal(t, Bool(false), CopyArbitraryValue(helpers.AsPointer(false))) assert.Equal(t, Null(), CopyArbitraryValue((*bool)(nil))) }) t.Run("int8", func(t *testing.T) { var n int8 = 1 assert.Equal(t, Int(1), CopyArbitraryValue(n)) assert.Equal(t, Int(1), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*int8)(nil))) }) t.Run("uint8", func(t *testing.T) { var n uint8 = 1 assert.Equal(t, Int(1), CopyArbitraryValue(n)) assert.Equal(t, Int(1), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*uint8)(nil))) }) t.Run("int16", func(t *testing.T) { var n int16 = 1 assert.Equal(t, Int(1), CopyArbitraryValue(n)) assert.Equal(t, Int(1), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*int16)(nil))) }) t.Run("uint16", func(t *testing.T) { var n uint16 = 1 assert.Equal(t, Int(1), CopyArbitraryValue(n)) assert.Equal(t, Int(1), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*uint16)(nil))) }) t.Run("int", func(t *testing.T) { var n int = 1 assert.Equal(t, Int(1), CopyArbitraryValue(n)) assert.Equal(t, Int(1), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*int)(nil))) }) t.Run("uint", func(t *testing.T) { var n uint = 1 assert.Equal(t, Int(1), CopyArbitraryValue(n)) assert.Equal(t, Int(1), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*uint)(nil))) }) t.Run("int32", func(t *testing.T) { var n int32 = 1 assert.Equal(t, Int(1), CopyArbitraryValue(n)) assert.Equal(t, Int(1), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*int32)(nil))) }) t.Run("uint32", func(t *testing.T) { var n uint32 = 1 assert.Equal(t, Int(1), CopyArbitraryValue(n)) assert.Equal(t, Int(1), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*uint32)(nil))) }) t.Run("float32", func(t *testing.T) { var n float32 = 2.5 assert.Equal(t, Float64(2.5), CopyArbitraryValue(n)) assert.Equal(t, Float64(2.5), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*float32)(nil))) }) t.Run("float64", func(t *testing.T) { var n float64 = 2.5 assert.Equal(t, Float64(2.5), CopyArbitraryValue(n)) assert.Equal(t, Float64(2.5), CopyArbitraryValue(&n)) assert.Equal(t, Null(), CopyArbitraryValue((*float64)(nil))) }) t.Run("string", func(t *testing.T) { s := "x" assert.Equal(t, String(s), CopyArbitraryValue(s)) assert.Equal(t, String(s), CopyArbitraryValue(&s)) assert.Equal(t, Null(), CopyArbitraryValue((*string)(nil))) }) t.Run("[]interface{}", func(t *testing.T) { a := []interface{}{2, []interface{}{"x"}} av := ArrayOf(Int(2), ArrayOf(String("x"))) assert.Equal(t, av, CopyArbitraryValue(a)) assert.Equal(t, av, CopyArbitraryValue(&a)) assert.Equal(t, Null(), CopyArbitraryValue((*[]interface{})(nil))) }) t.Run("[]Value", func(t *testing.T) { a := []Value{Int(2), ArrayOf(String("x"))} av := ArrayOf(Int(2), ArrayOf(String("x"))) assert.Equal(t, av, CopyArbitraryValue(a)) assert.Equal(t, av, CopyArbitraryValue(&a)) assert.Equal(t, Null(), CopyArbitraryValue((*[]Value)(nil))) }) t.Run("map[string]interface{}", func(t *testing.T) { m := map[string]interface{}{"x": []interface{}{2}} mv := ObjectBuild().Set("x", ArrayOf(Int(2))).Build() assert.Equal(t, mv, CopyArbitraryValue(m)) assert.Equal(t, mv, CopyArbitraryValue(&m)) assert.Equal(t, Null(), CopyArbitraryValue((*map[string]interface{})(nil))) }) t.Run("map[string]Value", func(t *testing.T) { m := map[string]Value{"x": ArrayOf(Int(2))} mv := ObjectBuild().Set("x", ArrayOf(Int(2))).Build() assert.Equal(t, mv, CopyArbitraryValue(m)) assert.Equal(t, mv, CopyArbitraryValue(&m)) assert.Equal(t, Null(), CopyArbitraryValue((*map[string]Value)(nil))) }) t.Run("arbitrary struct", func(t *testing.T) { s := struct { X int `json:"x"` }{X: 2} v := CopyArbitraryValue(s) assert.Equal(t, ObjectBuild().Set("x", Int(2)).Build(), v) }) t.Run("raw", func(t *testing.T) { j := json.RawMessage("[3]") jv := Raw(json.RawMessage("[3]")) assert.Equal(t, jv, CopyArbitraryValue(j)) assert.Equal(t, jv, CopyArbitraryValue(&j)) assert.Equal(t, Null(), CopyArbitraryValue((*json.RawMessage)(nil))) }) } func TestFromJSONMarshal(t *testing.T) { s := struct { X int `json:"x"` }{X: 2} v := FromJSONMarshal(s) assert.Equal(t, ObjectBuild().Set("x", Int(2)).Build(), v) } func TestConvertPrimitivesToArbitraryValue(t *testing.T) { assert.Nil(t, Null().AsArbitraryValue()) assert.Equal(t, true, Bool(true).AsArbitraryValue()) assert.Equal(t, false, Bool(false).AsArbitraryValue()) assert.Equal(t, float64(2), Int(2).AsArbitraryValue()) assert.Equal(t, "x", String("x").AsArbitraryValue()) assert.Equal(t, json.RawMessage("[3]"), Raw(json.RawMessage("[3]")).AsArbitraryValue()) } func TestEqualPrimitives(t *testing.T) { valueFns := []func() Value{ func() Value { return Null() }, func() Value { return Bool(false) }, func() Value { return Bool(true) }, func() Value { return Int(1) }, func() Value { return Float64(2.5) }, func() Value { return String("") }, func() Value { return String("1") }, } for i, fn0 := range valueFns { v0 := fn0() for j, fn1 := range valueFns { v1 := fn1() if i == j { valuesShouldBeEqual(t, v0, v1) } else { valuesShouldNotBeEqual(t, v0, v1) } } } } func TestEqualPrimitivesAndRawRepresentations(t *testing.T) { valueFns := []func() Value{ func() Value { return Null() }, func() Value { return Bool(false) }, func() Value { return Bool(true) }, func() Value { return Int(1) }, func() Value { return Float64(2.5) }, func() Value { return String("") }, func() Value { return String("1") }, } for i, fn0 := range valueFns { v0 := fn0() v0Raw := Raw([]byte(v0.JSONString())) valuesShouldBeEqual(t, v0, v0Raw) for j, fn1 := range valueFns { if i != j { v1 := fn1() v1Raw := Raw([]byte(v1.JSONString())) valuesShouldNotBeEqual(t, v0, v1Raw) valuesShouldNotBeEqual(t, v1, v0Raw) } } } } func valuesShouldBeEqual(t *testing.T, value0 Value, value1 Value) { t.Helper() assert.True(t, value0.Equal(value1), "%s should equal %s", value0, value1) assert.True(t, value1.Equal(value0), "%s should equal %s conversely", value1, value0) } func valuesShouldNotBeEqual(t *testing.T, value0 Value, value1 Value) { t.Helper() assert.False(t, value0.Equal(value1), "%s should not equal %s", value0, value1) assert.False(t, value1.Equal(value0), "%s should not equal %s", value1, value0) } func TestValueWithInvalidType(t *testing.T) { // Application code has no way to construct a Value like this, but we'll still prove // that we would handle it gracefully if we did it somehow v := Value{valueType: ValueType(99)} assert.False(t, v.IsNull()) assert.False(t, v.IsNumber()) assert.False(t, v.IsInt()) assert.False(t, v.BoolValue()) assert.Equal(t, 0, v.IntValue()) assert.Equal(t, float64(0), v.Float64Value()) assert.Equal(t, "", v.StringValue()) assert.Equal(t, OptionalString{}, v.AsOptionalString()) assert.Equal(t, 0, v.Count()) assert.Equal(t, Null(), v.GetByIndex(0)) assert.Equal(t, Null(), v.GetByKey("x")) assert.Nil(t, v.AsArbitraryValue()) assert.Nil(t, v.AsRaw()) } func TestValueAsPointer(t *testing.T) { v := String("value") assert.Equal(t, &v, v.AsPointer()) assert.Nil(t, Null().AsPointer()) } func TestConvertArbitraryValueThatFailsToSerialize(t *testing.T) { assert.Equal(t, Null(), CopyArbitraryValue(unserializableValue{})) assert.Equal(t, Null(), FromJSONMarshal(unserializableValue{})) } type unserializableValue struct{} func (u unserializableValue) MarshalJSON() ([]byte, error) { return nil, errors.New("no") }