github.com/MontFerret/ferret@v0.18.0/pkg/runtime/values/object_test.go (about)

     1  package values_test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/MontFerret/ferret/pkg/runtime/core"
     8  	"github.com/MontFerret/ferret/pkg/runtime/values"
     9  	"github.com/MontFerret/ferret/pkg/runtime/values/types"
    10  
    11  	. "github.com/smartystreets/goconvey/convey"
    12  )
    13  
    14  func TestObject(t *testing.T) {
    15  	Convey("#constructor", t, func() {
    16  		Convey("Should create an empty object", func() {
    17  			obj := values.NewObject()
    18  
    19  			So(obj.Length(), ShouldEqual, 0)
    20  		})
    21  
    22  		Convey("Should create an object, from passed values", func() {
    23  			obj := values.NewObjectWith(
    24  				values.NewObjectProperty("none", values.None),
    25  				values.NewObjectProperty("boolean", values.False),
    26  				values.NewObjectProperty("int", values.NewInt(1)),
    27  				values.NewObjectProperty("float", values.Float(1)),
    28  				values.NewObjectProperty("string", values.NewString("1")),
    29  				values.NewObjectProperty("array", values.NewArray(10)),
    30  				values.NewObjectProperty("object", values.NewObject()),
    31  			)
    32  
    33  			So(obj.Length(), ShouldEqual, 7)
    34  		})
    35  	})
    36  
    37  	Convey(".MarshalJSON", t, func() {
    38  		Convey("Should serialize an empty object", func() {
    39  			obj := values.NewObject()
    40  			marshaled, err := obj.MarshalJSON()
    41  
    42  			So(err, ShouldBeNil)
    43  
    44  			So(string(marshaled), ShouldEqual, "{}")
    45  		})
    46  
    47  		Convey("Should serialize full object", func() {
    48  			obj := values.NewObjectWith(
    49  				values.NewObjectProperty("none", values.None),
    50  				values.NewObjectProperty("boolean", values.False),
    51  				values.NewObjectProperty("int", values.NewInt(1)),
    52  				values.NewObjectProperty("float", values.Float(1)),
    53  				values.NewObjectProperty("string", values.NewString("1")),
    54  				values.NewObjectProperty("array", values.NewArray(10)),
    55  				values.NewObjectProperty("object", values.NewObject()),
    56  			)
    57  			marshaled, err := obj.MarshalJSON()
    58  
    59  			So(err, ShouldBeNil)
    60  
    61  			So(string(marshaled), ShouldEqual, "{\"array\":[],\"boolean\":false,\"float\":1,\"int\":1,\"none\":null,\"object\":{},\"string\":\"1\"}")
    62  		})
    63  	})
    64  
    65  	Convey(".Type", t, func() {
    66  		Convey("Should return type", func() {
    67  			obj := values.NewObject()
    68  
    69  			So(obj.Type().Equals(types.Object), ShouldBeTrue)
    70  		})
    71  	})
    72  
    73  	Convey(".Unwrap", t, func() {
    74  		Convey("Should return an unwrapped items", func() {
    75  			obj := values.NewObjectWith(
    76  				values.NewObjectProperty("foo", values.NewString("foo")),
    77  				values.NewObjectProperty("bar", values.NewString("bar")),
    78  			)
    79  
    80  			for _, val := range obj.Unwrap().(map[string]interface{}) {
    81  				So(val, ShouldHaveSameTypeAs, "")
    82  			}
    83  		})
    84  	})
    85  
    86  	Convey(".String", t, func() {
    87  		Convey("Should return a string representation ", func() {
    88  			obj := values.NewObjectWith(
    89  				values.NewObjectProperty("foo", values.NewString("bar")),
    90  			)
    91  
    92  			So(obj.String(), ShouldEqual, "{\"foo\":\"bar\"}")
    93  		})
    94  	})
    95  
    96  	Convey(".Compare", t, func() {
    97  		Convey("It should return 1 for all non-object values", func() {
    98  			arr := []core.Value{
    99  				values.None,
   100  				values.False,
   101  				values.NewInt(1),
   102  				values.Float(1),
   103  				values.NewString("1"),
   104  				values.NewArray(10),
   105  			}
   106  			obj := values.NewObject()
   107  
   108  			for _, val := range arr {
   109  				So(obj.Compare(val), ShouldEqual, 1)
   110  			}
   111  		})
   112  
   113  		Convey("It should return -1 for all object values", func() {
   114  			arr := values.NewArrayWith(values.ZeroInt, values.ZeroInt)
   115  			obj := values.NewObject()
   116  
   117  			So(arr.Compare(obj), ShouldEqual, -1)
   118  		})
   119  
   120  		Convey("It should return 0 when both objects are empty", func() {
   121  			obj1 := values.NewObject()
   122  			obj2 := values.NewObject()
   123  
   124  			So(obj1.Compare(obj2), ShouldEqual, 0)
   125  		})
   126  
   127  		Convey("It should return 0 when both objects are equal (independent of key order)", func() {
   128  			obj1 := values.NewObjectWith(
   129  				values.NewObjectProperty("foo", values.NewString("foo")),
   130  				values.NewObjectProperty("bar", values.NewString("bar")),
   131  			)
   132  			obj2 := values.NewObjectWith(
   133  				values.NewObjectProperty("foo", values.NewString("foo")),
   134  				values.NewObjectProperty("bar", values.NewString("bar")),
   135  			)
   136  
   137  			So(obj1.Compare(obj1), ShouldEqual, 0)
   138  			So(obj2.Compare(obj2), ShouldEqual, 0)
   139  			So(obj1.Compare(obj2), ShouldEqual, 0)
   140  			So(obj2.Compare(obj1), ShouldEqual, 0)
   141  		})
   142  
   143  		Convey("It should return 1 when other array is empty", func() {
   144  			obj1 := values.NewObjectWith(values.NewObjectProperty("foo", values.NewString("bar")))
   145  			obj2 := values.NewObject()
   146  
   147  			So(obj1.Compare(obj2), ShouldEqual, 1)
   148  		})
   149  
   150  		Convey("It should return 1 when values are bigger", func() {
   151  			obj1 := values.NewObjectWith(values.NewObjectProperty("foo", values.NewFloat(3)))
   152  			obj2 := values.NewObjectWith(values.NewObjectProperty("foo", values.NewFloat(2)))
   153  
   154  			So(obj1.Compare(obj2), ShouldEqual, 1)
   155  		})
   156  
   157  		Convey("It should return 1 when values are less", func() {
   158  			obj1 := values.NewObjectWith(values.NewObjectProperty("foo", values.NewFloat(1)))
   159  			obj2 := values.NewObjectWith(values.NewObjectProperty("foo", values.NewFloat(2)))
   160  
   161  			So(obj1.Compare(obj2), ShouldEqual, -1)
   162  		})
   163  
   164  		Convey("ArangoDB compatibility", func() {
   165  			Convey("It should return 1 when {a:1} and {b:2}", func() {
   166  				obj1 := values.NewObjectWith(values.NewObjectProperty("a", values.NewInt(1)))
   167  				obj2 := values.NewObjectWith(values.NewObjectProperty("b", values.NewInt(2)))
   168  
   169  				So(obj1.Compare(obj2), ShouldEqual, 1)
   170  			})
   171  
   172  			Convey("It should return 0 when {a:1} and {a:1}", func() {
   173  				obj1 := values.NewObjectWith(values.NewObjectProperty("a", values.NewInt(1)))
   174  				obj2 := values.NewObjectWith(values.NewObjectProperty("a", values.NewInt(1)))
   175  
   176  				So(obj1.Compare(obj2), ShouldEqual, 0)
   177  			})
   178  
   179  			Convey("It should return 0 {a:1, c:2} and {c:2, a:1}", func() {
   180  				obj1 := values.NewObjectWith(
   181  					values.NewObjectProperty("a", values.NewInt(1)),
   182  					values.NewObjectProperty("c", values.NewInt(2)),
   183  				)
   184  				obj2 := values.NewObjectWith(
   185  					values.NewObjectProperty("c", values.NewInt(2)),
   186  					values.NewObjectProperty("a", values.NewInt(1)),
   187  				)
   188  
   189  				So(obj1.Compare(obj2), ShouldEqual, 0)
   190  			})
   191  
   192  			Convey("It should return -1 when {a:1} and {a:2}", func() {
   193  				obj1 := values.NewObjectWith(values.NewObjectProperty("a", values.NewInt(1)))
   194  				obj2 := values.NewObjectWith(values.NewObjectProperty("a", values.NewInt(2)))
   195  
   196  				So(obj1.Compare(obj2), ShouldEqual, -1)
   197  			})
   198  
   199  			Convey("It should return 1 when {a:1, c:2} and {c:2, b:2}", func() {
   200  				obj1 := values.NewObjectWith(
   201  					values.NewObjectProperty("a", values.NewInt(1)),
   202  					values.NewObjectProperty("c", values.NewInt(2)),
   203  				)
   204  				obj2 := values.NewObjectWith(
   205  					values.NewObjectProperty("c", values.NewInt(2)),
   206  					values.NewObjectProperty("b", values.NewInt(2)),
   207  				)
   208  
   209  				So(obj1.Compare(obj2), ShouldEqual, 1)
   210  			})
   211  
   212  			Convey("It should return 1 {a:1, c:3} and {c:2, a:1}", func() {
   213  				obj1 := values.NewObjectWith(
   214  					values.NewObjectProperty("a", values.NewInt(1)),
   215  					values.NewObjectProperty("c", values.NewInt(3)),
   216  				)
   217  				obj2 := values.NewObjectWith(
   218  					values.NewObjectProperty("c", values.NewInt(2)),
   219  					values.NewObjectProperty("a", values.NewInt(1)),
   220  				)
   221  
   222  				So(obj1.Compare(obj2), ShouldEqual, 1)
   223  			})
   224  		})
   225  	})
   226  
   227  	Convey(".Hash", t, func() {
   228  		Convey("It should calculate hash of non-empty object", func() {
   229  			v := values.NewObjectWith(
   230  				values.NewObjectProperty("foo", values.NewString("bar")),
   231  				values.NewObjectProperty("faz", values.NewInt(1)),
   232  				values.NewObjectProperty("qaz", values.True),
   233  			)
   234  
   235  			h := v.Hash()
   236  
   237  			So(h, ShouldBeGreaterThan, 0)
   238  		})
   239  
   240  		Convey("It should calculate hash of empty object", func() {
   241  			v := values.NewObject()
   242  
   243  			h := v.Hash()
   244  
   245  			So(h, ShouldBeGreaterThan, 0)
   246  		})
   247  
   248  		Convey("Hash sum should be consistent", func() {
   249  			v := values.NewObjectWith(
   250  				values.NewObjectProperty("boolean", values.True),
   251  				values.NewObjectProperty("int", values.NewInt(1)),
   252  				values.NewObjectProperty("float", values.NewFloat(1.1)),
   253  				values.NewObjectProperty("string", values.NewString("foobar")),
   254  				values.NewObjectProperty("datetime", values.NewCurrentDateTime()),
   255  				values.NewObjectProperty("array", values.NewArrayWith(values.NewInt(1), values.True)),
   256  				values.NewObjectProperty("object", values.NewObjectWith(values.NewObjectProperty("foo", values.NewString("bar")))),
   257  			)
   258  
   259  			h1 := v.Hash()
   260  			h2 := v.Hash()
   261  
   262  			So(h1, ShouldEqual, h2)
   263  		})
   264  	})
   265  
   266  	Convey(".Length", t, func() {
   267  		Convey("Should return 0 when empty", func() {
   268  			obj := values.NewObject()
   269  
   270  			So(obj.Length(), ShouldEqual, 0)
   271  		})
   272  
   273  		Convey("Should return greater than 0 when not empty", func() {
   274  			obj := values.NewObjectWith(
   275  				values.NewObjectProperty("foo", values.ZeroInt),
   276  				values.NewObjectProperty("bar", values.ZeroInt),
   277  			)
   278  
   279  			So(obj.Length(), ShouldEqual, 2)
   280  		})
   281  	})
   282  
   283  	Convey(".ForEach", t, func() {
   284  		Convey("Should iterate over elements", func() {
   285  			obj := values.NewObjectWith(
   286  				values.NewObjectProperty("foo", values.ZeroInt),
   287  				values.NewObjectProperty("bar", values.ZeroInt),
   288  			)
   289  			counter := 0
   290  
   291  			obj.ForEach(func(value core.Value, key string) bool {
   292  				counter++
   293  
   294  				return true
   295  			})
   296  
   297  			So(counter, ShouldEqual, obj.Length())
   298  		})
   299  
   300  		Convey("Should not iterate when empty", func() {
   301  			obj := values.NewObject()
   302  			counter := 0
   303  
   304  			obj.ForEach(func(value core.Value, key string) bool {
   305  				counter++
   306  
   307  				return true
   308  			})
   309  
   310  			So(counter, ShouldEqual, obj.Length())
   311  		})
   312  
   313  		Convey("Should break iteration when false returned", func() {
   314  			obj := values.NewObjectWith(
   315  				values.NewObjectProperty("1", values.NewInt(1)),
   316  				values.NewObjectProperty("2", values.NewInt(2)),
   317  				values.NewObjectProperty("3", values.NewInt(3)),
   318  				values.NewObjectProperty("4", values.NewInt(4)),
   319  				values.NewObjectProperty("5", values.NewInt(5)),
   320  			)
   321  			threshold := 3
   322  			counter := 0
   323  
   324  			obj.ForEach(func(value core.Value, key string) bool {
   325  				counter++
   326  
   327  				return counter < threshold
   328  			})
   329  
   330  			So(counter, ShouldEqual, threshold)
   331  		})
   332  	})
   333  
   334  	Convey(".Get", t, func() {
   335  		Convey("Should return item by key", func() {
   336  			obj := values.NewObjectWith(
   337  				values.NewObjectProperty("foo", values.NewInt(1)),
   338  				values.NewObjectProperty("bar", values.NewInt(2)),
   339  				values.NewObjectProperty("qaz", values.NewInt(3)),
   340  			)
   341  
   342  			el, _ := obj.Get("foo")
   343  
   344  			So(el.Compare(values.NewInt(1)), ShouldEqual, 0)
   345  		})
   346  
   347  		Convey("Should return None when no items", func() {
   348  			obj := values.NewObject()
   349  
   350  			el, _ := obj.Get("foo")
   351  
   352  			So(el.Compare(values.None), ShouldEqual, 0)
   353  		})
   354  	})
   355  
   356  	Convey(".Set", t, func() {
   357  		Convey("Should set item by index", func() {
   358  			obj := values.NewObject()
   359  
   360  			obj.Set("foo", values.NewInt(1))
   361  
   362  			So(obj.Length(), ShouldEqual, 1)
   363  
   364  			v, _ := obj.Get("foo")
   365  			So(v.Compare(values.NewInt(1)), ShouldEqual, 0)
   366  		})
   367  	})
   368  
   369  	Convey(".Clone", t, func() {
   370  		Convey("Cloned object should be equal to source object", func() {
   371  			obj := values.NewObjectWith(
   372  				values.NewObjectProperty("one", values.NewInt(1)),
   373  				values.NewObjectProperty("two", values.NewInt(2)),
   374  			)
   375  
   376  			clone := obj.Clone().(*values.Object)
   377  
   378  			So(obj.Compare(clone), ShouldEqual, 0)
   379  		})
   380  
   381  		Convey("Cloned object should be independent of the source object", func() {
   382  			obj := values.NewObjectWith(
   383  				values.NewObjectProperty("one", values.NewInt(1)),
   384  				values.NewObjectProperty("two", values.NewInt(2)),
   385  			)
   386  
   387  			clone := obj.Clone().(*values.Object)
   388  
   389  			obj.Remove(values.NewString("one"))
   390  
   391  			So(obj.Compare(clone), ShouldNotEqual, 0)
   392  		})
   393  
   394  		Convey("Cloned object must contain copies of the nested objects", func() {
   395  			obj := values.NewObjectWith(
   396  				values.NewObjectProperty(
   397  					"arr", values.NewArrayWith(values.NewInt(1)),
   398  				),
   399  			)
   400  
   401  			clone := obj.Clone().(*values.Object)
   402  
   403  			nestedInObj, _ := obj.Get(values.NewString("arr"))
   404  			nestedInObjArr := nestedInObj.(*values.Array)
   405  			nestedInObjArr.Push(values.NewInt(2))
   406  
   407  			nestedInClone, _ := clone.Get(values.NewString("arr"))
   408  			nestedInCloneArr := nestedInClone.(*values.Array)
   409  
   410  			So(nestedInObjArr.Compare(nestedInCloneArr), ShouldNotEqual, 0)
   411  		})
   412  	})
   413  
   414  	Convey(".GetIn", t, func() {
   415  
   416  		ctx := context.Background()
   417  
   418  		Convey("Should return the same as .Get when input is correct", func() {
   419  
   420  			Convey("Should return item by key", func() {
   421  				key := values.NewString("foo")
   422  				obj := values.NewObjectWith(
   423  					values.NewObjectProperty(key.String(), values.NewInt(1)),
   424  				)
   425  
   426  				el, err := obj.GetIn(ctx, []core.Value{key})
   427  				elGet, _ := obj.Get(key)
   428  
   429  				So(err, ShouldBeNil)
   430  				So(el.Compare(elGet), ShouldEqual, 0)
   431  			})
   432  
   433  			Convey("Should return None when no items", func() {
   434  				key := values.NewString("foo")
   435  				obj := values.NewObject()
   436  
   437  				el, err := obj.GetIn(ctx, []core.Value{key})
   438  				elGet, _ := obj.Get(key)
   439  
   440  				So(err, ShouldBeNil)
   441  				So(el.Compare(elGet), ShouldEqual, 0)
   442  			})
   443  		})
   444  
   445  		Convey("Should error when input is not correct", func() {
   446  
   447  			Convey("Should return None when path[0] is not a string", func() {
   448  				obj := values.NewObject()
   449  				path := []core.Value{values.NewInt(0)}
   450  
   451  				el, err := obj.GetIn(ctx, path)
   452  
   453  				So(err, ShouldBeNil)
   454  				So(el, ShouldNotBeNil)
   455  				So(el.Type().String(), ShouldEqual, types.None.String())
   456  			})
   457  
   458  			Convey("Should error when first received item is not a Getter and len(path) > 1", func() {
   459  				key := values.NewString("foo")
   460  				obj := values.NewObjectWith(
   461  					values.NewObjectProperty(key.String(), values.NewInt(1)),
   462  				)
   463  				path := []core.Value{key, key}
   464  
   465  				el, err := obj.GetIn(ctx, path)
   466  
   467  				So(err, ShouldBeError)
   468  				So(el.Compare(values.None), ShouldEqual, 0)
   469  			})
   470  		})
   471  
   472  		Convey("Should return None when len(path) == 0", func() {
   473  			obj := values.NewObject()
   474  
   475  			el, err := obj.GetIn(ctx, nil)
   476  
   477  			So(err, ShouldBeNil)
   478  			So(el.Compare(values.None), ShouldEqual, 0)
   479  		})
   480  
   481  		Convey("Should call the nested Getter", func() {
   482  			key := values.NewString("foo")
   483  			obj := values.NewObjectWith(
   484  				values.NewObjectProperty(key.String(), values.NewArrayWith(key)),
   485  			)
   486  
   487  			el, err := obj.GetIn(ctx, []core.Value{
   488  				key,              // obj.foo
   489  				values.NewInt(0), // obj.foo[0]
   490  			})
   491  
   492  			So(err, ShouldBeNil)
   493  			So(el.Compare(key), ShouldEqual, 0)
   494  		})
   495  	})
   496  }