@@ -73,7 +73,97 @@ describe("sqlite WAL maintenance", () => {
7373 }
7474 } ) ;
7575
76- it ( "refuses NFS-backed databases when SQLite keeps WAL active" , ( ) => {
76+ it . each ( [
77+ [ "SMB" , 0x517b ] ,
78+ [ "CIFS" , 0xff534d42 ] ,
79+ [ "SMB2" , 0xfe534d42 ] ,
80+ ] ) ( "uses rollback journaling for databases on Linux %s volumes" , ( _label , fsType ) => {
81+ const tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sqlite-network-" ) ) ;
82+ try {
83+ const db = createMockDb ( ) ;
84+ vi . spyOn ( fs , "statfsSync" ) . mockReturnValue ( statfsFixture ( fsType ) ) ;
85+
86+ configureSqliteWalMaintenance ( db , {
87+ checkpointIntervalMs : 0 ,
88+ databasePath : path . join ( tempDir , "openclaw.sqlite" ) ,
89+ } ) ;
90+
91+ expect ( db [ "prepare" ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
92+ } finally {
93+ fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
94+ }
95+ } ) ;
96+
97+ it . each ( [
98+ String . raw `\\server\share\openclaw.sqlite` ,
99+ String . raw `\\?\UNC\server\share\openclaw.sqlite` ,
100+ "//server/share/openclaw.sqlite" ,
101+ "//?/UNC/server/share/openclaw.sqlite" ,
102+ ] ) ( "uses rollback journaling for databases on Windows UNC paths: %s" , ( databasePath ) => {
103+ const db = createMockDb ( ) ;
104+ vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
105+
106+ configureSqliteWalMaintenance ( db , {
107+ checkpointIntervalMs : 0 ,
108+ databasePath,
109+ } ) ;
110+
111+ expect ( db [ "prepare" ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
112+ expect ( db [ "exec" ] ) . not . toHaveBeenCalled ( ) ;
113+ } ) ;
114+
115+ it ( "uses rollback journaling for mapped Windows network drives" , ( ) => {
116+ const db = createMockDb ( ) ;
117+ const databasePath = String . raw `Z:\state\openclaw.sqlite` ;
118+ vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
119+ const realpath = vi
120+ . spyOn ( fs . realpathSync , "native" )
121+ . mockReturnValue ( String . raw `\\server\share\state\openclaw.sqlite` ) ;
122+
123+ configureSqliteWalMaintenance ( db , {
124+ checkpointIntervalMs : 0 ,
125+ databasePath,
126+ } ) ;
127+
128+ expect ( realpath ) . toHaveBeenCalledWith ( databasePath ) ;
129+ expect ( db [ "prepare" ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
130+ expect ( db [ "exec" ] ) . not . toHaveBeenCalled ( ) ;
131+ } ) ;
132+
133+ it ( "does not treat namespaced Windows local drives as UNC paths" , ( ) => {
134+ const db = createMockDb ( ) ;
135+ const databasePath = String . raw `\\?\C:\state\openclaw.sqlite` ;
136+ vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
137+ const realpath = vi . spyOn ( fs . realpathSync , "native" ) . mockReturnValue ( databasePath ) ;
138+
139+ configureSqliteWalMaintenance ( db , {
140+ checkpointIntervalMs : 0 ,
141+ databasePath,
142+ } ) ;
143+
144+ expect ( realpath ) . toHaveBeenCalledWith ( databasePath ) ;
145+ expect ( db [ "prepare" ] ) . not . toHaveBeenCalled ( ) ;
146+ expect ( db [ "exec" ] ) . toHaveBeenNthCalledWith ( 1 , "PRAGMA journal_mode = WAL;" ) ;
147+ } ) ;
148+
149+ it ( "uses rollback journaling when Windows cannot classify an opened drive path" , ( ) => {
150+ const db = createMockDb ( ) ;
151+ const databasePath = String . raw `Z:\restricted\openclaw.sqlite` ;
152+ vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
153+ vi . spyOn ( fs . realpathSync , "native" ) . mockImplementation ( ( ) => {
154+ throw new Error ( "access denied" ) ;
155+ } ) ;
156+
157+ configureSqliteWalMaintenance ( db , {
158+ checkpointIntervalMs : 0 ,
159+ databasePath,
160+ } ) ;
161+
162+ expect ( db [ "prepare" ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
163+ expect ( db [ "exec" ] ) . not . toHaveBeenCalled ( ) ;
164+ } ) ;
165+
166+ it ( "refuses network-backed databases when SQLite keeps WAL active" , ( ) => {
77167 const tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sqlite-nfs-" ) ) ;
78168 try {
79169 const db = createMockDb ( ) ;
@@ -137,6 +227,29 @@ describe("sqlite WAL maintenance", () => {
137227 }
138228 } ) ;
139229
230+ it ( "uses macOS SMB mount filesystem names" , ( ) => {
231+ const tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sqlite-smb-" ) ) ;
232+ try {
233+ const db = createMockDb ( ) ;
234+ vi . spyOn ( fs , "statfsSync" ) . mockReturnValue ( statfsFixture ( 0 ) ) ;
235+ vi . spyOn ( fs , "readFileSync" ) . mockImplementation ( ( ) => {
236+ throw new Error ( "no proc mountinfo" ) ;
237+ } ) ;
238+ vi . spyOn ( childProcess , "execFileSync" ) . mockReturnValue (
239+ Buffer . from ( `//server/share on ${ tempDir } (smbfs, nodev, nosuid)\n` ) ,
240+ ) ;
241+
242+ configureSqliteWalMaintenance ( db , {
243+ checkpointIntervalMs : 0 ,
244+ databasePath : path . join ( tempDir , "openclaw.sqlite" ) ,
245+ } ) ;
246+
247+ expect ( db [ "prepare" ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
248+ } finally {
249+ fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
250+ }
251+ } ) ;
252+
140253 it ( "parses Linux mount command filesystem names when proc mountinfo is unavailable" , ( ) => {
141254 const tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sqlite-nfs-" ) ) ;
142255 try {
0 commit comments