From cfadb0a3561c8e579d428485a0a45d4ced8dcc15 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 13:27:15 +0100 Subject: [PATCH] test: clear fs-safe broad matchers --- src/infra/fs-safe.test.ts | 100 ++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 58 deletions(-) diff --git a/src/infra/fs-safe.test.ts b/src/infra/fs-safe.test.ts index 62c8c9da0d2..c9e5c9935a0 100644 --- a/src/infra/fs-safe.test.ts +++ b/src/infra/fs-safe.test.ts @@ -24,6 +24,17 @@ afterEach(async () => { await tempDirs.cleanup(); }); +async function expectRejectCode(promise: Promise, 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/, + ); }); });