329 lines
8.9 KiB
Go
329 lines
8.9 KiB
Go
package httpexpect
|
|
|
|
import (
|
|
"reflect"
|
|
)
|
|
|
|
// Object provides methods to inspect attached map[string]interface{} object
|
|
// (Go representation of JSON object).
|
|
type Object struct {
|
|
chain chain
|
|
value map[string]interface{}
|
|
}
|
|
|
|
// NewObject returns a new Object given a reporter used to report failures
|
|
// and value to be inspected.
|
|
//
|
|
// Both reporter and value should not be nil. If value is nil, failure is
|
|
// reported.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
func NewObject(reporter Reporter, value map[string]interface{}) *Object {
|
|
chain := makeChain(reporter)
|
|
if value == nil {
|
|
chain.fail("expected non-nil map value")
|
|
} else {
|
|
value, _ = canonMap(&chain, value)
|
|
}
|
|
return &Object{chain, value}
|
|
}
|
|
|
|
// Raw returns underlying value attached to Object.
|
|
// This is the value originally passed to NewObject, converted to canonical form.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
// assert.Equal(t, map[string]interface{}{"foo": 123.0}, object.Raw())
|
|
func (o *Object) Raw() map[string]interface{} {
|
|
return o.value
|
|
}
|
|
|
|
// Path is similar to Value.Path.
|
|
func (o *Object) Path(path string) *Value {
|
|
return getPath(&o.chain, o.value, path)
|
|
}
|
|
|
|
// Schema is similar to Value.Schema.
|
|
func (o *Object) Schema(schema interface{}) *Object {
|
|
checkSchema(&o.chain, o.value, schema)
|
|
return o
|
|
}
|
|
|
|
// Keys returns a new Array object that may be used to inspect objects keys.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123, "bar": 456})
|
|
// object.Keys().ContainsOnly("foo", "bar")
|
|
func (o *Object) Keys() *Array {
|
|
keys := []interface{}{}
|
|
for k := range o.value {
|
|
keys = append(keys, k)
|
|
}
|
|
return &Array{o.chain, keys}
|
|
}
|
|
|
|
// Values returns a new Array object that may be used to inspect objects values.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123, "bar": 456})
|
|
// object.Values().ContainsOnly(123, 456)
|
|
func (o *Object) Values() *Array {
|
|
values := []interface{}{}
|
|
for _, v := range o.value {
|
|
values = append(values, v)
|
|
}
|
|
return &Array{o.chain, values}
|
|
}
|
|
|
|
// Value returns a new Value object that may be used to inspect single value
|
|
// for given key.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
// object.Value("foo").Number().Equal(123)
|
|
func (o *Object) Value(key string) *Value {
|
|
value, ok := o.value[key]
|
|
if !ok {
|
|
o.chain.fail("\nexpected object containing key '%s', but got:\n%s",
|
|
key, dumpValue(o.value))
|
|
return &Value{o.chain, nil}
|
|
}
|
|
return &Value{o.chain, value}
|
|
}
|
|
|
|
// Empty succeeds if object is empty.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{})
|
|
// object.Empty()
|
|
func (o *Object) Empty() *Object {
|
|
return o.Equal(map[string]interface{}{})
|
|
}
|
|
|
|
// NotEmpty succeeds if object is non-empty.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
// object.NotEmpty()
|
|
func (o *Object) NotEmpty() *Object {
|
|
return o.NotEqual(map[string]interface{}{})
|
|
}
|
|
|
|
// Equal succeeds if object is equal to given Go map or struct.
|
|
// Before comparison, both object and value are converted to canonical form.
|
|
//
|
|
// value should be map[string]interface{} or struct.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
// object.Equal(map[string]interface{}{"foo": 123})
|
|
func (o *Object) Equal(value interface{}) *Object {
|
|
expected, ok := canonMap(&o.chain, value)
|
|
if !ok {
|
|
return o
|
|
}
|
|
if !reflect.DeepEqual(expected, o.value) {
|
|
o.chain.fail("\nexpected object equal to:\n%s\n\nbut got:\n%s\n\ndiff:\n%s",
|
|
dumpValue(expected),
|
|
dumpValue(o.value),
|
|
diffValues(expected, o.value))
|
|
}
|
|
return o
|
|
}
|
|
|
|
// NotEqual succeeds if object is not equal to given Go map or struct.
|
|
// Before comparison, both object and value are converted to canonical form.
|
|
//
|
|
// value should be map[string]interface{} or struct.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
// object.Equal(map[string]interface{}{"bar": 123})
|
|
func (o *Object) NotEqual(v interface{}) *Object {
|
|
expected, ok := canonMap(&o.chain, v)
|
|
if !ok {
|
|
return o
|
|
}
|
|
if reflect.DeepEqual(expected, o.value) {
|
|
o.chain.fail("\nexpected object not equal to:\n%s",
|
|
dumpValue(expected))
|
|
}
|
|
return o
|
|
}
|
|
|
|
// ContainsKey succeeds if object contains given key.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
// object.ContainsKey("foo")
|
|
func (o *Object) ContainsKey(key string) *Object {
|
|
if !o.containsKey(key) {
|
|
o.chain.fail("\nexpected object containing key '%s', but got:\n%s",
|
|
key, dumpValue(o.value))
|
|
}
|
|
return o
|
|
}
|
|
|
|
// NotContainsKey succeeds if object doesn't contain given key.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
// object.NotContainsKey("bar")
|
|
func (o *Object) NotContainsKey(key string) *Object {
|
|
if o.containsKey(key) {
|
|
o.chain.fail(
|
|
"\nexpected object not containing key '%s', but got:\n%s", key,
|
|
dumpValue(o.value))
|
|
}
|
|
return o
|
|
}
|
|
|
|
// ContainsMap succeeds if object contains given Go value.
|
|
// Before comparison, both object and value are converted to canonical form.
|
|
//
|
|
// value should be map[string]interface{} or struct.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{
|
|
// "foo": 123,
|
|
// "bar": []interface{}{"x", "y"},
|
|
// "bar": map[string]interface{}{
|
|
// "a": true,
|
|
// "b": false,
|
|
// },
|
|
// })
|
|
//
|
|
// object.ContainsMap(map[string]interface{}{ // success
|
|
// "foo": 123,
|
|
// "bar": map[string]interface{}{
|
|
// "a": true,
|
|
// },
|
|
// })
|
|
//
|
|
// object.ContainsMap(map[string]interface{}{ // failure
|
|
// "foo": 123,
|
|
// "qux": 456,
|
|
// })
|
|
//
|
|
// object.ContainsMap(map[string]interface{}{ // failure, slices should match exactly
|
|
// "bar": []interface{}{"x"},
|
|
// })
|
|
func (o *Object) ContainsMap(value interface{}) *Object {
|
|
if !o.containsMap(value) {
|
|
o.chain.fail("\nexpected object containing sub-object:\n%s\n\nbut got:\n%s",
|
|
dumpValue(value), dumpValue(o.value))
|
|
}
|
|
return o
|
|
}
|
|
|
|
// NotContainsMap succeeds if object doesn't contain given Go value.
|
|
// Before comparison, both object and value are converted to canonical form.
|
|
//
|
|
// value should be map[string]interface{} or struct.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123, "bar": 456})
|
|
// object.NotContainsMap(map[string]interface{}{"foo": 123, "bar": "no-no-no"})
|
|
func (o *Object) NotContainsMap(value interface{}) *Object {
|
|
if o.containsMap(value) {
|
|
o.chain.fail("\nexpected object not containing sub-object:\n%s\n\nbut got:\n%s",
|
|
dumpValue(value), dumpValue(o.value))
|
|
}
|
|
return o
|
|
}
|
|
|
|
// ValueEqual succeeds if object's value for given key is equal to given Go value.
|
|
// Before comparison, both values are converted to canonical form.
|
|
//
|
|
// value should be map[string]interface{} or struct.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
// object.ValueEqual("foo", 123)
|
|
func (o *Object) ValueEqual(key string, value interface{}) *Object {
|
|
if !o.containsKey(key) {
|
|
o.chain.fail("\nexpected object containing key '%s', but got:\n%s",
|
|
key, dumpValue(o.value))
|
|
return o
|
|
}
|
|
expected, ok := canonValue(&o.chain, value)
|
|
if !ok {
|
|
return o
|
|
}
|
|
if !reflect.DeepEqual(expected, o.value[key]) {
|
|
o.chain.fail(
|
|
"\nexpected value for key '%s' equal to:\n%s\n\nbut got:\n%s\n\ndiff:\n%s",
|
|
key,
|
|
dumpValue(expected),
|
|
dumpValue(o.value[key]),
|
|
diffValues(expected, o.value[key]))
|
|
}
|
|
return o
|
|
}
|
|
|
|
// ValueNotEqual succeeds if object's value for given key is not equal to given
|
|
// Go value. Before comparison, both values are converted to canonical form.
|
|
//
|
|
// value should be map[string]interface{} or struct.
|
|
//
|
|
// If object doesn't contain any value for given key, failure is reported.
|
|
//
|
|
// Example:
|
|
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
|
// object.ValueNotEqual("foo", "bad value") // success
|
|
// object.ValueNotEqual("bar", "bad value") // failure! (key is missing)
|
|
func (o *Object) ValueNotEqual(key string, value interface{}) *Object {
|
|
if !o.containsKey(key) {
|
|
o.chain.fail("\nexpected object containing key '%s', but got:\n%s",
|
|
key, dumpValue(o.value))
|
|
return o
|
|
}
|
|
expected, ok := canonValue(&o.chain, value)
|
|
if !ok {
|
|
return o
|
|
}
|
|
if reflect.DeepEqual(expected, o.value[key]) {
|
|
o.chain.fail("\nexpected value for key '%s' not equal to:\n%s",
|
|
key, dumpValue(expected))
|
|
}
|
|
return o
|
|
}
|
|
|
|
func (o *Object) containsKey(key string) bool {
|
|
for k := range o.value {
|
|
if k == key {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (o *Object) containsMap(sm interface{}) bool {
|
|
submap, ok := canonMap(&o.chain, sm)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return checkContainsMap(o.value, submap)
|
|
}
|
|
|
|
func checkContainsMap(outer, inner map[string]interface{}) bool {
|
|
for k, iv := range inner {
|
|
ov, ok := outer[k]
|
|
if !ok {
|
|
return false
|
|
}
|
|
if ovm, ok := ov.(map[string]interface{}); ok {
|
|
if ivm, ok := iv.(map[string]interface{}); ok {
|
|
if !checkContainsMap(ovm, ivm) {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(ov, iv) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|