Files
foundry-core/internal/platform/subgraph_migration_test.go
T
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

103 lines
4.6 KiB
Go

package platform
import (
"encoding/json"
"reflect"
"testing"
)
const legacyMigrationFixture = `{"schema_version":1,"nodes":[
{"id":"f","type":"frame","position":{"x":100,"y":200},"width":640,"height":400,"data":{"kind":"frame","title":"Frame","text":"","labelMode":"fixed"}},
{"id":"empty","type":"frame","position":{"x":0,"y":0},"width":400,"height":300,"data":{"kind":"frame","title":"Empty","text":""}},
{"id":"a","type":"material","position":{"x":150,"y":260},"data":{"kind":"text","title":"A","text":"preserved","frameId":"f","fontSize":24}},
{"id":"b","type":"material","position":{"x":250,"y":300},"data":{"kind":"script","title":"B","text":"","frameId":"f"}},
{"id":"outside","type":"material","position":{"x":160,"y":270},"data":{"kind":"text","title":"Not a member","text":"overlaps frame"}}
],"edges":[{"id":"internal","source":"a","target":"b","sourceHandle":"right","targetHandle":"left"},{"id":"boundary","source":"b","target":"outside","sourceHandle":"right","targetHandle":"left"},{"id":"boundary-2","source":"b","target":"outside","sourceHandle":"right","targetHandle":"top"}],"viewport":{"x":2000,"y":3000,"zoom":2}}`
func TestLegacyMigrationOwnershipAndBoundary(t *testing.T) {
parent := canvasSnapshot{BoardID: "parent", Version: 5, Document: json.RawMessage(legacyMigrationFixture)}
plan := legacyPlan("p", "w", parent)
if plan.ChildCount != 2 || len(plan.Frames) != 2 {
t.Fatal("empty frame omitted")
}
if !reflect.DeepEqual(plan.Frames[1].NodeIDs, []string{"a", "b"}) {
t.Fatalf("non-explicit member included: %+v", plan.Frames)
}
children := map[string]string{"f": "11111111-1111-4111-8111-111111111111", "empty": "22222222-2222-4222-8222-222222222222"}
migrated, docs, err := migrateLegacyDocument(parent.Document, children)
if err != nil {
t.Fatal(err)
}
data := canvasNodeData(migrated)
if data["a"] != nil || data["b"] != nil || data["outside"] == nil {
t.Fatal("ownership duplicated or overlapping node absorbed")
}
ports := data["f"]["ports"].([]any)
if len(ports) != 1 {
t.Fatalf("ports not deduplicated: %+v", ports)
}
if ports[0].(map[string]any)["nodeId"] != "b" {
t.Fatal("boundary port targets wrong child node")
}
var child map[string]any
_ = json.Unmarshal(docs["f"], &child)
nodes := child["nodes"].([]any)
if len(nodes) != 2 {
t.Fatal("child membership")
}
a := nodes[0].(map[string]any)
position := a["position"].(map[string]any)
if position["x"] != float64(26) || position["y"] != float64(24) {
t.Fatalf("inverse projection used viewport: %v", position)
}
if a["data"].(map[string]any)["text"] != "preserved" || a["data"].(map[string]any)["frameId"] != nil {
t.Fatal("member data changed")
}
if len(child["edges"].([]any)) != 1 {
t.Fatal("internal edge lost")
}
var p map[string]any
_ = json.Unmarshal(migrated, &p)
if len(p["edges"].([]any)) != 2 {
t.Fatal("boundary edges lost")
}
for _, v := range p["edges"].([]any) {
e := v.(map[string]any)
if e["source"] != "f" {
t.Fatal("boundary edge not remapped")
}
}
// A second plan skips linked frames and does not create more children.
if legacyPlan("p", "w", canvasSnapshot{BoardID: "parent", Version: 6, Document: migrated}).ChildCount != 0 {
t.Fatal("migration not repeatable")
}
changed := parent
changed.Version++
if legacyPlan("p", "w", changed).PlanID == plan.PlanID {
t.Fatal("plan not bound to version")
}
}
func TestMigrationPreservesBezierGeometry(t *testing.T) {
var doc map[string]any
_ = json.Unmarshal([]byte(legacyMigrationFixture), &doc)
doc["schema_version"] = 2
e := doc["edges"].([]any)[0].(map[string]any)
e["data"] = map[string]any{"path": map[string]any{"type": "bezier-chain", "startControl": map[string]any{"x": 90, "y": 0}, "knots": []any{map[string]any{"id": "k", "position": map[string]any{"x": 200, "y": 280}, "in": map[string]any{"x": -20, "y": 0}, "out": map[string]any{"x": 20, "y": 0}, "mode": "symmetric"}}}}
raw, _ := json.Marshal(doc)
_, children, err := migrateLegacyDocument(raw, map[string]string{"f": "11111111-1111-4111-8111-111111111111", "empty": "22222222-2222-4222-8222-222222222222"})
if err != nil {
t.Fatal(err)
}
var child map[string]any
_ = json.Unmarshal(children["f"], &child)
path := child["edges"].([]any)[0].(map[string]any)["data"].(map[string]any)["path"].(map[string]any)
knot := path["knots"].([]any)[0].(map[string]any)
if knot["position"].(map[string]any)["x"] != float64(76) || knot["position"].(map[string]any)["y"] != float64(44) {
t.Fatal("knot position not translated")
}
if knot["in"].(map[string]any)["x"] != float64(-20) || path["startControl"].(map[string]any)["x"] != float64(90) {
t.Fatal("control vector translated")
}
}