Files
tutti/packages/agent/daemon/runtime/tool_file_changes_test.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

205 lines
6.4 KiB
Go

package agentruntime
import "testing"
func TestCanonicalFileChangesNormalizeProviderShapes(t *testing.T) {
t.Parallel()
tests := []struct {
name string
payload map[string]any
path string
change string
}{
{
name: "cursor delete kind",
payload: map[string]any{
"kind": "delete",
"content": []any{map[string]any{
"type": "diff", "path": "/workspace/obsolete.txt", "oldText": "obsolete", "newText": "",
}},
},
path: "/workspace/obsolete.txt",
change: "deleted",
},
{
name: "codex nested change kind",
payload: map[string]any{
"kind": "edit",
"input": map[string]any{"changes": []any{map[string]any{
"path": "/workspace/new.go", "kind": map[string]any{"type": "add"}, "diff": "@@ -0,0 +1 @@\n+package new",
}}},
},
path: "/workspace/new.go",
change: "added",
},
{
name: "claude structured patch change",
payload: map[string]any{
"output": map[string]any{"changes": []any{map[string]any{
"path": "/workspace/app.ts", "type": "update", "diff": "@@ -1 +1 @@\n-old\n+new",
}}},
},
path: "/workspace/app.ts",
change: "modified",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
files := payloadArray(canonicalFileChangesFromToolPayload(test.payload)["files"])
if len(files) != 1 || files[0]["path"] != test.path || files[0]["change"] != test.change {
t.Fatalf("canonical files = %#v, want %s %s", files, test.change, test.path)
}
})
}
}
func TestCanonicalFileChangesMovesRawCreatedBodyOutOfUnifiedDiff(t *testing.T) {
t.Parallel()
content := "# Liying\n\n- 自我介绍\n- 欢迎来到我的 README\n"
got := canonicalFileChangesFromToolPayload(map[string]any{
"fileChanges": map[string]any{
"files": []any{map[string]any{
"path": "/workspace/README.md",
"change": "created",
"unifiedDiff": content,
}},
},
})
files := payloadArray(got["files"])
if len(files) != 1 {
t.Fatalf("canonical files = %#v, want one file", files)
}
file := files[0]
if file["change"] != "added" || file["newString"] != content {
t.Fatalf("canonical file = %#v, want added file with its body in newString", file)
}
if _, exists := file["diff"]; exists {
t.Fatalf("canonical file retained invalid diff: %#v", file)
}
if _, exists := file["unifiedDiff"]; exists {
t.Fatalf("canonical file retained invalid unifiedDiff: %#v", file)
}
}
func TestCanonicalFileChangesKeepsValidUnifiedDiff(t *testing.T) {
t.Parallel()
diff := "@@ -1 +1 @@\n-old\n+new"
got := canonicalFileChangesFromToolPayload(map[string]any{
"fileChanges": map[string]any{
"files": []any{map[string]any{
"path": "/workspace/app.ts",
"change": "modified",
"unifiedDiff": diff,
}},
},
})
file := payloadArray(got["files"])[0]
if file["unifiedDiff"] != diff || file["diff"] != diff {
t.Fatalf("canonical file = %#v, want valid unified diff preserved", file)
}
}
func TestCanonicalFileChangesChoosesFirstValidDiffAlias(t *testing.T) {
t.Parallel()
valid := "@@ -1 +1 @@\n-old\n+new"
rawBody := "README\n- bullet\n"
got := canonicalFileChangesFromToolPayload(map[string]any{
"fileChanges": map[string]any{
"files": []any{map[string]any{
"path": "/workspace/app.ts",
"change": "modified",
"diff": rawBody,
"unifiedDiff": valid,
}},
},
})
file := payloadArray(got["files"])[0]
if file["diff"] != valid || file["unifiedDiff"] != valid {
t.Fatalf("canonical file = %#v, want the valid later alias", file)
}
}
func TestCanonicalFileChangesPreservesInvalidModifiedBodyWithoutDiff(t *testing.T) {
t.Parallel()
body := "README\n- bullet\n"
got := canonicalFileChangesFromToolPayload(map[string]any{
"fileChanges": map[string]any{
"files": []any{map[string]any{
"path": "/workspace/README.md",
"change": "modified",
"diff": body,
}},
},
})
file := payloadArray(got["files"])[0]
if file["newString"] != body || file["change"] != "modified" {
t.Fatalf("canonical file = %#v, want modified body preserved as newString", file)
}
if _, exists := file["diff"]; exists {
t.Fatalf("canonical file retained invalid diff: %#v", file)
}
if _, exists := file["content"]; exists {
t.Fatalf("canonical file retained obsolete content field: %#v", file)
}
}
func TestLooksLikeUnifiedDiffRejectsPseudoHunks(t *testing.T) {
t.Parallel()
if looksLikeUnifiedDiff("@@ notes\n- bullet") {
t.Fatal("pseudo hunk was accepted as a unified diff")
}
if !looksLikeUnifiedDiff("@@ -1 +1 @@\n-old\n+new") {
t.Fatal("valid unified diff was rejected")
}
}
func TestMergeCanonicalFileChangesKeepsTurnLevelSemantics(t *testing.T) {
t.Parallel()
current := map[string]any{"files": []any{
map[string]any{"path": "/workspace/new.ts", "change": "added", "oldString": "", "newString": "first"},
map[string]any{"path": "/workspace/old.ts", "change": "modified", "oldString": "before", "newString": "after"},
}}
incoming := map[string]any{"files": []any{
map[string]any{"path": "/workspace/new.ts", "change": "modified", "oldString": "first", "newString": "final"},
map[string]any{"path": "/workspace/old.ts", "change": "deleted", "oldString": "after", "newString": ""},
}}
files := payloadArray(mergeCanonicalFileChanges(current, incoming)["files"])
if len(files) != 2 {
t.Fatalf("merged files = %#v, want two files", files)
}
if files[0]["change"] != "added" || files[0]["oldString"] != "" || files[0]["newString"] != "final" {
t.Fatalf("created-then-edited file = %#v, want added with final content", files[0])
}
if files[1]["change"] != "deleted" || files[1]["oldString"] != "before" || files[1]["newString"] != "" {
t.Fatalf("edited-then-deleted file = %#v, want deleted with original content", files[1])
}
}
func TestMergeCanonicalFileChangesDeduplicatesAndCancelsCreatedFiles(t *testing.T) {
t.Parallel()
current := map[string]any{"files": []any{
map[string]any{"path": "/workspace/a.txt", "change": "added", "newString": "first"},
map[string]any{"path": "/workspace/a.txt", "change": "modified", "newString": "final"},
}}
incoming := map[string]any{"files": []any{
map[string]any{"path": "/workspace/a.txt", "change": "deleted"},
map[string]any{"path": "/workspace/b.txt", "change": "added", "newString": "b"},
}}
files := payloadArray(mergeCanonicalFileChanges(current, incoming)["files"])
if len(files) != 1 || files[0]["path"] != "/workspace/b.txt" {
t.Fatalf("merged files = %#v, want only the surviving file", files)
}
}