@@ -14,6 +14,9 @@ import {
1414function createMockDb ( ) : DatabaseSync {
1515 return {
1616 exec : vi . fn ( ) ,
17+ prepare : vi . fn ( ( ) => ( {
18+ get : vi . fn ( ( ) => ( { journal_mode : "delete" } ) ) ,
19+ } ) ) ,
1720 } as unknown as DatabaseSync ;
1821}
1922
@@ -59,11 +62,32 @@ describe("sqlite WAL maintenance", () => {
5962 } ) ;
6063
6164 expect ( statfs ) . toHaveBeenCalledWith ( tempDir ) ;
62- expect ( db [ "exec " ] ) . toHaveBeenCalledTimes ( 1 ) ;
63- expect ( db [ "exec" ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
65+ expect ( db [ "prepare " ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
66+ expect ( db [ "exec" ] ) . not . toHaveBeenCalled ( ) ;
6467 expect ( maintenance . checkpoint ( ) ) . toBe ( true ) ;
6568 expect ( maintenance . close ( ) ) . toBe ( true ) ;
66- expect ( db [ "exec" ] ) . toHaveBeenCalledTimes ( 1 ) ;
69+ expect ( db [ "exec" ] ) . not . toHaveBeenCalled ( ) ;
70+ } finally {
71+ fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
72+ }
73+ } ) ;
74+
75+ it ( "refuses NFS-backed databases when SQLite keeps WAL active" , ( ) => {
76+ const tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sqlite-nfs-" ) ) ;
77+ try {
78+ const db = createMockDb ( ) ;
79+ vi . mocked ( db [ "prepare" ] ) . mockReturnValue ( {
80+ get : vi . fn ( ( ) => ( { journal_mode : "wal" } ) ) ,
81+ } as unknown as ReturnType < DatabaseSync [ "prepare" ] > ) ;
82+ vi . spyOn ( fs , "statfsSync" ) . mockReturnValue ( statfsFixture ( 0x6969 ) ) ;
83+
84+ expect ( ( ) =>
85+ configureSqliteWalMaintenance ( db , {
86+ checkpointIntervalMs : 0 ,
87+ databaseLabel : "test-db" ,
88+ databasePath : path . join ( tempDir , "openclaw.sqlite" ) ,
89+ } ) ,
90+ ) . toThrow ( / t e s t - d b .* j o u r n a l _ m o d e = w a l / ) ;
6791 } finally {
6892 fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
6993 }
@@ -83,7 +107,7 @@ describe("sqlite WAL maintenance", () => {
83107 databasePath : path . join ( tempDir , "openclaw.sqlite" ) ,
84108 } ) ;
85109
86- expect ( db [ "exec " ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
110+ expect ( db [ "prepare " ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
87111 } finally {
88112 fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
89113 }
@@ -106,7 +130,30 @@ describe("sqlite WAL maintenance", () => {
106130 databasePath : path . join ( tempDir , "openclaw.sqlite" ) ,
107131 } ) ;
108132
109- expect ( db [ "exec" ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
133+ expect ( db [ "prepare" ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
134+ } finally {
135+ fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
136+ }
137+ } ) ;
138+
139+ it ( "parses Linux mount command filesystem names when proc mountinfo is unavailable" , ( ) => {
140+ const tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sqlite-nfs-" ) ) ;
141+ try {
142+ const db = createMockDb ( ) ;
143+ vi . spyOn ( fs , "statfsSync" ) . mockReturnValue ( statfsFixture ( 0 ) ) ;
144+ vi . spyOn ( fs , "readFileSync" ) . mockImplementation ( ( ) => {
145+ throw new Error ( "no proc mountinfo" ) ;
146+ } ) ;
147+ vi . spyOn ( childProcess , "execFileSync" ) . mockReturnValue (
148+ Buffer . from ( `server:/share on ${ tempDir } type nfs4 (rw,relatime)\n` ) ,
149+ ) ;
150+
151+ configureSqliteWalMaintenance ( db , {
152+ checkpointIntervalMs : 0 ,
153+ databasePath : path . join ( tempDir , "openclaw.sqlite" ) ,
154+ } ) ;
155+
156+ expect ( db [ "prepare" ] ) . toHaveBeenCalledWith ( "PRAGMA journal_mode = DELETE;" ) ;
110157 } finally {
111158 fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
112159 }
0 commit comments