Skip to content

Commit c4788c3

Browse files
📝 CodeRabbit Chat: Add generated unit tests
1 parent 2fd37e9 commit c4788c3

2 files changed

Lines changed: 382 additions & 0 deletions

File tree

internal/cel/functions_test.go

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,59 @@ import (
88
"github.com/stretchr/testify/require"
99
)
1010

11+
// ── normalizeJSONNumbers unit tests ──────────────────────────────────────────
12+
13+
func TestNormalizeJSONNumbers_ExponentFloat(t *testing.T) {
14+
result := normalizeJSONNumbers(json.Number("1e5"))
15+
assert.Equal(t, float64(100000), result)
16+
}
17+
18+
func TestNormalizeJSONNumbers_NegativeFloat(t *testing.T) {
19+
result := normalizeJSONNumbers(json.Number("-3.14"))
20+
assert.Equal(t, float64(-3.14), result)
21+
}
22+
23+
func TestNormalizeJSONNumbers_IntegerMaxInt64(t *testing.T) {
24+
// 9223372036854775807 is exactly math.MaxInt64 — must come back as int64.
25+
result := normalizeJSONNumbers(json.Number("9223372036854775807"))
26+
assert.Equal(t, int64(9223372036854775807), result)
27+
}
28+
29+
func TestNormalizeJSONNumbers_OverflowInt64PreservedAsString(t *testing.T) {
30+
// One past MaxInt64 — cannot fit in int64; must preserve as string.
31+
result := normalizeJSONNumbers(json.Number("9223372036854775808"))
32+
assert.Equal(t, "9223372036854775808", result)
33+
}
34+
35+
func TestNormalizeJSONNumbers_NestedMap(t *testing.T) {
36+
input := map[string]any{
37+
"count": json.Number("42"),
38+
"ratio": json.Number("0.5"),
39+
"label": "hello",
40+
}
41+
result := normalizeJSONNumbers(input)
42+
m := result.(map[string]any)
43+
assert.Equal(t, int64(42), m["count"])
44+
assert.Equal(t, float64(0.5), m["ratio"])
45+
assert.Equal(t, "hello", m["label"])
46+
}
47+
48+
func TestNormalizeJSONNumbers_NestedArray(t *testing.T) {
49+
input := []any{json.Number("1"), json.Number("2.5"), "text"}
50+
result := normalizeJSONNumbers(input)
51+
arr := result.([]any)
52+
assert.Equal(t, int64(1), arr[0])
53+
assert.Equal(t, float64(2.5), arr[1])
54+
assert.Equal(t, "text", arr[2])
55+
}
56+
57+
func TestNormalizeJSONNumbers_PassthroughTypes(t *testing.T) {
58+
// Non-number types should be returned unchanged.
59+
assert.Equal(t, true, normalizeJSONNumbers(true))
60+
assert.Equal(t, "hello", normalizeJSONNumbers("hello"))
61+
assert.Nil(t, normalizeJSONNumbers(nil))
62+
}
63+
1164
func TestFunc_ToLower(t *testing.T) {
1265
eval, err := NewEvaluator()
1366
require.NoError(t, err)
@@ -460,6 +513,16 @@ func TestFunc_FormatTimestamp(t *testing.T) {
460513
expr: `formatTimestamp(parseTimestamp("2024-01-15T14:30:00Z"), "15:04")`,
461514
want: "14:30",
462515
},
516+
{
517+
name: "named month format",
518+
expr: `formatTimestamp(parseTimestamp("2024-01-15T00:00:00Z"), "Jan 2, 2006")`,
519+
want: "Jan 15, 2024",
520+
},
521+
{
522+
name: "rfc3339 roundtrip",
523+
expr: `formatTimestamp(parseTimestamp("2026-03-24T00:00:00Z"), "2006-01-02T15:04:05Z07:00")`,
524+
want: "2026-03-24T00:00:00Z",
525+
},
463526
}
464527
for _, tt := range tests {
465528
t.Run(tt.name, func(t *testing.T) {
@@ -469,3 +532,224 @@ func TestFunc_FormatTimestamp(t *testing.T) {
469532
})
470533
}
471534
}
535+
536+
// ── Additional boundary and regression tests ──────────────────────────────────
537+
538+
func TestFunc_Obj_MaxArity(t *testing.T) {
539+
// obj() supports up to 5 key-value pairs (10 args). Verify all registered
540+
// overloads (2, 4, 6, 8, 10 args) produce the correct maps.
541+
eval, err := NewEvaluator()
542+
require.NoError(t, err)
543+
544+
t.Run("three pairs (6 args)", func(t *testing.T) {
545+
result, err := eval.Eval(`obj("a", 1, "b", 2, "c", 3)`, newTestContext())
546+
require.NoError(t, err)
547+
assert.Equal(t, map[string]any{"a": int64(1), "b": int64(2), "c": int64(3)}, result)
548+
})
549+
550+
t.Run("four pairs (8 args)", func(t *testing.T) {
551+
result, err := eval.Eval(`obj("a", 1, "b", 2, "c", 3, "d", 4)`, newTestContext())
552+
require.NoError(t, err)
553+
assert.Equal(t, map[string]any{"a": int64(1), "b": int64(2), "c": int64(3), "d": int64(4)}, result)
554+
})
555+
556+
t.Run("five pairs (10 args — max arity)", func(t *testing.T) {
557+
result, err := eval.Eval(`obj("a", 1, "b", 2, "c", 3, "d", 4, "e", 5)`, newTestContext())
558+
require.NoError(t, err)
559+
assert.Equal(t, map[string]any{
560+
"a": int64(1), "b": int64(2), "c": int64(3),
561+
"d": int64(4), "e": int64(5),
562+
}, result)
563+
})
564+
}
565+
566+
func TestFunc_Flatten_MixedScalarAndSublist(t *testing.T) {
567+
// The flatten implementation passes non-list elements through unchanged.
568+
// A list like [1, [2, 3]] should produce [1, 2, 3].
569+
eval, err := NewEvaluator()
570+
require.NoError(t, err)
571+
572+
ctx := newTestContext()
573+
ctx.Inputs["mixed"] = []any{int64(1), []any{int64(2), int64(3)}}
574+
result, err := eval.Eval(`flatten(inputs.mixed)`, ctx)
575+
require.NoError(t, err)
576+
assert.Equal(t, []any{int64(1), int64(2), int64(3)}, result)
577+
}
578+
579+
func TestFunc_Default_FalsyButNonNull(t *testing.T) {
580+
// false and 0 are falsy but are NOT null — default() must return them unchanged.
581+
eval, err := NewEvaluator()
582+
require.NoError(t, err)
583+
584+
t.Run("false returns false not fallback", func(t *testing.T) {
585+
result, err := eval.Eval(`default(false, true)`, newTestContext())
586+
require.NoError(t, err)
587+
assert.Equal(t, false, result)
588+
})
589+
590+
t.Run("zero returns zero not fallback", func(t *testing.T) {
591+
result, err := eval.Eval(`default(0, 99)`, newTestContext())
592+
require.NoError(t, err)
593+
assert.Equal(t, int64(0), result)
594+
})
595+
596+
t.Run("empty string returns empty string not fallback", func(t *testing.T) {
597+
result, err := eval.Eval(`default("", "fallback")`, newTestContext())
598+
require.NoError(t, err)
599+
assert.Equal(t, "", result)
600+
})
601+
}
602+
603+
func TestFunc_Split_EmptySeparator(t *testing.T) {
604+
// strings.Split("abc", "") returns ["a", "b", "c"] — each character.
605+
eval, err := NewEvaluator()
606+
require.NoError(t, err)
607+
608+
result, err := eval.Eval(`"abc".split("")`, newTestContext())
609+
require.NoError(t, err)
610+
assert.Equal(t, []any{"a", "b", "c"}, result)
611+
}
612+
613+
func TestFunc_Replace_EmptyOldString(t *testing.T) {
614+
// strings.ReplaceAll("ab", "", "X") inserts X between and around each char.
615+
eval, err := NewEvaluator()
616+
require.NoError(t, err)
617+
618+
result, err := eval.Eval(`"ab".replace("", "X")`, newTestContext())
619+
require.NoError(t, err)
620+
assert.Equal(t, "XaXbX", result)
621+
}
622+
623+
func TestFunc_ParseInt_WhitespaceIsInvalid(t *testing.T) {
624+
// strconv.ParseInt is strict — " 42" (leading space) must fail.
625+
eval, err := NewEvaluator()
626+
require.NoError(t, err)
627+
628+
_, err = eval.Eval(`parseInt(" 42")`, newTestContext())
629+
require.Error(t, err)
630+
}
631+
632+
func TestFunc_JsonDecode_FloatValue(t *testing.T) {
633+
eval, err := NewEvaluator()
634+
require.NoError(t, err)
635+
636+
result, err := eval.Eval(`jsonDecode("3.14")`, newTestContext())
637+
require.NoError(t, err)
638+
assert.Equal(t, float64(3.14), result)
639+
}
640+
641+
func TestFunc_JsonDecode_BoolValue(t *testing.T) {
642+
eval, err := NewEvaluator()
643+
require.NoError(t, err)
644+
645+
result, err := eval.Eval(`jsonDecode("true")`, newTestContext())
646+
require.NoError(t, err)
647+
assert.Equal(t, true, result)
648+
}
649+
650+
func TestFunc_JsonDecode_ArrayOfIntegers(t *testing.T) {
651+
eval, err := NewEvaluator()
652+
require.NoError(t, err)
653+
654+
result, err := eval.Eval(`jsonDecode("[1, 2, 3]")`, newTestContext())
655+
require.NoError(t, err)
656+
657+
arr, ok := result.([]any)
658+
require.True(t, ok, "expected []any, got %T", result)
659+
assert.Equal(t, []any{int64(1), int64(2), int64(3)}, arr)
660+
}
661+
662+
func TestFunc_JsonDecode_NestedObject(t *testing.T) {
663+
eval, err := NewEvaluator()
664+
require.NoError(t, err)
665+
666+
result, err := eval.Eval(`jsonDecode("{\"a\":{\"b\":42}}")`, newTestContext())
667+
require.NoError(t, err)
668+
669+
m, ok := result.(map[string]any)
670+
require.True(t, ok)
671+
inner, ok := m["a"].(map[string]any)
672+
require.True(t, ok, "expected inner map")
673+
assert.Equal(t, int64(42), inner["b"])
674+
}
675+
676+
func TestFunc_JsonEncode_List(t *testing.T) {
677+
eval, err := NewEvaluator()
678+
require.NoError(t, err)
679+
680+
result, err := eval.Eval(`jsonEncode(["a", "b", "c"])`, newTestContext())
681+
require.NoError(t, err)
682+
683+
s, ok := result.(string)
684+
require.True(t, ok)
685+
var arr []string
686+
require.NoError(t, json.Unmarshal([]byte(s), &arr))
687+
assert.Equal(t, []string{"a", "b", "c"}, arr)
688+
}
689+
690+
func TestFunc_JsonEncode_Primitive(t *testing.T) {
691+
eval, err := NewEvaluator()
692+
require.NoError(t, err)
693+
694+
t.Run("integer", func(t *testing.T) {
695+
result, err := eval.Eval(`jsonEncode(42)`, newTestContext())
696+
require.NoError(t, err)
697+
assert.Equal(t, "42", result)
698+
})
699+
700+
t.Run("boolean", func(t *testing.T) {
701+
result, err := eval.Eval(`jsonEncode(true)`, newTestContext())
702+
require.NoError(t, err)
703+
assert.Equal(t, "true", result)
704+
})
705+
706+
t.Run("string", func(t *testing.T) {
707+
result, err := eval.Eval(`jsonEncode("hello")`, newTestContext())
708+
require.NoError(t, err)
709+
assert.Equal(t, `"hello"`, result)
710+
})
711+
}
712+
713+
func TestFunc_ParseTimestamp_NamedMonthFormat(t *testing.T) {
714+
// "Jan 2, 2006" is one of the supported layouts in parseTimestamp.
715+
eval, err := NewEvaluator()
716+
require.NoError(t, err)
717+
718+
result, err := eval.Eval(`formatTimestamp(parseTimestamp("Mar 15, 2025"), "2006-01-02")`, newTestContext())
719+
require.NoError(t, err)
720+
assert.Equal(t, "2025-03-15", result)
721+
}
722+
723+
func TestFunc_StringChaining(t *testing.T) {
724+
// Verify that string methods can be chained.
725+
eval, err := NewEvaluator()
726+
require.NoError(t, err)
727+
728+
result, err := eval.Eval(`" HELLO WORLD ".trim().toLower()`, newTestContext())
729+
require.NoError(t, err)
730+
assert.Equal(t, "hello world", result)
731+
}
732+
733+
func TestFunc_ResolveString_WithCustomFunction(t *testing.T) {
734+
// Verify that custom functions work when embedded in {{ }} template strings.
735+
eval, err := NewEvaluator()
736+
require.NoError(t, err)
737+
738+
result, err := eval.ResolveString(`tag:{{ "PRODUCTION".toLower() }}`, newTestContext())
739+
require.NoError(t, err)
740+
assert.Equal(t, "tag:production", result)
741+
}
742+
743+
func TestFunc_JsonRoundtrip(t *testing.T) {
744+
// jsonEncode followed by jsonDecode must preserve the original value.
745+
eval, err := NewEvaluator()
746+
require.NoError(t, err)
747+
748+
result, err := eval.Eval(`jsonDecode(jsonEncode(obj("x", 1, "y", "hello")))`, newTestContext())
749+
require.NoError(t, err)
750+
751+
m, ok := result.(map[string]any)
752+
require.True(t, ok)
753+
assert.Equal(t, int64(1), m["x"])
754+
assert.Equal(t, "hello", m["y"])
755+
}

0 commit comments

Comments
 (0)