Some checks failed
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Has been skipped
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-go-pg (push) Failing after 2m39s
CI/CD / test-sqlite (push) Successful in 3m0s
CI/CD / build-and-deploy (push) Has been skipped
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
163 lines
5.0 KiB
Go
163 lines
5.0 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"go.starlark.net/starlark"
|
|
)
|
|
|
|
func runRoutingScript(t *testing.T, script string) starlark.StringDict {
|
|
t.Helper()
|
|
mod := BuildRoutingModule()
|
|
predeclared := starlark.StringDict{"routing": mod}
|
|
globals, err := starlark.ExecFile(&starlark.Thread{Name: "test"}, "test.star", script, predeclared)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return globals
|
|
}
|
|
|
|
func runRoutingScriptErr(t *testing.T, script string) error {
|
|
t.Helper()
|
|
mod := BuildRoutingModule()
|
|
predeclared := starlark.StringDict{"routing": mod}
|
|
_, err := starlark.ExecFile(&starlark.Thread{Name: "test"}, "test.star", script, predeclared)
|
|
return err
|
|
}
|
|
|
|
func TestRoutingEvaluate_SingleMatch(t *testing.T) {
|
|
globals := runRoutingScript(t, `
|
|
result = routing.evaluate([
|
|
{"field": "priority", "op": "eq", "value": "high", "target": "escalation"},
|
|
], {"priority": "high"})
|
|
`)
|
|
result := globals["result"]
|
|
if s, ok := result.(starlark.String); !ok || string(s) != "escalation" {
|
|
t.Fatalf("expected 'escalation', got %v", result)
|
|
}
|
|
}
|
|
|
|
func TestRoutingEvaluate_FirstMatchWins(t *testing.T) {
|
|
globals := runRoutingScript(t, `
|
|
result = routing.evaluate([
|
|
{"field": "amount", "op": "gt", "value": 100, "target": "big"},
|
|
{"field": "amount", "op": "gt", "value": 50, "target": "medium"},
|
|
], {"amount": 200})
|
|
`)
|
|
result := globals["result"]
|
|
if s, ok := result.(starlark.String); !ok || string(s) != "big" {
|
|
t.Fatalf("expected 'big', got %v", result)
|
|
}
|
|
}
|
|
|
|
func TestRoutingEvaluate_NoMatch(t *testing.T) {
|
|
globals := runRoutingScript(t, `
|
|
result = routing.evaluate([
|
|
{"field": "status", "op": "eq", "value": "done", "target": "finish"},
|
|
], {"status": "pending"})
|
|
`)
|
|
if globals["result"] != starlark.None {
|
|
t.Fatalf("expected None, got %v", globals["result"])
|
|
}
|
|
}
|
|
|
|
func TestRoutingEvaluate_EmptyRules(t *testing.T) {
|
|
globals := runRoutingScript(t, `
|
|
result = routing.evaluate([], {"x": 1})
|
|
`)
|
|
if globals["result"] != starlark.None {
|
|
t.Fatalf("expected None, got %v", globals["result"])
|
|
}
|
|
}
|
|
|
|
func TestRoutingEvaluate_AllOperators(t *testing.T) {
|
|
globals := runRoutingScript(t, `
|
|
# exists
|
|
r1 = routing.evaluate([{"field": "x", "op": "exists", "value": "", "target": "yes"}], {"x": 1})
|
|
# not_exists
|
|
r2 = routing.evaluate([{"field": "x", "op": "not_exists", "value": "", "target": "yes"}], {"y": 1})
|
|
# eq
|
|
r3 = routing.evaluate([{"field": "s", "op": "eq", "value": "abc", "target": "yes"}], {"s": "abc"})
|
|
# neq
|
|
r4 = routing.evaluate([{"field": "s", "op": "neq", "value": "abc", "target": "yes"}], {"s": "xyz"})
|
|
# gt
|
|
r5 = routing.evaluate([{"field": "n", "op": "gt", "value": 10, "target": "yes"}], {"n": 20})
|
|
# lt
|
|
r6 = routing.evaluate([{"field": "n", "op": "lt", "value": 10, "target": "yes"}], {"n": 5})
|
|
# gte
|
|
r7 = routing.evaluate([{"field": "n", "op": "gte", "value": 10, "target": "yes"}], {"n": 10})
|
|
# lte
|
|
r8 = routing.evaluate([{"field": "n", "op": "lte", "value": 10, "target": "yes"}], {"n": 10})
|
|
# in
|
|
r9 = routing.evaluate([{"field": "s", "op": "in", "value": ["a", "b", "c"], "target": "yes"}], {"s": "b"})
|
|
# contains
|
|
r10 = routing.evaluate([{"field": "s", "op": "contains", "value": "ell", "target": "yes"}], {"s": "hello"})
|
|
`)
|
|
for _, name := range []string{"r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10"} {
|
|
v := globals[name]
|
|
if s, ok := v.(starlark.String); !ok || string(s) != "yes" {
|
|
t.Errorf("%s: expected 'yes', got %v", name, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRoutingEvaluate_TypeCoercion(t *testing.T) {
|
|
globals := runRoutingScript(t, `
|
|
# Numeric string vs int
|
|
r1 = routing.evaluate([
|
|
{"field": "n", "op": "gt", "value": 10, "target": "yes"},
|
|
], {"n": "20"})
|
|
|
|
# Int equality via string normalization
|
|
r2 = routing.evaluate([
|
|
{"field": "n", "op": "eq", "value": 42, "target": "yes"},
|
|
], {"n": 42})
|
|
`)
|
|
for _, name := range []string{"r1", "r2"} {
|
|
v := globals[name]
|
|
if s, ok := v.(starlark.String); !ok || string(s) != "yes" {
|
|
t.Errorf("%s: expected 'yes', got %v", name, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRoutingEvaluate_BadInput(t *testing.T) {
|
|
// rules must be a list
|
|
err1 := runRoutingScriptErr(t, `routing.evaluate("bad", {})`)
|
|
if err1 == nil {
|
|
t.Fatal("expected error for non-list rules")
|
|
}
|
|
|
|
// data must be a dict
|
|
err2 := runRoutingScriptErr(t, `routing.evaluate([], "bad")`)
|
|
if err2 == nil {
|
|
t.Fatal("expected error for non-dict data")
|
|
}
|
|
|
|
// rule missing required fields
|
|
err3 := runRoutingScriptErr(t, `routing.evaluate([{"field": "x"}], {})`)
|
|
if err3 == nil {
|
|
t.Fatal("expected error for incomplete rule")
|
|
}
|
|
}
|
|
|
|
func TestRoutingEvaluate_MissingFields(t *testing.T) {
|
|
globals := runRoutingScript(t, `
|
|
# Field not in data — eq should not match
|
|
r1 = routing.evaluate([
|
|
{"field": "absent", "op": "eq", "value": "x", "target": "bad"},
|
|
], {"other": "y"})
|
|
|
|
# not_exists matches when field is absent
|
|
r2 = routing.evaluate([
|
|
{"field": "absent", "op": "not_exists", "value": "", "target": "good"},
|
|
], {"other": "y"})
|
|
`)
|
|
if globals["r1"] != starlark.None {
|
|
t.Fatalf("r1: expected None for missing field, got %v", globals["r1"])
|
|
}
|
|
if s, ok := globals["r2"].(starlark.String); !ok || string(s) != "good" {
|
|
t.Fatalf("r2: expected 'good', got %v", globals["r2"])
|
|
}
|
|
}
|