From 59189750e4a31a7987cb93271c13c7dcba156be6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 21 Feb 2026 19:51:19 +0000 Subject: [PATCH] test(browser): dedupe path fixture calls and cover root resolvers --- src/browser/paths.test.ts | 100 ++++++++++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 19 deletions(-) diff --git a/src/browser/paths.test.ts b/src/browser/paths.test.ts index 03f88a8a1c0..1178753ff92 100644 --- a/src/browser/paths.test.ts +++ b/src/browser/paths.test.ts @@ -2,7 +2,11 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { resolveExistingPathsWithinRoot } from "./paths.js"; +import { + resolveExistingPathsWithinRoot, + resolvePathsWithinRoot, + resolvePathWithinRoot, +} from "./paths.js"; async function createFixtureRoot(): Promise<{ baseDir: string; uploadsDir: string }> { const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-browser-paths-")); @@ -33,6 +37,17 @@ describe("resolveExistingPathsWithinRoot", () => { } } + function resolveWithinUploads(params: { + uploadsDir: string; + requestedPaths: string[]; + }): Promise>> { + return resolveExistingPathsWithinRoot({ + rootDir: params.uploadsDir, + requestedPaths: params.requestedPaths, + scopeLabel: "uploads directory", + }); + } + it("accepts existing files under the upload root", async () => { await withFixtureRoot(async ({ uploadsDir }) => { const nestedDir = path.join(uploadsDir, "nested"); @@ -40,10 +55,9 @@ describe("resolveExistingPathsWithinRoot", () => { const filePath = path.join(nestedDir, "ok.txt"); await fs.writeFile(filePath, "ok", "utf8"); - const result = await resolveExistingPathsWithinRoot({ - rootDir: uploadsDir, + const result = await resolveWithinUploads({ + uploadsDir, requestedPaths: [filePath], - scopeLabel: "uploads directory", }); expect(result.ok).toBe(true); @@ -58,10 +72,9 @@ describe("resolveExistingPathsWithinRoot", () => { const outsidePath = path.join(baseDir, "outside.txt"); await fs.writeFile(outsidePath, "nope", "utf8"); - const result = await resolveExistingPathsWithinRoot({ - rootDir: uploadsDir, + const result = await resolveWithinUploads({ + uploadsDir, requestedPaths: ["../outside.txt"], - scopeLabel: "uploads directory", }); expectInvalidResult(result, "must stay within uploads directory"); @@ -70,10 +83,9 @@ describe("resolveExistingPathsWithinRoot", () => { it("rejects blank paths", async () => { await withFixtureRoot(async ({ uploadsDir }) => { - const result = await resolveExistingPathsWithinRoot({ - rootDir: uploadsDir, + const result = await resolveWithinUploads({ + uploadsDir, requestedPaths: [" "], - scopeLabel: "uploads directory", }); expectInvalidResult(result, "path is required"); @@ -82,10 +94,9 @@ describe("resolveExistingPathsWithinRoot", () => { it("keeps lexical in-root paths when files do not exist yet", async () => { await withFixtureRoot(async ({ uploadsDir }) => { - const result = await resolveExistingPathsWithinRoot({ - rootDir: uploadsDir, + const result = await resolveWithinUploads({ + uploadsDir, requestedPaths: ["missing.txt"], - scopeLabel: "uploads directory", }); expect(result.ok).toBe(true); @@ -100,10 +111,9 @@ describe("resolveExistingPathsWithinRoot", () => { const nestedDir = path.join(uploadsDir, "nested"); await fs.mkdir(nestedDir, { recursive: true }); - const result = await resolveExistingPathsWithinRoot({ - rootDir: uploadsDir, + const result = await resolveWithinUploads({ + uploadsDir, requestedPaths: ["nested"], - scopeLabel: "uploads directory", }); expectInvalidResult(result, "regular non-symlink file"); @@ -119,10 +129,9 @@ describe("resolveExistingPathsWithinRoot", () => { const symlinkPath = path.join(uploadsDir, "leak.txt"); await fs.symlink(outsidePath, symlinkPath); - const result = await resolveExistingPathsWithinRoot({ - rootDir: uploadsDir, + const result = await resolveWithinUploads({ + uploadsDir, requestedPaths: ["leak.txt"], - scopeLabel: "uploads directory", }); expectInvalidResult(result, "regular non-symlink file"); @@ -130,3 +139,56 @@ describe("resolveExistingPathsWithinRoot", () => { }, ); }); + +describe("resolvePathWithinRoot", () => { + it("uses default file name when requested path is blank", () => { + const result = resolvePathWithinRoot({ + rootDir: "/tmp/uploads", + requestedPath: " ", + scopeLabel: "uploads directory", + defaultFileName: "fallback.txt", + }); + expect(result).toEqual({ + ok: true, + path: path.resolve("/tmp/uploads", "fallback.txt"), + }); + }); + + it("rejects root-level path aliases that do not point to a file", () => { + const result = resolvePathWithinRoot({ + rootDir: "/tmp/uploads", + requestedPath: ".", + scopeLabel: "uploads directory", + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain("must stay within uploads directory"); + } + }); +}); + +describe("resolvePathsWithinRoot", () => { + it("resolves all valid in-root paths", () => { + const result = resolvePathsWithinRoot({ + rootDir: "/tmp/uploads", + requestedPaths: ["a.txt", "nested/b.txt"], + scopeLabel: "uploads directory", + }); + expect(result).toEqual({ + ok: true, + paths: [path.resolve("/tmp/uploads", "a.txt"), path.resolve("/tmp/uploads", "nested/b.txt")], + }); + }); + + it("returns the first path validation error", () => { + const result = resolvePathsWithinRoot({ + rootDir: "/tmp/uploads", + requestedPaths: ["a.txt", "../outside.txt", "b.txt"], + scopeLabel: "uploads directory", + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain("must stay within uploads directory"); + } + }); +});