@@ -42,9 +42,9 @@ describe('syncToR2', () => {
4242 it ( 'returns error when source has no config file' , async ( ) => {
4343 const { sandbox, startProcessMock } = createMockSandbox ( ) ;
4444 startProcessMock
45- . mockResolvedValueOnce ( createMockProcess ( 's3fs on /data/moltbot type fuse.s3fs \n' ) )
46- . mockResolvedValueOnce ( createMockProcess ( '' , { exitCode : 1 } ) ) // No openclaw.json
47- . mockResolvedValueOnce ( createMockProcess ( '' , { exitCode : 1 } ) ) ; // No clawdbot.json either
45+ . mockResolvedValueOnce ( createMockProcess ( 'mounted \n' ) )
46+ . mockResolvedValueOnce ( createMockProcess ( '' ) ) // No openclaw.json
47+ . mockResolvedValueOnce ( createMockProcess ( '' ) ) ; // No clawdbot.json either
4848
4949 const env = createMockEnvWithR2 ( ) ;
5050
@@ -56,62 +56,76 @@ describe('syncToR2', () => {
5656 } ) ;
5757
5858 describe ( 'sync execution' , ( ) => {
59- it ( 'returns success when sync completes ' , async ( ) => {
59+ it ( 'returns success when timestamp file is written ' , async ( ) => {
6060 const { sandbox, startProcessMock } = createMockSandbox ( ) ;
6161 const timestamp = '2026-01-27T12:00:00+00:00' ;
6262
63- // Calls: mount check, check openclaw.json , rsync, cat timestamp
63+ // Calls: mount check, config detect , rsync (all-in-one) , cat timestamp
6464 startProcessMock
65- . mockResolvedValueOnce ( createMockProcess ( 's3fs on /data/moltbot type fuse.s3fs \n' ) )
66- . mockResolvedValueOnce ( createMockProcess ( 'ok ' ) )
67- . mockResolvedValueOnce ( createMockProcess ( '' ) )
68- . mockResolvedValueOnce ( createMockProcess ( timestamp ) ) ;
65+ . mockResolvedValueOnce ( createMockProcess ( 'mounted \n' ) )
66+ . mockResolvedValueOnce ( createMockProcess ( 'exists ' ) )
67+ . mockResolvedValueOnce ( createMockProcess ( '' ) ) // rsync chain
68+ . mockResolvedValueOnce ( createMockProcess ( timestamp ) ) ; // cat timestamp
6969
7070 const env = createMockEnvWithR2 ( ) ;
71-
7271 const result = await syncToR2 ( sandbox , env ) ;
7372
7473 expect ( result . success ) . toBe ( true ) ;
7574 expect ( result . lastSync ) . toBe ( timestamp ) ;
7675 } ) ;
7776
78- it ( 'returns error when rsync fails (no timestamp created) ' , async ( ) => {
77+ it ( 'falls back to legacy clawdbot config directory ' , async ( ) => {
7978 const { sandbox, startProcessMock } = createMockSandbox ( ) ;
79+ const timestamp = '2026-01-27T12:00:00+00:00' ;
8080
81- // Calls: mount check, check openclaw.json, rsync (fails), cat timestamp (empty)
8281 startProcessMock
83- . mockResolvedValueOnce ( createMockProcess ( 's3fs on /data/moltbot type fuse.s3fs\n' ) )
84- . mockResolvedValueOnce ( createMockProcess ( 'ok' ) )
85- . mockResolvedValueOnce ( createMockProcess ( '' , { exitCode : 1 } ) )
86- . mockResolvedValueOnce ( createMockProcess ( '' ) ) ;
82+ . mockResolvedValueOnce ( createMockProcess ( 'mounted\n' ) )
83+ . mockResolvedValueOnce ( createMockProcess ( '' ) ) // No openclaw.json
84+ . mockResolvedValueOnce ( createMockProcess ( 'exists' ) ) // clawdbot.json found
85+ . mockResolvedValueOnce ( createMockProcess ( '' ) ) // rsync chain
86+ . mockResolvedValueOnce ( createMockProcess ( timestamp ) ) ;
8787
8888 const env = createMockEnvWithR2 ( ) ;
89-
9089 const result = await syncToR2 ( sandbox , env ) ;
9190
91+ expect ( result . success ) . toBe ( true ) ;
92+
93+ // rsync chain should reference .clawdbot
94+ const rsyncCall = startProcessMock . mock . calls [ 3 ] [ 0 ] ;
95+ expect ( rsyncCall ) . toContain ( '/root/.clawdbot/' ) ;
96+ } ) ;
97+
98+ it ( 'returns error when no timestamp after sync' , async ( ) => {
99+ const { sandbox, startProcessMock } = createMockSandbox ( ) ;
100+
101+ startProcessMock
102+ . mockResolvedValueOnce ( createMockProcess ( 'mounted\n' ) )
103+ . mockResolvedValueOnce ( createMockProcess ( 'exists' ) )
104+ . mockResolvedValueOnce ( createMockProcess ( '' ) ) // rsync chain
105+ . mockResolvedValue ( createMockProcess ( '' ) ) ; // all cat polls return empty
106+
107+ const env = createMockEnvWithR2 ( ) ;
108+ const result = await syncToR2 ( sandbox , env , 10 , 3 ) ; // fast: 10ms interval, 3 polls
109+
92110 expect ( result . success ) . toBe ( false ) ;
93- expect ( result . error ) . toBe ( 'Sync failed ' ) ;
111+ expect ( result . error ) . toBe ( 'Sync timed out ' ) ;
94112 } ) ;
95113
96- it ( 'verifies rsync command is called with correct flags ' , async ( ) => {
114+ it ( 'verifies rsync command excludes .git ' , async ( ) => {
97115 const { sandbox, startProcessMock } = createMockSandbox ( ) ;
98116 const timestamp = '2026-01-27T12:00:00+00:00' ;
99117
100118 startProcessMock
101- . mockResolvedValueOnce ( createMockProcess ( 's3fs on /data/moltbot type fuse.s3fs \n' ) )
102- . mockResolvedValueOnce ( createMockProcess ( 'ok ' ) )
119+ . mockResolvedValueOnce ( createMockProcess ( 'mounted \n' ) )
120+ . mockResolvedValueOnce ( createMockProcess ( 'exists ' ) )
103121 . mockResolvedValueOnce ( createMockProcess ( '' ) )
104122 . mockResolvedValueOnce ( createMockProcess ( timestamp ) ) ;
105123
106124 const env = createMockEnvWithR2 ( ) ;
107-
108125 await syncToR2 ( sandbox , env ) ;
109126
110- // Third call should be rsync to openclaw/ R2 prefix
111127 const rsyncCall = startProcessMock . mock . calls [ 2 ] [ 0 ] ;
112- expect ( rsyncCall ) . toContain ( 'rsync' ) ;
113- expect ( rsyncCall ) . toContain ( '--no-times' ) ;
114- expect ( rsyncCall ) . toContain ( '--delete' ) ;
128+ expect ( rsyncCall ) . toContain ( "--exclude='.git'" ) ;
115129 expect ( rsyncCall ) . toContain ( '/root/.openclaw/' ) ;
116130 expect ( rsyncCall ) . toContain ( '/data/moltbot/openclaw/' ) ;
117131 } ) ;
0 commit comments