






















@@ -1,10 +1,14 @@
11import fs from "node:fs";
22import os from "node:os";
3-import { beforeEach, describe, expect, it, vi } from "vitest";
3+import path from "node:path";
4+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4556const resolvePreferredOpenClawTmpDirMock = vi.hoisted(() => vi.fn(() => "/tmp/openclaw"));
6-const OPENCLAW_TMP_ROOT = "/tmp/openclaw";
7-const TRASH_SOURCE = `${OPENCLAW_TMP_ROOT}/demo`;
7+const realMkdirSync = fs.mkdirSync.bind(fs);
8+const realMkdtempSync = fs.mkdtempSync.bind(fs);
9+const realRmSync = fs.rmSync.bind(fs);
10+const realWriteFileSync = fs.writeFileSync.bind(fs);
11+const realRealpathSyncNative = fs.realpathSync.native.bind(fs.realpathSync);
812913vi.mock("openclaw/plugin-sdk/temp-path", () => ({
1014resolvePreferredOpenClawTmpDir: resolvePreferredOpenClawTmpDirMock,
@@ -15,70 +19,92 @@ function mockTrashContainer(...suffixes: string[]) {
1519return vi.spyOn(fs, "mkdtempSync").mockImplementation((prefix) => {
1620const suffix = suffixes[call] ?? "secure";
1721call += 1;
18-return `${prefix}${suffix}`;
22+const container = `${prefix}${suffix}`;
23+realMkdirSync(container, { recursive: true });
24+return container;
1925});
2026}
21272228describe("browser trash", () => {
29+let testRoot = "";
30+let homeDir = "";
31+let tmpDir = "";
32+2333beforeEach(() => {
2434vi.restoreAllMocks();
35+testRoot = realRealpathSyncNative(realMkdtempSync(path.join(os.tmpdir(), "openclaw-browser-")));
36+homeDir = path.join(testRoot, "home", "test");
37+tmpDir = path.join(testRoot, "tmp");
38+realMkdirSync(path.join(homeDir, ".Trash"), { recursive: true, mode: 0o700 });
39+realMkdirSync(tmpDir, { recursive: true, mode: 0o700 });
2540resolvePreferredOpenClawTmpDirMock.mockReset();
26-resolvePreferredOpenClawTmpDirMock.mockReturnValue("/tmp/openclaw");
41+resolvePreferredOpenClawTmpDirMock.mockReturnValue(tmpDir);
2742vi.spyOn(Date, "now").mockReturnValue(123);
28-vi.spyOn(os, "homedir").mockReturnValue("/home/test");
29-vi.spyOn(os, "tmpdir").mockReturnValue("/tmp");
30-vi.spyOn(fs, "lstatSync").mockReturnValue({
31-isDirectory: () => true,
32-isSymbolicLink: () => false,
33-} as fs.Stats);
34-vi.spyOn(fs.realpathSync, "native").mockImplementation((candidate) => String(candidate));
43+vi.spyOn(os, "homedir").mockReturnValue(homeDir);
44+vi.spyOn(os, "tmpdir").mockReturnValue(tmpDir);
45+vi.spyOn(fs.realpathSync, "native").mockImplementation((candidate) =>
46+realRealpathSyncNative(candidate),
47+);
3548});
364950+afterEach(() => {
51+vi.restoreAllMocks();
52+if (testRoot) {
53+realRmSync(testRoot, { recursive: true, force: true });
54+}
55+});
56+57+function writeTrashTarget(name = "demo"): string {
58+const target = path.join(tmpDir, name);
59+realWriteFileSync(target, "demo");
60+return target;
61+}
62+3763it("moves paths to a reserved user trash container without invoking a PATH-resolved command", async () => {
3864const { movePathToTrash } = await import("./trash.js");
3965const mkdirSync = vi.spyOn(fs, "mkdirSync").mockImplementation(() => undefined);
4066const mkdtempSync = mockTrashContainer("secure");
4167const renameSync = vi.spyOn(fs, "renameSync").mockImplementation(() => undefined);
4268const cpSync = vi.spyOn(fs, "cpSync");
4369const rmSync = vi.spyOn(fs, "rmSync");
70+const target = writeTrashTarget();
71+const expected = path.join(homeDir, ".Trash", "demo-123-secure", "demo");
447245-await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
46-"/home/test/.Trash/demo-123-secure/demo",
47-);
48-expect(mkdirSync).toHaveBeenCalledWith("/home/test/.Trash", {
73+await expect(movePathToTrash(target)).resolves.toBe(expected);
74+expect(mkdirSync).toHaveBeenCalledWith(path.join(homeDir, ".Trash"), {
4975recursive: true,
5076mode: 0o700,
5177});
52-expect(mkdtempSync).toHaveBeenCalledWith("/home/test/.Trash/demo-123-");
53-expect(renameSync).toHaveBeenCalledWith(TRASH_SOURCE, "/home/test/.Trash/demo-123-secure/demo");
78+expect(mkdtempSync).toHaveBeenCalledWith(path.join(homeDir, ".Trash", "demo-123-"));
79+expect(renameSync).toHaveBeenCalledWith(target, expected);
5480expect(cpSync).not.toHaveBeenCalled();
5581expect(rmSync).not.toHaveBeenCalled();
5682});
57835884it("uses the resolved trash directory for reserved destinations", async () => {
5985const { movePathToTrash } = await import("./trash.js");
6086vi.spyOn(fs, "mkdirSync").mockImplementation(() => undefined);
87+const resolvedHomeDir = path.join(testRoot, "real", "home", "test");
88+const resolvedTrashDir = path.join(resolvedHomeDir, ".Trash");
89+realMkdirSync(resolvedTrashDir, { recursive: true, mode: 0o700 });
6190vi.spyOn(fs.realpathSync, "native").mockImplementation((candidate) => {
6291const value = String(candidate);
63-if (value === "/home/test") {
64-return "/real/home/test";
92+if (value === homeDir) {
93+return resolvedHomeDir;
6594}
66-if (value === "/home/test/.Trash") {
67-return "/real/home/test/.Trash";
95+if (value === path.join(homeDir, ".Trash")) {
96+return resolvedTrashDir;
6897}
69-return value;
98+return realRealpathSyncNative(candidate);
7099});
71100const mkdtempSync = mockTrashContainer("secure");
72101const renameSync = vi.spyOn(fs, "renameSync").mockImplementation(() => undefined);
102+const target = writeTrashTarget();
103+const expected = path.join(resolvedTrashDir, "demo-123-secure", "demo");
7310474-await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
75-"/real/home/test/.Trash/demo-123-secure/demo",
76-);
77-expect(mkdtempSync).toHaveBeenCalledWith("/real/home/test/.Trash/demo-123-");
78-expect(renameSync).toHaveBeenCalledWith(
79-TRASH_SOURCE,
80-"/real/home/test/.Trash/demo-123-secure/demo",
81-);
105+await expect(movePathToTrash(target)).resolves.toBe(expected);
106+expect(mkdtempSync).toHaveBeenCalledWith(path.join(resolvedTrashDir, "demo-123-"));
107+expect(renameSync).toHaveBeenCalledWith(target, expected);
82108});
8310984110it("refuses to trash filesystem roots", async () => {
@@ -97,16 +123,13 @@ describe("browser trash", () => {
9712398124it("refuses to use a symlinked trash directory", async () => {
99125const { movePathToTrash } = await import("./trash.js");
126+const realTrashDir = path.join(testRoot, "real-trash");
127+realRmSync(path.join(homeDir, ".Trash"), { recursive: true, force: true });
128+realMkdirSync(realTrashDir, { recursive: true, mode: 0o700 });
129+fs.symlinkSync(realTrashDir, path.join(homeDir, ".Trash"), "dir");
100130vi.spyOn(fs, "mkdirSync").mockImplementation(() => undefined);
101-vi.spyOn(fs, "lstatSync").mockImplementation(
102-(candidate) =>
103-({
104-isDirectory: () => true,
105-isSymbolicLink: () => String(candidate) === "/home/test/.Trash",
106-}) as fs.Stats,
107-);
108131109-await expect(movePathToTrash(TRASH_SOURCE)).rejects.toThrow(
132+await expect(movePathToTrash(writeTrashTarget())).rejects.toThrow(
110133"Refusing to use non-directory/symlink trash directory",
111134);
112135});
@@ -121,16 +144,16 @@ describe("browser trash", () => {
121144});
122145const cpSync = vi.spyOn(fs, "cpSync").mockImplementation(() => undefined);
123146const rmSync = vi.spyOn(fs, "rmSync").mockImplementation(() => undefined);
147+const target = writeTrashTarget();
148+const expected = path.join(homeDir, ".Trash", "demo-123-secure", "demo");
124149125-await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
126-"/home/test/.Trash/demo-123-secure/demo",
127-);
128-expect(cpSync).toHaveBeenCalledWith(TRASH_SOURCE, "/home/test/.Trash/demo-123-secure/demo", {
150+await expect(movePathToTrash(target)).resolves.toBe(expected);
151+expect(cpSync).toHaveBeenCalledWith(target, expected, {
129152recursive: true,
130153force: false,
131154errorOnExist: true,
132155});
133-expect(rmSync).toHaveBeenCalledWith(TRASH_SOURCE, { recursive: true, force: false });
156+expect(rmSync).toHaveBeenCalledWith(target, { recursive: true, force: false });
134157});
135158136159it("retries copy fallback when the copy destination is created concurrently", async () => {
@@ -151,30 +174,21 @@ describe("browser trash", () => {
151174})
152175.mockImplementation(() => undefined);
153176const rmSync = vi.spyOn(fs, "rmSync").mockImplementation(() => undefined);
177+const target = writeTrashTarget();
178+const first = path.join(homeDir, ".Trash", "demo-123-first", "demo");
179+const second = path.join(homeDir, ".Trash", "demo-123-second", "demo");
154180155-await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
156-"/home/test/.Trash/demo-123-second/demo",
157-);
158-expect(cpSync).toHaveBeenNthCalledWith(
159-1,
160-TRASH_SOURCE,
161-"/home/test/.Trash/demo-123-first/demo",
162-{
163-recursive: true,
164-force: false,
165-errorOnExist: true,
166-},
167-);
168-expect(cpSync).toHaveBeenNthCalledWith(
169-2,
170-TRASH_SOURCE,
171-"/home/test/.Trash/demo-123-second/demo",
172-{
173-recursive: true,
174-force: false,
175-errorOnExist: true,
176-},
177-);
181+await expect(movePathToTrash(target)).resolves.toBe(second);
182+expect(cpSync).toHaveBeenNthCalledWith(1, target, first, {
183+recursive: true,
184+force: false,
185+errorOnExist: true,
186+});
187+expect(cpSync).toHaveBeenNthCalledWith(2, target, second, {
188+recursive: true,
189+force: false,
190+errorOnExist: true,
191+});
178192expect(rmSync).toHaveBeenCalledTimes(1);
179193expect(Date.now).toHaveBeenCalledTimes(1);
180194});
@@ -190,20 +204,13 @@ describe("browser trash", () => {
190204throw collision;
191205})
192206.mockImplementation(() => undefined);
207+const target = writeTrashTarget();
208+const first = path.join(homeDir, ".Trash", "demo-123-first", "demo");
209+const second = path.join(homeDir, ".Trash", "demo-123-second", "demo");
193210194-await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
195-"/home/test/.Trash/demo-123-second/demo",
196-);
197-expect(renameSync).toHaveBeenNthCalledWith(
198-1,
199-TRASH_SOURCE,
200-"/home/test/.Trash/demo-123-first/demo",
201-);
202-expect(renameSync).toHaveBeenNthCalledWith(
203-2,
204-TRASH_SOURCE,
205-"/home/test/.Trash/demo-123-second/demo",
206-);
211+await expect(movePathToTrash(target)).resolves.toBe(second);
212+expect(renameSync).toHaveBeenNthCalledWith(1, target, first);
213+expect(renameSync).toHaveBeenNthCalledWith(2, target, second);
207214expect(Date.now).toHaveBeenCalledTimes(1);
208215});
209216});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。