fix: 保留 apply-patch CRLF 完整性
This commit is contained in:
@@ -98,6 +98,75 @@ describe("apply-patch v2 fs bulk update path", () => {
|
||||
expect(stderr.text()).toContain("\"remoteOperationCounts\":{\"fs.readFiles\":1,\"fs.applyReplacementsBulk\":1}");
|
||||
});
|
||||
|
||||
test("preserves CRLF for single and multi-chunk localized replacements", async () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "single",
|
||||
patch: [
|
||||
"*** Begin Patch",
|
||||
"*** Update File: src/test.c",
|
||||
"@@",
|
||||
" alpha",
|
||||
"-old-a",
|
||||
"+new-a",
|
||||
" middle",
|
||||
"*** End Patch",
|
||||
],
|
||||
expected: "alpha\r\nnew-a\r\nmiddle\r\nold-b\r\nomega\r\n",
|
||||
},
|
||||
{
|
||||
name: "multi",
|
||||
patch: [
|
||||
"*** Begin Patch",
|
||||
"*** Update File: src/test.c",
|
||||
"@@",
|
||||
" alpha",
|
||||
"-old-a",
|
||||
"+new-a",
|
||||
" middle",
|
||||
"@@",
|
||||
"-old-b",
|
||||
"+new-b",
|
||||
" omega",
|
||||
"*** End Patch",
|
||||
],
|
||||
expected: "alpha\r\nnew-a\r\nmiddle\r\nnew-b\r\nomega\r\n",
|
||||
},
|
||||
];
|
||||
|
||||
for (const item of cases) {
|
||||
const files = new Map<string, string>([["src/test.c", "alpha\r\nold-a\r\nmiddle\r\nold-b\r\nomega\r\n"]]);
|
||||
let capturedPlan: ApplyPatchV2BulkReplacementWritePlan | undefined;
|
||||
const fs: ApplyPatchV2FileSystem = {
|
||||
async stat() { throw new Error("stat should not be used"); },
|
||||
async readBlock() { throw new Error("readBlock should not be used"); },
|
||||
async writeFile() { throw new Error("writeFile should not be used"); },
|
||||
async deleteFile() { throw new Error("deleteFile should not be used"); },
|
||||
async readFiles(paths) { return new Map(paths.map((path) => [path, files.get(path) ?? ""])); },
|
||||
async applyReplacementsBulk(paths, plans) {
|
||||
for (const target of paths) {
|
||||
const plan = plans.get(target);
|
||||
if (plan === undefined) throw new Error(`missing plan for ${target}`);
|
||||
capturedPlan = plan;
|
||||
files.set(target, applyReplacementPlan(files.get(target) ?? "", plan));
|
||||
}
|
||||
},
|
||||
};
|
||||
const exitCode = await runApplyPatchV2({
|
||||
executor: { fs },
|
||||
stdin: Readable.from([item.patch.join("\n")]),
|
||||
stdout: new CaptureWritable(),
|
||||
stderr: new CaptureWritable(),
|
||||
});
|
||||
|
||||
expect(exitCode, item.name).toBe(0);
|
||||
expect(files.get("src/test.c"), item.name).toBe(item.expected);
|
||||
expect(files.get("src/test.c")?.match(/(?<!\r)\n/u), item.name).toBeNull();
|
||||
expect(capturedPlan?.newlineStyle, item.name).toBe("crlf");
|
||||
expect(capturedPlan?.encodingStyle, item.name).toBe("utf8");
|
||||
}
|
||||
});
|
||||
|
||||
test("bulk read failure falls back to block reads but still writes through replacement bulk", async () => {
|
||||
const files = new Map<string, string>([
|
||||
["a.md", "first\nold-a\nlast\n"],
|
||||
@@ -244,4 +313,58 @@ describe("apply-patch v2 fs bulk update path", () => {
|
||||
expect(err).toContain("Remote stderr tail:");
|
||||
expect(err).toContain("UNIDESK_SSH_RUNTIME_TIMEOUT timeoutSeconds=60");
|
||||
});
|
||||
|
||||
test("integrity mismatch reports current fingerprint and text format without file content", async () => {
|
||||
let readCount = 0;
|
||||
const fs: ApplyPatchV2FileSystem = {
|
||||
async stat() { throw new Error("stat should not be used"); },
|
||||
async readBlock() { throw new Error("readBlock should not be used"); },
|
||||
async writeFile() { throw new Error("writeFile should not be used"); },
|
||||
async deleteFile() { throw new Error("deleteFile should not be used"); },
|
||||
async readFiles(paths) {
|
||||
readCount += 1;
|
||||
const text = readCount === 1 ? "alpha\r\nold\r\nomega\r\n" : "alpha\r\nchanged-elsewhere\r\nomega\r\n";
|
||||
return new Map(paths.map((path) => [path, text]));
|
||||
},
|
||||
async applyReplacementsBulk() {
|
||||
throw new ApplyPatchV2Error("windows apply-patch fs operation failed: apply-replacements-bulk-stdin", {
|
||||
operation: "apply-replacements-bulk-stdin",
|
||||
route: "D601:win/F/Work/ConStart",
|
||||
targetCount: 1,
|
||||
landed: false,
|
||||
exitCode: 23,
|
||||
stderrTail: "bulk replacement original integrity mismatch for F:\\Work\\ConStart\\src\\test.c",
|
||||
});
|
||||
},
|
||||
};
|
||||
const stdout = new CaptureWritable();
|
||||
const stderr = new CaptureWritable();
|
||||
const exitCode = await runApplyPatchV2({
|
||||
executor: { fs },
|
||||
stdin: Readable.from([[
|
||||
"*** Begin Patch",
|
||||
"*** Update File: src/test.c",
|
||||
"@@",
|
||||
" alpha",
|
||||
"-old",
|
||||
"+new",
|
||||
" omega",
|
||||
"*** End Patch",
|
||||
].join("\n")]),
|
||||
stdout,
|
||||
stderr,
|
||||
timing: { transport: "local", route: "D601:win/F/Work/ConStart" },
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(stdout.text()).toBe("");
|
||||
expect(readCount).toBe(2);
|
||||
const err = stderr.text();
|
||||
expect(err).toContain("Integrity: adapter=windows stage=original file=src/test.c");
|
||||
expect(err).toContain("expectedOriginalSha256=");
|
||||
expect(err).toContain("currentSha256=");
|
||||
expect(err).toContain("expectedNewline=crlf currentNewline=crlf");
|
||||
expect(err).toContain("expectedEncoding=utf8 currentEncoding=utf8");
|
||||
expect(err).not.toContain("changed-elsewhere");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user