93 lines
3.7 KiB
Go
93 lines
3.7 KiB
Go
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)
|
||
}
|
||
}
|