Feat v0.9.8 conditional routing SDK primitive
Some checks failed
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Has been skipped
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / e2e-smoke (pull_request) Has been skipped
CI/CD / test-go-pg (pull_request) Failing after 2m42s
CI/CD / test-sqlite (pull_request) Successful in 3m5s
CI/CD / build-and-deploy (pull_request) Has been skipped
Some checks failed
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Has been skipped
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / e2e-smoke (pull_request) Has been skipped
CI/CD / test-go-pg (pull_request) Failing after 2m42s
CI/CD / test-sqlite (pull_request) Successful in 3m5s
CI/CD / build-and-deploy (pull_request) Has been skipped
Promote workflow branch-rule evaluation to a generic Starlark routing.evaluate(rules, data) module available to all extensions. 10 operators, first-match-wins, no permission gate. 8 new tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
162
server/sandbox/routing_module_test.go
Normal file
162
server/sandbox/routing_module_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
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"])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user