test: clear fs-safe broad matchers

This commit is contained in:
Peter Steinberger
2026-05-10 13:27:15 +01:00
parent c58cf5b014
commit cfadb0a356

View File

@@ -24,6 +24,17 @@ afterEach(async () => {
await tempDirs.cleanup();
});
async function expectRejectCode(promise: Promise<unknown>, expected: string | RegExp) {
const err = await promise.catch((caught: unknown) => caught);
expect(err).toBeDefined();
const code = (err as NodeJS.ErrnoException).code;
if (typeof expected === "string") {
expect(code).toBe(expected);
} else {
expect(code).toMatch(expected);
}
}
async function runWriteOpenRace(params: {
slotPath: string;
outsideDir: string;
@@ -38,11 +49,9 @@ async function runWriteOpenRace(params: {
try {
await params.runWrite();
} catch (err) {
expect(err).toMatchObject({
code: expect.stringMatching(
/outside-workspace|path-mismatch|path-alias|invalid-path|not-file/,
),
});
expect((err as NodeJS.ErrnoException).code).toMatch(
/outside-workspace|path-mismatch|path-alias|invalid-path|not-file/,
);
}
},
});
@@ -121,9 +130,7 @@ describe("fs-safe", () => {
it("rejects directories", async () => {
const dir = await tempDirs.make("openclaw-fs-safe-");
await expect(readLocalFileSafely({ filePath: dir })).rejects.toMatchObject({
code: "not-file",
});
await expectRejectCode(readLocalFileSafely({ filePath: dir }), "not-file");
const err = await readLocalFileSafely({ filePath: dir }).catch((e: unknown) => e);
expect(err).toBeInstanceOf(FsSafeError);
expect((err as FsSafeError).message).not.toMatch(/EISDIR/i);
@@ -149,9 +156,7 @@ describe("fs-safe", () => {
const file = path.join(dir, "big.bin");
await fs.writeFile(file, Buffer.alloc(8));
await expect(readLocalFileSafely({ filePath: file, maxBytes: 4 })).rejects.toMatchObject({
code: "too-large",
});
await expectRejectCode(readLocalFileSafely({ filePath: file, maxBytes: 4 }), "too-large");
});
it.runIf(process.platform !== "win32")("rejects symlinks", async () => {
@@ -161,9 +166,7 @@ describe("fs-safe", () => {
await fs.writeFile(target, "target");
await fs.symlink(target, link);
await expect(readLocalFileSafely({ filePath: link })).rejects.toMatchObject({
code: "symlink",
});
await expectRejectCode(readLocalFileSafely({ filePath: link }), "symlink");
});
it.runIf(process.platform !== "win32")(
@@ -200,7 +203,7 @@ describe("fs-safe", () => {
await expect(
(await openRoot(root)).open(path.join("..", path.basename(outside), "outside.txt")),
).rejects.toMatchObject({ code: "outside-workspace" });
).rejects.toSatisfy((err: NodeJS.ErrnoException) => err.code === "outside-workspace");
});
it("rejects directory path within root without leaking EISDIR (issue #31186)", async () => {
@@ -208,9 +211,7 @@ describe("fs-safe", () => {
await fs.mkdir(path.join(root, "memory"), { recursive: true });
const rootFs = await openRoot(root);
await expect(rootFs.open("memory")).rejects.toMatchObject({
code: expect.stringMatching(/invalid-path|not-file/),
});
await expectRejectCode(rootFs.open("memory"), /invalid-path|not-file/);
const err = await rootFs.open("memory").catch((e: unknown) => e);
expect(err).toBeInstanceOf(FsSafeError);
@@ -246,9 +247,7 @@ describe("fs-safe", () => {
await fs.writeFile(target, "outside");
await fs.symlink(target, link);
await expect((await openRoot(root)).open("link.txt")).rejects.toMatchObject({
code: "symlink",
});
await expectRejectCode((await openRoot(root)).open("link.txt"), "symlink");
});
it.runIf(process.platform !== "win32")(
@@ -273,7 +272,7 @@ describe("fs-safe", () => {
(await openRoot(root)).read("link.txt", {
symlinks: "follow-within-root",
}),
).rejects.toMatchObject({ code: "path-mismatch" });
).rejects.toSatisfy((err: NodeJS.ErrnoException) => err.code === "path-mismatch");
},
);
@@ -294,9 +293,7 @@ describe("fs-safe", () => {
if (openedHandle === undefined) {
throw new Error("expected opened file handle");
}
await expect(openedHandle.readFile({ encoding: "utf8" })).rejects.toMatchObject({
code: "EBADF",
});
await expectRejectCode(openedHandle.readFile({ encoding: "utf8" }), "EBADF");
});
it("rejects setting fs-safe test hooks outside test mode", () => {
@@ -316,9 +313,7 @@ describe("fs-safe", () => {
await withOutsideHardlinkAlias({
aliasPath: hardlinkPath,
run: async () => {
await expect((await openRoot(root)).open("link.txt")).rejects.toMatchObject({
code: "hardlink",
});
await expectRejectCode((await openRoot(root)).open("link.txt"), "hardlink");
},
});
});
@@ -365,7 +360,7 @@ describe("fs-safe", () => {
await (await openRoot(root)).remove("nested/out.txt");
await expect(fs.stat(targetPath)).rejects.toMatchObject({ code: "ENOENT" });
await expectRejectCode(fs.stat(targetPath), "ENOENT");
});
it("creates directories within root safely", async () => {
@@ -405,9 +400,7 @@ describe("fs-safe", () => {
await (await openRoot(root)).remove(path.join("alias", "target.txt"));
await expect(fs.stat(path.join(realDir, "target.txt"))).rejects.toMatchObject({
code: "ENOENT",
});
await expectRejectCode(fs.stat(path.join(realDir, "target.txt")), "ENOENT");
},
);
@@ -421,10 +414,8 @@ describe("fs-safe", () => {
(await openRoot(root)).copyIn("nested/big.bin", sourcePath, {
maxBytes: 4,
}),
).rejects.toMatchObject({ code: "too-large" });
await expect(fs.stat(path.join(root, "nested", "big.bin"))).rejects.toMatchObject({
code: "ENOENT",
});
).rejects.toSatisfy((err: NodeJS.ErrnoException) => err.code === "too-large");
await expectRejectCode(fs.stat(path.join(root, "nested", "big.bin")), "ENOENT");
});
it("writes a file within root from another local source path safely", async () => {
@@ -439,9 +430,7 @@ describe("fs-safe", () => {
});
it("rejects write traversal outside root", async () => {
const root = await tempDirs.make("openclaw-fs-safe-root-");
await expect((await openRoot(root)).write("../escape.txt", "x")).rejects.toMatchObject({
code: "outside-workspace",
});
await expectRejectCode((await openRoot(root)).write("../escape.txt", "x"), "outside-workspace");
});
it.runIf(process.platform !== "win32")("rejects writing through hardlink aliases", async () => {
@@ -450,9 +439,7 @@ describe("fs-safe", () => {
await withOutsideHardlinkAlias({
aliasPath: hardlinkPath,
run: async (outsideFile) => {
await expect((await openRoot(root)).write("alias.txt", "pwned")).rejects.toMatchObject({
code: "path-alias",
});
await expectRejectCode((await openRoot(root)).write("alias.txt", "pwned"), "path-alias");
await expect(fs.readFile(outsideFile, "utf8")).resolves.toBe("outside");
},
});
@@ -468,7 +455,7 @@ describe("fs-safe", () => {
(await openRoot(root)).append("alias.txt", "pwned", {
prependNewlineIfNeeded: true,
}),
).rejects.toMatchObject({ code: "path-alias" });
).rejects.toSatisfy((err: NodeJS.ErrnoException) => err.code === "path-alias");
await expect(fs.readFile(outsideFile, "utf8")).resolves.toBe("outside");
},
});
@@ -526,11 +513,10 @@ describe("fs-safe", () => {
symlinkTarget: outside,
timing: "before-realpath",
run: async () => {
await expect(
await expectRejectCode(
(await openRoot(root)).remove(path.join("slot", "target.txt")),
).rejects.toMatchObject({
code: expect.stringMatching(/path-alias|not-found/),
});
/path-alias|not-found/,
);
},
});
@@ -557,15 +543,14 @@ describe("fs-safe", () => {
symlinkTarget: outside,
timing: "before-realpath",
run: async () => {
await expect(
await expectRejectCode(
(await openRoot(root)).mkdir(path.join("slot", "nested", "deep")),
).rejects.toMatchObject({
code: "path-alias",
});
"path-alias",
);
},
});
await expect(fs.stat(path.join(outside, "nested"))).rejects.toMatchObject({ code: "ENOENT" });
await expectRejectCode(fs.stat(path.join(outside, "nested")), "ENOENT");
},
);
@@ -594,9 +579,7 @@ describe("fs-safe", () => {
const missing = path.join(dir, "missing.txt");
await expect(readLocalFileSafely({ filePath: missing })).rejects.toBeInstanceOf(FsSafeError);
await expect(readLocalFileSafely({ filePath: missing })).rejects.toMatchObject({
code: "not-found",
});
await expectRejectCode(readLocalFileSafely({ filePath: missing }), "not-found");
});
});
@@ -637,8 +620,9 @@ describe("tilde expansion in file tools", () => {
}
const outsideRoot = await tempDirs.make("openclaw-tilde-outside-");
await expect((await openRoot(outsideRoot)).open("~/escape.txt")).rejects.toMatchObject({
code: expect.stringMatching(/outside-workspace|not-found|invalid-path/),
});
await expectRejectCode(
(await openRoot(outsideRoot)).open("~/escape.txt"),
/outside-workspace|not-found|invalid-path/,
);
});
});