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
189 lines
5.6 KiB
TypeScript
189 lines
5.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildAgentTurnSummaryPatchDiff } from "./agentTurnSummaryPatchDiff";
|
|
import { isAgentUnifiedDiffText } from "./agentUnifiedDiffValidation";
|
|
|
|
describe("buildAgentTurnSummaryPatchDiff", () => {
|
|
it("wraps cwd-relative hunk diffs with git headers", () => {
|
|
const diff = buildAgentTurnSummaryPatchDiff({
|
|
cwd: "/workspace/project",
|
|
toolCallId: "call-1",
|
|
changes: [
|
|
{
|
|
path: "/workspace/project/src/app.ts",
|
|
changeType: "modified",
|
|
unifiedDiff: "@@ -1 +1 @@\n-old\n+new"
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(diff).toBe(
|
|
"diff --git a/src/app.ts b/src/app.ts\n--- a/src/app.ts\n+++ b/src/app.ts\n@@ -1 +1 @@\n-old\n+new\n"
|
|
);
|
|
});
|
|
|
|
it("generates add-file diffs from created content", () => {
|
|
const diff = buildAgentTurnSummaryPatchDiff({
|
|
cwd: "/workspace/project",
|
|
toolCallId: "call-1",
|
|
changes: [
|
|
{
|
|
path: "/workspace/project/src/new.ts",
|
|
changeType: "created",
|
|
content: "export const ready = true;\n"
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(diff).toBe(
|
|
"diff --git a/src/new.ts b/src/new.ts\nnew file mode 100644\n--- /dev/null\n+++ b/src/new.ts\n@@ -0,0 +1,1 @@\n+export const ready = true;\n"
|
|
);
|
|
});
|
|
|
|
it("makes Windows absolute paths relative to the patch cwd", () => {
|
|
const diff = buildAgentTurnSummaryPatchDiff({
|
|
cwd: "C:\\Users\\17940\\Documents\\tutti\\test_git",
|
|
toolCallId: "call-1",
|
|
changes: [
|
|
{
|
|
path: "C:/Users/17940/Documents/tutti/test_git/today.txt",
|
|
changeType: "created",
|
|
content: "2026-08-12\n"
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(diff).toBe(
|
|
"diff --git a/today.txt b/today.txt\nnew file mode 100644\n--- /dev/null\n+++ b/today.txt\n@@ -0,0 +1,1 @@\n+2026-08-12\n"
|
|
);
|
|
});
|
|
|
|
it("rebases Windows paths in complete unified diffs case-insensitively", () => {
|
|
const absolutePath = "C:/Users/17940/Documents/tutti/test_git/today.txt";
|
|
const diff = buildAgentTurnSummaryPatchDiff({
|
|
cwd: "c:/users/17940/documents/tutti/test_git",
|
|
toolCallId: "call-1",
|
|
changes: [
|
|
{
|
|
path: absolutePath,
|
|
changeType: "modified",
|
|
unifiedDiff: [
|
|
`diff --git a/${absolutePath} b/${absolutePath}`,
|
|
`--- a/${absolutePath}`,
|
|
`+++ b/${absolutePath}`,
|
|
"@@ -1,2 +1,2 @@",
|
|
"--- old option",
|
|
"+++ new option",
|
|
"-old",
|
|
"+new"
|
|
].join("\n")
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(diff).toBe(
|
|
"diff --git a/today.txt b/today.txt\n--- a/today.txt\n+++ b/today.txt\n@@ -1,2 +1,2 @@\n--- old option\n+++ new option\n-old\n+new\n"
|
|
);
|
|
});
|
|
|
|
it("builds a valid zero-old-count patch for the reported Kimi empty-file edit", () => {
|
|
const content =
|
|
"这是一个示例文本文件。\n\n当前时间:2026-08-20\n用途:测试文件创建\n\n你可以随时修改或删除此文件。";
|
|
const diff = buildAgentTurnSummaryPatchDiff({
|
|
cwd: "C:/Users/17940/Documents/tutti/test_git",
|
|
toolCallId: "kimi-create-note",
|
|
changes: [
|
|
{
|
|
path: "C:/Users/17940/Documents/tutti/test_git/note.txt",
|
|
changeType: "modified",
|
|
oldString: "",
|
|
newString: content
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(new TextEncoder().encode(content)).toHaveLength(132);
|
|
expect(new TextEncoder().encode(diff)).toHaveLength(218);
|
|
expect(diff).toContain("@@ -0,0 +1,6 @@");
|
|
expect(isAgentUnifiedDiffText(diff)).toBe(true);
|
|
});
|
|
|
|
it("normalizes CRLF while preserving a valid zero-new-count patch", () => {
|
|
const diff = buildAgentTurnSummaryPatchDiff({
|
|
cwd: "/workspace/project",
|
|
toolCallId: "clear-file",
|
|
changes: [
|
|
{
|
|
path: "/workspace/project/note.txt",
|
|
changeType: "modified",
|
|
oldString: "first\r\nsecond\r\n",
|
|
newString: ""
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(diff).toContain("@@ -1,2 +0,0 @@");
|
|
expect(diff).not.toContain("\r");
|
|
expect(isAgentUnifiedDiffText(diff)).toBe(true);
|
|
});
|
|
|
|
it("fails closed for binary content without an executable binary diff", () => {
|
|
const diff = buildAgentTurnSummaryPatchDiff({
|
|
cwd: "/workspace/project",
|
|
toolCallId: "binary-write",
|
|
changes: [
|
|
{
|
|
path: "/workspace/project/image.bin",
|
|
changeType: "modified",
|
|
oldString: "old\0bytes",
|
|
newString: "new\0bytes"
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(diff).toBe("");
|
|
});
|
|
|
|
it.each([
|
|
["created", "0000000..e69de29"],
|
|
["deleted", "e69de29..0000000"]
|
|
] as const)("builds an exact %s empty-file patch", (changeType, index) => {
|
|
const diff = buildAgentTurnSummaryPatchDiff({
|
|
cwd: "/workspace/project",
|
|
toolCallId: `empty-${changeType}`,
|
|
changes: [
|
|
{
|
|
path: "/workspace/project/empty.txt",
|
|
changeType,
|
|
...(changeType === "created"
|
|
? { newString: "", content: "" }
|
|
: { oldString: "", content: "" })
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(diff).toContain(`index ${index}`);
|
|
expect(diff).not.toContain("@@");
|
|
});
|
|
it.each([
|
|
{ oldString: "before\n", newString: undefined },
|
|
{ oldString: undefined, newString: "after\n" }
|
|
])(
|
|
"does not invent a modified-file patch from one-sided content",
|
|
(change) => {
|
|
const diff = buildAgentTurnSummaryPatchDiff({
|
|
cwd: "/workspace/project",
|
|
toolCallId: "call-1",
|
|
changes: [
|
|
{
|
|
path: "/workspace/project/src/app.ts",
|
|
changeType: "modified",
|
|
...change
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(diff).toBe("");
|
|
}
|
|
);
|
|
});
|