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) } } } } }