Skip to content

Commit 312e42e

Browse files
Copilotziflex
andcommitted
Fix MergeRecursive bug and add comprehensive edge case tests
Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com>
1 parent 696e1e3 commit 312e42e

4 files changed

Lines changed: 647 additions & 310 deletions

File tree

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package objects_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/MontFerret/ferret/pkg/runtime"
9+
"github.com/MontFerret/ferret/pkg/stdlib/objects"
10+
11+
. "github.com/smartystreets/goconvey/convey"
12+
)
13+
14+
func TestHasEdgeCases(t *testing.T) {
15+
Convey("Edge cases for Has function", t, func() {
16+
Convey("When key is empty string", func() {
17+
obj := runtime.NewObjectWith(
18+
runtime.NewObjectProperty("", runtime.NewString("empty")),
19+
)
20+
21+
val, err := objects.Has(context.Background(), obj, runtime.NewString(""))
22+
valBool := val.(runtime.Boolean)
23+
24+
So(err, ShouldEqual, nil)
25+
So(bool(valBool), ShouldEqual, true)
26+
})
27+
28+
Convey("When object has many keys", func() {
29+
properties := make([]*runtime.ObjectProperty, 100)
30+
for i := 0; i < 100; i++ {
31+
properties[i] = runtime.NewObjectProperty(
32+
fmt.Sprintf("key%d", i),
33+
runtime.NewInt(i),
34+
)
35+
}
36+
obj := runtime.NewObjectWith(properties...)
37+
38+
// Test existing key
39+
val, err := objects.Has(context.Background(), obj, runtime.NewString("key50"))
40+
valBool := val.(runtime.Boolean)
41+
42+
So(err, ShouldEqual, nil)
43+
So(bool(valBool), ShouldEqual, true)
44+
45+
// Test non-existing key
46+
val, err = objects.Has(context.Background(), obj, runtime.NewString("key999"))
47+
valBool = val.(runtime.Boolean)
48+
49+
So(err, ShouldEqual, nil)
50+
So(bool(valBool), ShouldEqual, false)
51+
})
52+
})
53+
}
54+
55+
func TestKeysEdgeCases(t *testing.T) {
56+
Convey("Edge cases for Keys function", t, func() {
57+
Convey("When object has special character keys", func() {
58+
obj := runtime.NewObjectWith(
59+
runtime.NewObjectProperty("key with spaces", runtime.NewInt(1)),
60+
runtime.NewObjectProperty("key_with_underscores", runtime.NewInt(2)),
61+
runtime.NewObjectProperty("key-with-dashes", runtime.NewInt(3)),
62+
runtime.NewObjectProperty("key.with.dots", runtime.NewInt(4)),
63+
)
64+
65+
keys, err := objects.Keys(context.Background(), obj, runtime.NewBoolean(true))
66+
keysArray := keys.(*runtime.Array)
67+
68+
So(err, ShouldEqual, nil)
69+
actualLength, _ := keysArray.Length(context.Background())
70+
So(actualLength, ShouldEqual, 4)
71+
72+
// Check sorted order
73+
expectedKeys := []string{"key with spaces", "key-with-dashes", "key.with.dots", "key_with_underscores"}
74+
for idx, expectedKey := range expectedKeys {
75+
actualKey, _ := keysArray.Get(context.Background(), runtime.NewInt(idx))
76+
So(actualKey.String(), ShouldEqual, expectedKey)
77+
}
78+
})
79+
})
80+
}
81+
82+
func TestValuesEdgeCases(t *testing.T) {
83+
Convey("Edge cases for Values function", t, func() {
84+
Convey("When object has nil values", func() {
85+
obj := runtime.NewObjectWith(
86+
runtime.NewObjectProperty("none", runtime.None),
87+
runtime.NewObjectProperty("bool", runtime.NewBoolean(false)),
88+
runtime.NewObjectProperty("empty_string", runtime.NewString("")),
89+
)
90+
91+
actual, err := objects.Values(context.Background(), obj)
92+
actualArray := actual.(*runtime.Array)
93+
94+
So(err, ShouldBeNil)
95+
actualLength, _ := actualArray.Length(context.Background())
96+
So(actualLength, ShouldEqual, 3)
97+
98+
// Verify all values are present
99+
foundNone := false
100+
foundBool := false
101+
foundEmptyString := false
102+
103+
actualArray.ForEach(context.Background(), func(ctx context.Context, val runtime.Value, idx runtime.Int) (runtime.Boolean, error) {
104+
if runtime.CompareValues(val, runtime.None) == 0 {
105+
foundNone = true
106+
}
107+
if runtime.CompareValues(val, runtime.NewBoolean(false)) == 0 {
108+
foundBool = true
109+
}
110+
if runtime.CompareValues(val, runtime.NewString("")) == 0 {
111+
foundEmptyString = true
112+
}
113+
return true, nil
114+
})
115+
116+
So(foundNone, ShouldBeTrue)
117+
So(foundBool, ShouldBeTrue)
118+
So(foundEmptyString, ShouldBeTrue)
119+
})
120+
121+
Convey("When object has deeply nested structures", func() {
122+
deepObject := runtime.NewObjectWith(
123+
runtime.NewObjectProperty("level1", runtime.NewObjectWith(
124+
runtime.NewObjectProperty("level2", runtime.NewObjectWith(
125+
runtime.NewObjectProperty("level3", runtime.NewString("deep")),
126+
)),
127+
)),
128+
)
129+
130+
obj := runtime.NewObjectWith(
131+
runtime.NewObjectProperty("deep", deepObject),
132+
)
133+
134+
actual, err := objects.Values(context.Background(), obj)
135+
actualArray := actual.(*runtime.Array)
136+
137+
So(err, ShouldBeNil)
138+
actualLength, _ := actualArray.Length(context.Background())
139+
So(actualLength, ShouldEqual, 1)
140+
141+
// Get the deep object and verify it's independent
142+
returnedDeep, _ := actualArray.Get(context.Background(), runtime.NewInt(0))
143+
returnedDeepObj := returnedDeep.(*runtime.Object)
144+
145+
// Modify the original deep object
146+
deepObject.Set(context.Background(), runtime.NewString("modified"), runtime.NewString("value"))
147+
148+
// Check that the returned object wasn't affected
149+
hasModified, _ := returnedDeepObj.ContainsKey(context.Background(), runtime.NewString("modified"))
150+
So(hasModified, ShouldEqual, runtime.False)
151+
})
152+
})
153+
}
154+
155+
func TestZipEdgeCases(t *testing.T) {
156+
Convey("Edge cases for Zip function", t, func() {
157+
Convey("When keys contain special characters", func() {
158+
keys := runtime.NewArrayWith(
159+
runtime.NewString("key with spaces"),
160+
runtime.NewString("key_with_underscores"),
161+
runtime.NewString("key-with-dashes"),
162+
)
163+
vals := runtime.NewArrayWith(
164+
runtime.NewInt(1),
165+
runtime.NewInt(2),
166+
runtime.NewInt(3),
167+
)
168+
169+
actual, err := objects.Zip(context.Background(), keys, vals)
170+
actualObj := actual.(*runtime.Object)
171+
172+
So(err, ShouldBeNil)
173+
174+
// Check all keys are present
175+
val1, _ := actualObj.Get(context.Background(), runtime.NewString("key with spaces"))
176+
So(runtime.CompareValues(val1, runtime.NewInt(1)), ShouldEqual, 0)
177+
178+
val2, _ := actualObj.Get(context.Background(), runtime.NewString("key_with_underscores"))
179+
So(runtime.CompareValues(val2, runtime.NewInt(2)), ShouldEqual, 0)
180+
181+
val3, _ := actualObj.Get(context.Background(), runtime.NewString("key-with-dashes"))
182+
So(runtime.CompareValues(val3, runtime.NewInt(3)), ShouldEqual, 0)
183+
})
184+
185+
Convey("When values are all None", func() {
186+
keys := runtime.NewArrayWith(
187+
runtime.NewString("k1"),
188+
runtime.NewString("k2"),
189+
)
190+
vals := runtime.NewArrayWith(
191+
runtime.None,
192+
runtime.None,
193+
)
194+
195+
actual, err := objects.Zip(context.Background(), keys, vals)
196+
actualObj := actual.(*runtime.Object)
197+
198+
So(err, ShouldBeNil)
199+
200+
val1, _ := actualObj.Get(context.Background(), runtime.NewString("k1"))
201+
So(runtime.CompareValues(val1, runtime.None), ShouldEqual, 0)
202+
203+
val2, _ := actualObj.Get(context.Background(), runtime.NewString("k2"))
204+
So(runtime.CompareValues(val2, runtime.None), ShouldEqual, 0)
205+
})
206+
})
207+
}
208+
209+
func TestMergeEdgeCases(t *testing.T) {
210+
Convey("Edge cases for Merge functions", t, func() {
211+
Convey("Merge with empty objects", func() {
212+
obj1 := runtime.NewObject()
213+
obj2 := runtime.NewObjectWith(
214+
runtime.NewObjectProperty("key", runtime.NewString("value")),
215+
)
216+
217+
merged, err := objects.Merge(context.Background(), obj1, obj2)
218+
mergedObj := merged.(*runtime.Object)
219+
220+
So(err, ShouldBeNil)
221+
222+
val, _ := mergedObj.Get(context.Background(), runtime.NewString("key"))
223+
So(runtime.CompareValues(val, runtime.NewString("value")), ShouldEqual, 0)
224+
})
225+
226+
Convey("MergeRecursive with identical objects", func() {
227+
obj := runtime.NewObjectWith(
228+
runtime.NewObjectProperty("key", runtime.NewString("value")),
229+
)
230+
231+
merged, err := objects.MergeRecursive(context.Background(), obj, obj)
232+
233+
So(err, ShouldBeNil)
234+
So(runtime.CompareValues(merged, obj), ShouldEqual, 0)
235+
})
236+
})
237+
}

pkg/stdlib/objects/merge_recursive.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ func MergeRecursive(ctx context.Context, args ...runtime.Value) (runtime.Value,
3838
}
3939

4040
func merge(ctx context.Context, src, dst runtime.Value) (runtime.Value, error) {
41-
if runtime.CompareValues(src, dst) != 0 {
41+
// If both values are equal, no need to merge
42+
if runtime.CompareValues(src, dst) == 0 {
4243
return src, nil
4344
}
4445

0 commit comments

Comments
 (0)