@@ -291,6 +291,23 @@ describe('useImmutableSession', () => {
291291 // Should NOT have called update -- token is still valid
292292 expect ( mockUpdate ) . not . toHaveBeenCalled ( ) ;
293293 } ) ;
294+
295+ it ( 'does not trigger refresh when session has error (prevents infinite loop)' , async ( ) => {
296+ // Simulate: token expired and last refresh failed (e.g. RefreshTokenError)
297+ const sessionWithError = createSession ( {
298+ accessTokenExpires : Date . now ( ) - 1000 , // expired
299+ error : 'RefreshTokenError' ,
300+ } ) ;
301+ setupUseSession ( sessionWithError ) ;
302+
303+ await act ( async ( ) => {
304+ renderHook ( ( ) => useImmutableSession ( ) ) ;
305+ } ) ;
306+
307+ // Must NOT call update - otherwise we would retry refresh repeatedly
308+ // and cause an infinite loop (update -> same session with error -> effect re-runs -> update again).
309+ expect ( mockUpdate ) . not . toHaveBeenCalled ( ) ;
310+ } ) ;
294311 } ) ;
295312
296313 describe ( 'getUser() respects pending refresh' , ( ) => {
@@ -317,5 +334,35 @@ describe('useImmutableSession', () => {
317334 // getUser() should have waited for the refresh and gotten the fresh token
318335 expect ( user ?. accessToken ) . toBe ( 'user-fresh-token' ) ;
319336 } ) ;
337+
338+ it ( 'getUser(true) still calls update with forceRefresh even when session has error' , async ( ) => {
339+ // Session is in error state (e.g. previous refresh failed)
340+ const sessionWithError = createSession ( {
341+ accessTokenExpires : Date . now ( ) - 1000 ,
342+ error : 'RefreshTokenError' ,
343+ } ) ;
344+ setupUseSession ( sessionWithError ) ;
345+
346+ // Server recovers and returns a valid session (e.g. user re-authenticated elsewhere)
347+ const recoveredSession = createSession ( {
348+ accessToken : 'recovered-token' ,
349+ accessTokenExpires : Date . now ( ) + 10 * 60 * 1000 ,
350+ user : { sub : 'user-1' , email : 'recovered@test.com' } ,
351+ } ) ;
352+ mockUpdate . mockResolvedValue ( recoveredSession ) ;
353+
354+ const { result } = renderHook ( ( ) => useImmutableSession ( ) ) ;
355+
356+ let user : any ;
357+ await act ( async ( ) => {
358+ user = await result . current . getUser ( true ) ;
359+ } ) ;
360+
361+ // forceRefresh must have been attempted (proactive effect does NOT run when session.error is set)
362+ expect ( mockUpdate ) . toHaveBeenCalledWith ( { forceRefresh : true } ) ;
363+ // When server returns a good session, we get the user
364+ expect ( user ?. accessToken ) . toBe ( 'recovered-token' ) ;
365+ expect ( user ?. profile ?. email ) . toBe ( 'recovered@test.com' ) ;
366+ } ) ;
320367 } ) ;
321368} ) ;
0 commit comments