




















@@ -12,6 +12,7 @@ import { join } from "node:path";
1212import { afterEach, beforeEach, describe, expect, it } from "vitest";
1313import {
1414type OutputRuntimeEnv,
15+formatUnifiedDiff,
1516pathEmitCommand,
1617pathFindCommand,
1718pathResolveCommand,
@@ -152,6 +153,92 @@ describe("openclaw path CLI", () => {
152153expect(readFileSync(filePath, "utf-8")).toBe(before);
153154});
154155156+it("CLI-S05 --dry-run --diff prints a unified diff", async () => {
157+const filePath = join(workspaceDir, "gateway.jsonc");
158+const before = '{\n "version": "1.0",\n "enabled": true\n}\n';
159+writeFileSync(filePath, before, "utf-8");
160+const rt = createTestRuntime();
161+await pathSetCommand(
162+"oc://gateway.jsonc/version",
163+"2.0",
164+{ cwd: workspaceDir, human: true, dryRun: true, diff: true },
165+rt,
166+);
167+expect(rt.exitCode).toBe(0);
168+const out = stdoutText(rt);
169+expect(out).toContain("--- ");
170+expect(out).toContain("+++ ");
171+expect(out).toContain('- "version": "1.0",');
172+expect(out).toContain('+ "version": "2.0",');
173+expect(readFileSync(filePath, "utf-8")).toBe(before);
174+});
175+176+it("CLI-S05b --dry-run --diff shows final newline-only byte changes", () => {
177+const out = formatUnifiedDiff(
178+"## Boundaries\n\n- timeout: 5\n",
179+"## Boundaries\n\n- timeout: 5",
180+"AGENTS.md",
181+);
182+expect(out).toContain("--- AGENTS.md");
183+expect(out).toContain("@@ -1,4 +1,3 @@");
184+expect(out).toContain("\n-\n");
185+});
186+187+it("CLI-S05c --dry-run --diff shows line-ending-only byte changes", async () => {
188+const filePath = join(workspaceDir, "AGENTS.md");
189+const before = "---\r\nname: x\r\n---\r\n";
190+writeFileSync(filePath, before, "utf-8");
191+const rt = createTestRuntime();
192+await pathSetCommand(
193+"oc://AGENTS.md/[frontmatter]/name",
194+"x",
195+{ cwd: workspaceDir, json: true, dryRun: true, diff: true },
196+rt,
197+);
198+expect(rt.exitCode).toBe(0);
199+const out = JSON.parse(stdoutText(rt));
200+expect(out.diff).toContain("-name: x\r");
201+expect(out.diff).toContain("+name: x");
202+expect(readFileSync(filePath, "utf-8")).toBe(before);
203+});
204+205+it("CLI-S06 --dry-run --diff includes diff in JSON output", async () => {
206+const filePath = join(workspaceDir, "gateway.jsonc");
207+writeFileSync(filePath, '{ "version": "1.0" }', "utf-8");
208+const rt = createTestRuntime();
209+await pathSetCommand(
210+"oc://gateway.jsonc/version",
211+"2.0",
212+{ cwd: workspaceDir, json: true, dryRun: true, diff: true },
213+rt,
214+);
215+expect(rt.exitCode).toBe(0);
216+const out = JSON.parse(stdoutText(rt));
217+expect(out.dryRun).toBe(true);
218+expect(out.bytes).toContain('"2.0"');
219+expect(out.diff).toContain('-{ "version": "1.0" }');
220+expect(out.diff).toContain('+{ "version": "2.0" }');
221+});
222+223+it("CLI-S07 rejects --diff without --dry-run", async () => {
224+const filePath = join(workspaceDir, "gateway.jsonc");
225+const before = '{ "version": "1.0" }';
226+writeFileSync(filePath, before, "utf-8");
227+const rt = createTestRuntime();
228+await pathSetCommand(
229+"oc://gateway.jsonc/version",
230+"2.0",
231+{ cwd: workspaceDir, json: true, diff: true },
232+rt,
233+);
234+expect(rt.exitCode).toBe(1);
235+expect(JSON.parse(stdoutText(rt))).toMatchObject({
236+ok: false,
237+reason: "--diff requires --dry-run",
238+});
239+expect(readFileSync(filePath, "utf-8")).toBe(before);
240+});
241+155242it("CLI-S03 sentinel-bearing value is refused at emit", async () => {
156243const filePath = join(workspaceDir, "gateway.jsonc");
157244writeFileSync(filePath, '{ "token": "x" }', "utf-8");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。