mirror of https://github.com/openclaw/openclaw.git
refactor: share temp dir test helper
This commit is contained in:
parent
b5349f7563
commit
88b87d893d
|
|
@ -1,22 +1,13 @@
|
|||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { withTempDir } from "../../test-helpers/temp-dir.js";
|
||||
import {
|
||||
buildPinnedWritePlan,
|
||||
SANDBOX_PINNED_MUTATION_PYTHON,
|
||||
} from "./fs-bridge-mutation-helper.js";
|
||||
|
||||
async function withTempRoot<T>(prefix: string, run: (root: string) => Promise<T>): Promise<T> {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
try {
|
||||
return await run(root);
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function runMutation(args: string[], input?: string) {
|
||||
return spawnSync("python3", ["-c", SANDBOX_PINNED_MUTATION_PYTHON, ...args], {
|
||||
input,
|
||||
|
|
@ -56,7 +47,7 @@ function runWritePlan(args: string[], input?: string) {
|
|||
|
||||
describe("sandbox pinned mutation helper", () => {
|
||||
it("writes through a pinned directory fd", async () => {
|
||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||
const workspace = path.join(root, "workspace");
|
||||
await fs.mkdir(workspace, { recursive: true });
|
||||
|
||||
|
|
@ -72,7 +63,7 @@ describe("sandbox pinned mutation helper", () => {
|
|||
it.runIf(process.platform !== "win32")(
|
||||
"preserves stdin payload bytes when the pinned write plan runs through sh",
|
||||
async () => {
|
||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||
const workspace = path.join(root, "workspace");
|
||||
await fs.mkdir(workspace, { recursive: true });
|
||||
|
||||
|
|
@ -92,7 +83,7 @@ describe("sandbox pinned mutation helper", () => {
|
|||
it.runIf(process.platform !== "win32")(
|
||||
"rejects symlink-parent writes instead of materializing a temp file outside the mount",
|
||||
async () => {
|
||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||
const workspace = path.join(root, "workspace");
|
||||
const outside = path.join(root, "outside");
|
||||
await fs.mkdir(workspace, { recursive: true });
|
||||
|
|
@ -108,7 +99,7 @@ describe("sandbox pinned mutation helper", () => {
|
|||
);
|
||||
|
||||
it.runIf(process.platform !== "win32")("rejects symlink segments during mkdirp", async () => {
|
||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||
const workspace = path.join(root, "workspace");
|
||||
const outside = path.join(root, "outside");
|
||||
await fs.mkdir(workspace, { recursive: true });
|
||||
|
|
@ -123,7 +114,7 @@ describe("sandbox pinned mutation helper", () => {
|
|||
});
|
||||
|
||||
it.runIf(process.platform !== "win32")("remove unlinks the symlink itself", async () => {
|
||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||
const workspace = path.join(root, "workspace");
|
||||
const outside = path.join(root, "outside");
|
||||
await fs.mkdir(workspace, { recursive: true });
|
||||
|
|
@ -144,7 +135,7 @@ describe("sandbox pinned mutation helper", () => {
|
|||
it.runIf(process.platform !== "win32")(
|
||||
"rejects symlink destination parents during rename",
|
||||
async () => {
|
||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||
const workspace = path.join(root, "workspace");
|
||||
const outside = path.join(root, "outside");
|
||||
await fs.mkdir(workspace, { recursive: true });
|
||||
|
|
@ -175,7 +166,7 @@ describe("sandbox pinned mutation helper", () => {
|
|||
it.runIf(process.platform !== "win32")(
|
||||
"copies directories across different mount roots during rename fallback",
|
||||
async () => {
|
||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||
const sourceRoot = path.join(root, "source");
|
||||
const destRoot = path.join(root, "dest");
|
||||
await fs.mkdir(path.join(sourceRoot, "dir", "nested"), { recursive: true });
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import {
|
||||
resolveDefaultConfigCandidates,
|
||||
resolveConfigPathCandidate,
|
||||
|
|
@ -37,15 +37,6 @@ describe("oauth paths", () => {
|
|||
});
|
||||
|
||||
describe("state + config path candidates", () => {
|
||||
async function withTempRoot(prefix: string, run: (root: string) => Promise<void>): Promise<void> {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
try {
|
||||
await run(root);
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function expectOpenClawHomeDefaults(env: NodeJS.ProcessEnv): void {
|
||||
const configuredHome = env.OPENCLAW_HOME;
|
||||
if (!configuredHome) {
|
||||
|
|
@ -107,7 +98,7 @@ describe("state + config path candidates", () => {
|
|||
});
|
||||
|
||||
it("prefers ~/.openclaw when it exists and legacy dir is missing", async () => {
|
||||
await withTempRoot("openclaw-state-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-state-" }, async (root) => {
|
||||
const newDir = path.join(root, ".openclaw");
|
||||
await fs.mkdir(newDir, { recursive: true });
|
||||
const resolved = resolveStateDir({} as NodeJS.ProcessEnv, () => root);
|
||||
|
|
@ -116,7 +107,7 @@ describe("state + config path candidates", () => {
|
|||
});
|
||||
|
||||
it("falls back to existing legacy state dir when ~/.openclaw is missing", async () => {
|
||||
await withTempRoot("openclaw-state-legacy-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-state-legacy-" }, async (root) => {
|
||||
const legacyDir = path.join(root, ".clawdbot");
|
||||
await fs.mkdir(legacyDir, { recursive: true });
|
||||
const resolved = resolveStateDir({} as NodeJS.ProcessEnv, () => root);
|
||||
|
|
@ -125,7 +116,7 @@ describe("state + config path candidates", () => {
|
|||
});
|
||||
|
||||
it("CONFIG_PATH prefers existing config when present", async () => {
|
||||
await withTempRoot("openclaw-config-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-config-" }, async (root) => {
|
||||
const legacyDir = path.join(root, ".openclaw");
|
||||
await fs.mkdir(legacyDir, { recursive: true });
|
||||
const legacyPath = path.join(legacyDir, "openclaw.json");
|
||||
|
|
@ -137,7 +128,7 @@ describe("state + config path candidates", () => {
|
|||
});
|
||||
|
||||
it("respects state dir overrides when config is missing", async () => {
|
||||
await withTempRoot("openclaw-config-override-", async (root) => {
|
||||
await withTempDir({ prefix: "openclaw-config-override-" }, async (root) => {
|
||||
const legacyDir = path.join(root, ".openclaw");
|
||||
await fs.mkdir(legacyDir, { recursive: true });
|
||||
const legacyConfig = path.join(legacyDir, "openclaw.json");
|
||||
|
|
|
|||
|
|
@ -1,19 +1,10 @@
|
|||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import { resolveBoundaryPath, resolveBoundaryPathSync } from "./boundary-path.js";
|
||||
import { isPathInside } from "./path-guards.js";
|
||||
|
||||
async function withTempRoot<T>(prefix: string, run: (root: string) => Promise<T>): Promise<T> {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
try {
|
||||
return await run(root);
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function createSeededRandom(seed: number): () => number {
|
||||
let state = seed >>> 0;
|
||||
return () => {
|
||||
|
|
@ -28,7 +19,7 @@ describe("resolveBoundaryPath", () => {
|
|||
return;
|
||||
}
|
||||
|
||||
await withTempRoot("openclaw-boundary-path-", async (base) => {
|
||||
await withTempDir({ prefix: "openclaw-boundary-path-" }, async (base) => {
|
||||
const root = path.join(base, "workspace");
|
||||
const targetDir = path.join(root, "target-dir");
|
||||
const linkPath = path.join(root, "alias");
|
||||
|
|
@ -55,7 +46,7 @@ describe("resolveBoundaryPath", () => {
|
|||
return;
|
||||
}
|
||||
|
||||
await withTempRoot("openclaw-boundary-path-", async (base) => {
|
||||
await withTempDir({ prefix: "openclaw-boundary-path-" }, async (base) => {
|
||||
const root = path.join(base, "workspace");
|
||||
const outside = path.join(base, "outside");
|
||||
const linkPath = path.join(root, "alias-out");
|
||||
|
|
@ -86,7 +77,7 @@ describe("resolveBoundaryPath", () => {
|
|||
return;
|
||||
}
|
||||
|
||||
await withTempRoot("openclaw-boundary-path-", async (base) => {
|
||||
await withTempDir({ prefix: "openclaw-boundary-path-" }, async (base) => {
|
||||
const root = path.join(base, "workspace");
|
||||
const outside = path.join(base, "outside");
|
||||
const outsideFile = path.join(outside, "target.txt");
|
||||
|
|
@ -122,7 +113,7 @@ describe("resolveBoundaryPath", () => {
|
|||
return;
|
||||
}
|
||||
|
||||
await withTempRoot("openclaw-boundary-path-", async (base) => {
|
||||
await withTempDir({ prefix: "openclaw-boundary-path-" }, async (base) => {
|
||||
const root = path.join(base, "workspace");
|
||||
const aliasRoot = path.join(base, "workspace-alias");
|
||||
const fileName = "plugin.js";
|
||||
|
|
@ -153,7 +144,7 @@ describe("resolveBoundaryPath", () => {
|
|||
return;
|
||||
}
|
||||
|
||||
await withTempRoot("openclaw-boundary-path-fuzz-", async (base) => {
|
||||
await withTempDir({ prefix: "openclaw-boundary-path-fuzz-" }, async (base) => {
|
||||
const root = path.join(base, "workspace");
|
||||
const outside = path.join(base, "outside");
|
||||
const safeTarget = path.join(root, "safe-target");
|
||||
|
|
|
|||
|
|
@ -1,76 +1,75 @@
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import { assertNoPathAliasEscape } from "./path-alias-guards.js";
|
||||
|
||||
async function withTempRoot<T>(run: (root: string) => Promise<T>): Promise<T> {
|
||||
const base = await fs.mkdtemp(path.join(process.cwd(), "openclaw-path-alias-"));
|
||||
const root = path.join(base, "root");
|
||||
await fs.mkdir(root, { recursive: true });
|
||||
try {
|
||||
return await run(root);
|
||||
} finally {
|
||||
await fs.rm(base, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe("assertNoPathAliasEscape", () => {
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"rejects broken final symlink targets outside root",
|
||||
async () => {
|
||||
await withTempRoot(async (root) => {
|
||||
const outside = path.join(path.dirname(root), "outside");
|
||||
await fs.mkdir(outside, { recursive: true });
|
||||
const linkPath = path.join(root, "jump");
|
||||
await fs.symlink(path.join(outside, "owned.txt"), linkPath);
|
||||
await withTempDir(
|
||||
{ prefix: "openclaw-path-alias-", parentDir: process.cwd(), subdir: "root" },
|
||||
async (root) => {
|
||||
const outside = path.join(path.dirname(root), "outside");
|
||||
await fs.mkdir(outside, { recursive: true });
|
||||
const linkPath = path.join(root, "jump");
|
||||
await fs.symlink(path.join(outside, "owned.txt"), linkPath);
|
||||
|
||||
await expect(
|
||||
assertNoPathAliasEscape({
|
||||
absolutePath: linkPath,
|
||||
rootPath: root,
|
||||
boundaryLabel: "sandbox root",
|
||||
}),
|
||||
).rejects.toThrow(/Symlink escapes sandbox root/);
|
||||
});
|
||||
await expect(
|
||||
assertNoPathAliasEscape({
|
||||
absolutePath: linkPath,
|
||||
rootPath: root,
|
||||
boundaryLabel: "sandbox root",
|
||||
}),
|
||||
).rejects.toThrow(/Symlink escapes sandbox root/);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"allows broken final symlink targets that remain inside root",
|
||||
async () => {
|
||||
await withTempRoot(async (root) => {
|
||||
const linkPath = path.join(root, "jump");
|
||||
await fs.symlink(path.join(root, "missing", "owned.txt"), linkPath);
|
||||
await withTempDir(
|
||||
{ prefix: "openclaw-path-alias-", parentDir: process.cwd(), subdir: "root" },
|
||||
async (root) => {
|
||||
const linkPath = path.join(root, "jump");
|
||||
await fs.symlink(path.join(root, "missing", "owned.txt"), linkPath);
|
||||
|
||||
await expect(
|
||||
assertNoPathAliasEscape({
|
||||
absolutePath: linkPath,
|
||||
rootPath: root,
|
||||
boundaryLabel: "sandbox root",
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
await expect(
|
||||
assertNoPathAliasEscape({
|
||||
absolutePath: linkPath,
|
||||
rootPath: root,
|
||||
boundaryLabel: "sandbox root",
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"rejects broken targets that traverse via an in-root symlink alias",
|
||||
async () => {
|
||||
await withTempRoot(async (root) => {
|
||||
const outside = path.join(path.dirname(root), "outside");
|
||||
await fs.mkdir(outside, { recursive: true });
|
||||
await fs.symlink(outside, path.join(root, "hop"));
|
||||
const linkPath = path.join(root, "jump");
|
||||
await fs.symlink(path.join("hop", "missing", "owned.txt"), linkPath);
|
||||
await withTempDir(
|
||||
{ prefix: "openclaw-path-alias-", parentDir: process.cwd(), subdir: "root" },
|
||||
async (root) => {
|
||||
const outside = path.join(path.dirname(root), "outside");
|
||||
await fs.mkdir(outside, { recursive: true });
|
||||
await fs.symlink(outside, path.join(root, "hop"));
|
||||
const linkPath = path.join(root, "jump");
|
||||
await fs.symlink(path.join("hop", "missing", "owned.txt"), linkPath);
|
||||
|
||||
await expect(
|
||||
assertNoPathAliasEscape({
|
||||
absolutePath: linkPath,
|
||||
rootPath: root,
|
||||
boundaryLabel: "sandbox root",
|
||||
}),
|
||||
).rejects.toThrow(/Symlink escapes sandbox root/);
|
||||
});
|
||||
await expect(
|
||||
assertNoPathAliasEscape({
|
||||
absolutePath: linkPath,
|
||||
rootPath: root,
|
||||
boundaryLabel: "sandbox root",
|
||||
}),
|
||||
).rejects.toThrow(/Symlink escapes sandbox root/);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
export async function withTempDir<T>(
|
||||
options: {
|
||||
prefix: string;
|
||||
parentDir?: string;
|
||||
subdir?: string;
|
||||
},
|
||||
run: (dir: string) => Promise<T>,
|
||||
): Promise<T> {
|
||||
const base = await fs.mkdtemp(path.join(options.parentDir ?? os.tmpdir(), options.prefix));
|
||||
const dir = options.subdir ? path.join(base, options.subdir) : base;
|
||||
if (options.subdir) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
try {
|
||||
return await run(dir);
|
||||
} finally {
|
||||
await fs.rm(base, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue