Files
codex-bot d6ee8942f1
Foundry Go checks / check (push) Successful in 2m40s
Foundry Go checks / check (pull_request) Successful in 2m36s
Implement Canvas V2 subgraphs, assets and asynchronous AI execution
2026-11-06 18:36:00 +08:00

108 lines
4.5 KiB
Go

package platform
import (
"encoding/json"
"math"
"net/http/httptest"
"reflect"
"strconv"
"strings"
"testing"
)
func TestCanvasV2EdgeRoundTrip(t *testing.T) {
raw := strings.Replace(canvasFixture, `"schema_version":1`, `"schema_version":2`, 1)
data := `{"appearance":{"color":"#AA968B","width":2,"opacity":1,"line":"dashed","head":{"type":"triangle","size":12,"filled":true},"tail":{"type":"circle","size":8,"filled":false}},"animation":{"mode":"executing","effect":"flow","direction":"reverse","durationMs":1600,"color":"#FFFFFF"},"path":{"type":"bezier-chain","startControl":{"x":90,"y":0},"endControl":{"x":-90,"y":0},"knots":[{"id":"k1","position":{"x":450,"y":280},"in":{"x":-60,"y":0},"out":{"x":60,"y":0},"mode":"symmetric"}]},"execution":{"enabled":true}}`
raw = strings.Replace(raw, `"id":"e"`, `"data":`+data+`,"id":"e"`, 1)
out, ok := normalizeCanvas(json.RawMessage(raw))
if !ok {
t.Fatal("valid V2 rejected")
}
var doc map[string]any
_ = json.Unmarshal(out, &doc)
var expected any
_ = json.Unmarshal([]byte(data), &expected)
if !reflect.DeepEqual(doc["edges"].([]any)[0].(map[string]any)["data"], expected) {
t.Fatal("edge data lost")
}
cases := [][2]string{
{`"width":2`, `"width":21`}, {`"opacity":1`, `"opacity":0`},
{`"line":"dashed"`, `"line":"css"`}, {`"type":"triangle"`, `"type":"url(#marker)"`},
{`"durationMs":1600`, `"durationMs":249`}, {`"mode":"executing"`, `"mode":"running"`},
{`"x":60,"y":0`, `"x":60,"y":1`}, {`"x":60,"y":0`, `"x":-60,"y":0`},
{`"enabled":true`, `"enabled":"true"`}, {`"schema_version":2`, `"schema_version":1`},
}
for _, c := range cases {
if _, ok := normalizeCanvas(json.RawMessage(strings.Replace(raw, c[0], c[1], 1))); ok {
t.Errorf("accepted invalid replacement %s", c[1])
}
}
}
func TestCanvasSchemaAccept(t *testing.T) {
for _, tc := range []struct {
accept string
want bool
}{
{"application/json", false}, {"application/vnd.foundry.canvas+json;version=2", true},
{"application/json, application/vnd.foundry.canvas+json; version=2", true},
{"application/vnd.foundry.canvas+json;version=2;q=0", false},
{"application/vnd.foundry.canvas+json;version=2;q=0.0", false},
{"application/vnd.foundry.canvas+json;version=2;q=NaN", false},
{"application/vnd.foundry.canvas+json;version=20", false},
} {
r := httptest.NewRequest("GET", "/", nil)
r.Header.Set("Accept", tc.accept)
if acceptsCanvasV2(r) != tc.want {
t.Error(tc.accept)
}
}
}
func TestCanvasV2CurveLimits(t *testing.T) {
knot := func(id string) map[string]any {
return map[string]any{"id": id, "position": map[string]any{"x": 0.0, "y": 0.0}, "in": map[string]any{"x": -1.0, "y": 0.0}, "out": map[string]any{"x": 1.0, "y": 0.0}, "mode": "smooth"}
}
for _, count := range []int{0, 1, 2, 64, 65} {
knots := []any{}
for i := 0; i < count; i++ {
knots = append(knots, knot(strconv.Itoa(i)))
}
data := map[string]any{"path": map[string]any{"type": "bezier-chain", "knots": knots}}
if validCanvasEdgeData(data) != (count <= 64) {
t.Errorf("knot count %d", count)
}
}
duplicate := map[string]any{"path": map[string]any{"type": "bezier-chain", "knots": []any{knot("same"), knot("same")}}}
if validCanvasEdgeData(duplicate) {
t.Fatal("duplicate knot ID")
}
for _, bad := range []float64{math.NaN(), math.Inf(1), math.Inf(-1), 1000001} {
k := knot("k")
k["position"].(map[string]any)["x"] = bad
if validCanvasEdgeData(map[string]any{"path": map[string]any{"type": "bezier-chain", "knots": []any{k}}}) {
t.Errorf("accepted non-finite/out-of-range coordinate %v", bad)
}
}
}
func TestCanvasV2AppearanceAndAnimationEnums(t *testing.T) {
for _, marker := range []string{"none", "open-arrow", "triangle", "circle", "diamond", "square"} {
for _, line := range []string{"solid", "dashed", "dotted"} {
data := map[string]any{"appearance": map[string]any{"line": line, "head": map[string]any{"type": marker, "size": 4.0, "filled": true}, "tail": map[string]any{"type": marker, "size": 32.0, "filled": false}}}
if !validCanvasEdgeData(data) {
t.Errorf("marker %s line %s rejected", marker, line)
}
}
}
for _, mode := range []string{"off", "always", "executing"} {
for _, effect := range []string{"flow", "dash", "pulse"} {
for _, direction := range []string{"forward", "reverse"} {
if !validCanvasEdgeData(map[string]any{"animation": map[string]any{"mode": mode, "effect": effect, "direction": direction, "durationMs": 30000.0, "color": "#FFFFFF"}}) {
t.Errorf("animation %s/%s/%s", mode, effect, direction)
}
}
}
}
}