@@ -164,7 +164,14 @@ describe('RuntimeProvider lifecycle', () => {
164164 useSessionStore . setState ( {
165165 ...useSessionStore . getState ( ) ,
166166 currentSessionId : 'session-2' ,
167- fetchSessions : vi . fn ( ) . mockResolvedValue ( undefined ) ,
167+ fetchSessions : vi . fn ( async ( gatewayAPI : any ) => {
168+ await gatewayAPI . listSessions ( )
169+ } ) ,
170+ projects : [ {
171+ id : 'group_today' ,
172+ name : 'Today' ,
173+ sessions : [ { id : 'session-2' , title : 'Two' , time : new Date ( 0 ) . toISOString ( ) } ] ,
174+ } ] ,
168175 } as any )
169176
170177 let runtimeSnapshot : any = null
@@ -183,12 +190,65 @@ describe('RuntimeProvider lifecycle', () => {
183190
184191 const methods = client . call . mock . calls . map ( ( call : any [ ] ) => call [ 0 ] )
185192 const switchIndex = methods . indexOf ( 'gateway.switchWorkspace' )
193+ const fetchIndex = methods . indexOf ( 'gateway.listSessions' )
186194 const bindIndex = methods . indexOf ( 'gateway.bindStream' )
187195 expect ( switchIndex ) . toBeGreaterThanOrEqual ( 0 )
196+ expect ( fetchIndex ) . toBeGreaterThanOrEqual ( 0 )
188197 expect ( bindIndex ) . toBeGreaterThanOrEqual ( 0 )
189- expect ( switchIndex ) . toBeLessThan ( bindIndex )
198+ expect ( switchIndex ) . toBeLessThan ( fetchIndex )
199+ expect ( fetchIndex ) . toBeLessThan ( bindIndex )
190200 expect ( client . call ) . toHaveBeenCalledWith ( 'gateway.switchWorkspace' , { workspace_hash : 'w2' } )
191201 expect ( client . call ) . toHaveBeenCalledWith ( 'gateway.bindStream' , { session_id : 'session-2' , channel : 'all' } )
192202 } )
203+
204+ it ( 'recovers reconnect when rebinding a stale session fails' , async ( ) => {
205+ sessionStorage . setItem (
206+ 'neocode.browserRuntimeConfig' ,
207+ JSON . stringify ( { mode : 'browser' , gatewayBaseURL : 'http://127.0.0.1:8080' , token : 'tok' } ) ,
208+ )
209+ useWorkspaceStore . setState ( {
210+ fetchWorkspaces : vi . fn ( ) . mockResolvedValue ( undefined ) ,
211+ workspaces : [ { hash : 'w2' , path : '/workspace-two' , name : 'Two' , createdAt : '' , updatedAt : '' } ] ,
212+ currentWorkspaceHash : 'w2' ,
213+ } as any )
214+ useSessionStore . setState ( {
215+ ...useSessionStore . getState ( ) ,
216+ currentSessionId : 'session-stale' ,
217+ fetchSessions : vi . fn ( ) . mockResolvedValue ( undefined ) ,
218+ projects : [ {
219+ id : 'group_today' ,
220+ name : 'Today' ,
221+ sessions : [ { id : 'session-stale' , title : 'Stale' , time : new Date ( 0 ) . toISOString ( ) } ] ,
222+ } ] ,
223+ } as any )
224+ const warnSpy = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } )
225+
226+ let runtimeSnapshot : any = null
227+ render (
228+ < RuntimeProvider >
229+ < RuntimeProbe onReady = { ( rt ) => { runtimeSnapshot = rt } } />
230+ </ RuntimeProvider > ,
231+ )
232+ await waitFor ( ( ) => expect ( runtimeSnapshot ?. status ) . toBe ( 'connected' ) )
233+ const client = clients [ 0 ]
234+ client . call . mockImplementation ( async ( method : string , params ?: any ) => {
235+ if ( method === 'gateway.bindStream' && params ?. session_id === 'session-stale' ) {
236+ throw new Error ( 'session not found' )
237+ }
238+ if ( method === 'gateway.authenticate' ) return { payload : { } }
239+ if ( method === 'gateway.listWorkspaces' ) return { payload : { workspaces : [ ] } }
240+ if ( method === 'gateway.ping' ) return { payload : { } }
241+ return { payload : { } }
242+ } )
243+
244+ await act ( async ( ) => {
245+ await client . _emitReconnect ( )
246+ } )
247+
248+ await waitFor ( ( ) => expect ( runtimeSnapshot ?. status ) . toBe ( 'connected' ) )
249+ expect ( runtimeSnapshot . error ) . toBe ( '' )
250+ expect ( useSessionStore . getState ( ) . setCurrentSessionId ) . toHaveBeenCalledWith ( '' )
251+ warnSpy . mockRestore ( )
252+ } )
193253} )
194254
0 commit comments