Files
ttstd-infinite-canvas/backend/internal/app/cloud_agent_node_scoped_snapshot_test.go

93 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app
import (
"encoding/json"
"strings"
"testing"
)
// 线上复现:Agent 读完分镜准备改第三镜,用户在画布上删掉一个无关节点,
// Agent 的修改被整画布哈希判为"画布已变化",整轮失败。
// 分镜/批量表的版本只看目标节点本身:无关节点的增删改不影响;目标节点被改才拒绝。
func TestCloudAgentStoryboardEditSurvivesUnrelatedCanvasChanges(t *testing.T) {
s, canvas := cloudAgentStoryboardFixture(t)
rows := createCloudAgentStoryboardForTest(t, s, canvas)
policy, err := s.RuntimePolicy()
if err != nil {
t.Fatal(err)
}
// 用户在画布上加一个无关节点。
stored, err := s.repo.CanvasProjectForUser("user", canvas.ID)
if err != nil {
t.Fatal(err)
}
doc, _ := creationDocument(stored.PayloadJSON)
doc["nodes"] = append(creationMaps(doc["nodes"]), map[string]any{"id": "scratch", "type": "text", "metadata": map[string]any{"content": "随手记"}})
raw, _ := json.Marshal(doc)
before := stored.PayloadJSON
stored.PayloadJSON = string(raw)
if err := saveCreationCanvasWithHistory(s.repo, stored, before); err != nil {
t.Fatal(err)
}
// Agent 用生产读工具读取分镜,拿到的版本号。
state := &cloudAgentRuntime{Request: CloudAgentRequest{CanvasID: canvas.ID}}
read := cloudAgentCall{ID: "read"}
read.Function.Name = "canvas_read_storyboard"
read.Function.Arguments = `{"nodeId":"storyboard-1"}`
view, err := cloudAgentReadTool(s.repo, "user", state, read)
if err != nil {
t.Fatal(err)
}
readHash, _ := view.(map[string]any)["snapshotHash"].(string)
if readHash == "" {
t.Fatal("storyboard read returned no snapshotHash")
}
// 用户删掉那个无关节点(与截图里的操作一致)。
stored, _ = s.repo.CanvasProjectForUser("user", canvas.ID)
doc, _ = creationDocument(stored.PayloadJSON)
kept := []map[string]any{}
for _, node := range creationMaps(doc["nodes"]) {
if stringValue(node["id"]) != "scratch" {
kept = append(kept, node)
}
}
doc["nodes"] = kept
raw, _ = json.Marshal(doc)
before = stored.PayloadJSON
stored.PayloadJSON = string(raw)
if err := saveCreationCanvasWithHistory(s.repo, stored, before); err != nil {
t.Fatal(err)
}
edit := cloudAgentStoryboardCall(t, "canvas_edit_storyboard", "edit-after-unrelated-delete", map[string]any{
"snapshotHash": readHash, "nodeId": "storyboard-1", "action": "update", "rowId": stringValue(rows[0]["id"]),
"patch": map[string]any{"dialogue": "差两文!"},
})
result, err := applyCloudAgentStoryboardMutation(s.repo, "user", canvas.ID, edit, policy)
if err != nil {
t.Fatalf("unrelated node deletion must not block the storyboard edit: %v", err)
}
after, _ := s.repo.CanvasProjectForUser("user", canvas.ID)
if !strings.Contains(after.PayloadJSON, "差两文!") {
t.Fatal("edit was not written")
}
// 返回的新版本号可以直接用于下一次修改(同一轮连续改多镜)。
nextHash, _ := result.(map[string]any)["snapshotHash"].(string)
next := cloudAgentStoryboardCall(t, "canvas_edit_storyboard", "next-edit", map[string]any{
"snapshotHash": nextHash, "nodeId": "storyboard-1", "action": "update", "rowId": stringValue(rows[1]["id"]),
"patch": map[string]any{"dialogue": "拍铜板报价"},
})
if _, err := applyCloudAgentStoryboardMutation(s.repo, "user", canvas.ID, next, policy); err != nil {
t.Fatalf("follow-up edit with returned version failed: %v", err)
}
// 目标分镜本身被别处改过:旧版本号必须被拒绝,不能覆盖用户的修改。
if _, err := applyCloudAgentStoryboardMutation(s.repo, "user", canvas.ID, edit, policy); err == nil || !strings.Contains(err.Error(), "被修改过") {
t.Fatalf("stale version of the same storyboard must be rejected: %v", err)
}
}