11// Tests safe filesystem wrappers and protected file-handle behavior.
22import type { FileHandle } from "node:fs/promises" ;
33import fs from "node:fs/promises" ;
4- import fsSync from "node:fs" ;
5- import os from "node:os" ;
64import path from "node:path" ;
75import { __setFsSafeTestHooksForTest } from "@openclaw/fs-safe/test-hooks" ;
8-
9- const canCreateHardlinks = ( ( ) => {
10- const probeDir = fsSync . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-fs-hardlink-probe-" ) ) ;
11- const targetFile = path . join ( probeDir , "target.txt" ) ;
12- const linkFile = path . join ( probeDir , "link.txt" ) ;
13- try {
14- fsSync . writeFileSync ( targetFile , "target" , "utf8" ) ;
15- fsSync . linkSync ( targetFile , linkFile ) ;
16- return true ;
17- } catch {
18- return false ;
19- } finally {
20- fsSync . rmSync ( probeDir , { recursive : true , force : true } ) ;
21- }
22- } ) ( ) ;
236import { afterEach , describe , expect , it , vi } from "vitest" ;
247import { withEnv , withEnvAsync } from "../test-utils/env.js" ;
258import {
@@ -36,13 +19,35 @@ import {
3619} from "./fs-safe.js" ;
3720
3821const tempDirs = createTrackedTempDirs ( ) ;
22+ let hardlinkSupport : Promise < boolean > | undefined ;
3923
4024afterEach ( async ( ) => {
4125 __setFsSafeTestHooksForTest ( undefined ) ;
4226 vi . restoreAllMocks ( ) ;
4327 await tempDirs . cleanup ( ) ;
4428} ) ;
4529
30+ async function canCreateHardlinks ( ) : Promise < boolean > {
31+ hardlinkSupport ??= ( async ( ) => {
32+ let probeDir : string | undefined ;
33+ try {
34+ probeDir = await tempDirs . make ( "openclaw-fs-hardlink-probe-" ) ;
35+ const targetFile = path . join ( probeDir , "target.txt" ) ;
36+ const linkFile = path . join ( probeDir , "link.txt" ) ;
37+ await fs . writeFile ( targetFile , "target" , "utf8" ) ;
38+ await fs . link ( targetFile , linkFile ) ;
39+ return true ;
40+ } catch {
41+ return false ;
42+ } finally {
43+ if ( probeDir !== undefined ) {
44+ await fs . rm ( probeDir , { recursive : true , force : true } ) ;
45+ }
46+ }
47+ } ) ( ) ;
48+ return await hardlinkSupport ;
49+ }
50+
4651async function expectRejectCode ( promise : Promise < unknown > , expected : string | RegExp ) {
4752 const err = await promise . catch ( ( caught : unknown ) => caught ) ;
4853 if ( err === undefined ) {
@@ -365,7 +370,10 @@ describe("fs-safe", () => {
365370 } ) ;
366371 } ) ;
367372
368- it . skipIf ( ! canCreateHardlinks ) ( "blocks hardlink aliases under root" , async ( ) => {
373+ it ( "blocks hardlink aliases under root" , async ( ) => {
374+ if ( ! ( await canCreateHardlinks ( ) ) ) {
375+ return ;
376+ }
369377 const root = await tempDirs . make ( "openclaw-fs-safe-root-" ) ;
370378 const hardlinkPath = path . join ( root , "link.txt" ) ;
371379 await withOutsideHardlinkAlias ( {
@@ -492,7 +500,10 @@ describe("fs-safe", () => {
492500 await expectRejectCode ( ( await openRoot ( root ) ) . write ( "../escape.txt" , "x" ) , "outside-workspace" ) ;
493501 } ) ;
494502
495- it . skipIf ( ! canCreateHardlinks ) ( "rejects writing through hardlink aliases" , async ( ) => {
503+ it ( "rejects writing through hardlink aliases" , async ( ) => {
504+ if ( ! ( await canCreateHardlinks ( ) ) ) {
505+ return ;
506+ }
496507 const root = await tempDirs . make ( "openclaw-fs-safe-root-" ) ;
497508 const hardlinkPath = path . join ( root , "alias.txt" ) ;
498509 await withOutsideHardlinkAlias ( {
@@ -504,7 +515,10 @@ describe("fs-safe", () => {
504515 } ) ;
505516 } ) ;
506517
507- it . skipIf ( ! canCreateHardlinks ) ( "rejects appending through hardlink aliases" , async ( ) => {
518+ it ( "rejects appending through hardlink aliases" , async ( ) => {
519+ if ( ! ( await canCreateHardlinks ( ) ) ) {
520+ return ;
521+ }
508522 const root = await tempDirs . make ( "openclaw-fs-safe-root-" ) ;
509523 const hardlinkPath = path . join ( root , "alias.txt" ) ;
510524 await withOutsideHardlinkAlias ( {
0 commit comments