@@ -352,25 +352,39 @@ describe('ToolHandlers', () => {
352352
353353 it ( 'should call reauthenticate when auth status flips to true' , async ( ) => {
354354 mockIBClient . checkAuthenticationStatus = vi . fn ( )
355- . mockResolvedValueOnce ( false ) // Continue past early return
356- . mockResolvedValueOnce ( true ) ; // Browser path: now authenticated
355+ . mockResolvedValueOnce ( false ) // Initial check: not yet authenticated (skip early return)
356+ . mockResolvedValueOnce ( true ) // Browser path: now authenticated
357+ . mockResolvedValueOnce ( true ) ; // Final re-check after reauth: still authenticated
357358 mockIBClient . reauthenticate = vi . fn ( ) . mockResolvedValue ( undefined ) ;
358359
359360 await ( handlers as any ) . ensureAuth ( ) ;
360361
361362 expect ( mockIBClient . reauthenticate ) . toHaveBeenCalled ( ) ;
362363 } ) ;
363364
364- it ( 'should proceed even if reauthenticate fails' , async ( ) => {
365+ it ( 'should proceed when reauthenticate fails but session is still authenticated ' , async ( ) => {
365366 mockIBClient . checkAuthenticationStatus = vi . fn ( )
366- . mockResolvedValueOnce ( false )
367- . mockResolvedValueOnce ( true ) ;
367+ . mockResolvedValueOnce ( false ) // Initial check: skip early return
368+ . mockResolvedValueOnce ( true ) // Browser path: authenticated
369+ . mockResolvedValueOnce ( true ) ; // Final re-check still passes
368370 mockIBClient . reauthenticate = vi . fn ( ) . mockRejectedValue ( new Error ( 'Reauth failed' ) ) ;
369371
370- // Should not throw — error is caught and logged
372+ // Reauth error is swallowed because the final auth status check still succeeds.
371373 await expect ( ( handlers as any ) . ensureAuth ( ) ) . resolves . not . toThrow ( ) ;
372374 expect ( mockIBClient . reauthenticate ) . toHaveBeenCalled ( ) ;
373375 } ) ;
376+
377+ it ( 'should throw when reauthenticate fails and session is no longer authenticated' , async ( ) => {
378+ mockIBClient . checkAuthenticationStatus = vi . fn ( )
379+ . mockResolvedValueOnce ( false ) // Initial check: skip early return
380+ . mockResolvedValueOnce ( true ) // Browser path: authenticated
381+ . mockResolvedValueOnce ( false ) ; // Final re-check: session lost
382+ mockIBClient . reauthenticate = vi . fn ( ) . mockRejectedValue ( new Error ( 'Reauth failed' ) ) ;
383+
384+ await expect ( ( handlers as any ) . ensureAuth ( ) )
385+ . rejects . toThrow ( 'Authentication required' ) ;
386+ expect ( mockIBClient . reauthenticate ) . toHaveBeenCalled ( ) ;
387+ } ) ;
374388 } ) ;
375389
376390 describe ( 'startBrowserAuthPolling' , ( ) => {
@@ -389,21 +403,32 @@ describe('ToolHandlers', () => {
389403 mockIBClient . reauthenticate = vi . fn ( ) . mockResolvedValue ( undefined ) ;
390404
391405 ( handlers as any ) . startBrowserAuthPolling ( 'https://localhost:5000' , 5000 ) ;
392- await vi . runAllTimersAsync ( ) ;
406+ await vi . advanceTimersByTimeAsync ( 120_000 ) ;
393407
394408 expect ( mockIBClient . checkAuthenticationStatus ) . toHaveBeenCalledTimes ( 2 ) ;
395409 expect ( mockIBClient . reauthenticate ) . toHaveBeenCalledTimes ( 1 ) ;
396410 } ) ;
397411
398- it ( 'should not call reauthenticate when auth never detected' , async ( ) => {
412+ it ( 'should stop polling once the deadline passes when auth never detected' , async ( ) => {
399413 mockIBClient . checkAuthenticationStatus = vi . fn ( ) . mockResolvedValue ( false ) ;
400414 mockIBClient . reauthenticate = vi . fn ( ) ;
401415
402416 ( handlers as any ) . startBrowserAuthPolling ( 'https://localhost:5000' , 5000 ) ;
403- await vi . runAllTimersAsync ( ) ;
417+ // Advance past the 2-minute deadline.
418+ await vi . advanceTimersByTimeAsync ( 120_000 ) ;
404419
405- expect ( mockIBClient . checkAuthenticationStatus ) . toHaveBeenCalledTimes ( 60 ) ;
420+ // Deadline-based loop produces fewer than the legacy 60 attempts since the
421+ // backoff caps at 10s. We assert (a) reauthenticate was never called and
422+ // (b) polling stayed within the documented 2-minute upper bound.
406423 expect ( mockIBClient . reauthenticate ) . not . toHaveBeenCalled ( ) ;
424+ const attemptsWithinDeadline = ( mockIBClient . checkAuthenticationStatus as any ) . mock . calls . length ;
425+ expect ( attemptsWithinDeadline ) . toBeGreaterThan ( 0 ) ;
426+ expect ( attemptsWithinDeadline ) . toBeLessThan ( 60 ) ;
427+
428+ // Advancing further must not produce additional polls.
429+ await vi . advanceTimersByTimeAsync ( 120_000 ) ;
430+ expect ( ( mockIBClient . checkAuthenticationStatus as any ) . mock . calls . length )
431+ . toBe ( attemptsWithinDeadline ) ;
407432 } ) ;
408433
409434 it ( 'should handle checkAuthenticationStatus throwing without stopping' , async ( ) => {
@@ -413,7 +438,7 @@ describe('ToolHandlers', () => {
413438 mockIBClient . reauthenticate = vi . fn ( ) . mockResolvedValue ( undefined ) ;
414439
415440 ( handlers as any ) . startBrowserAuthPolling ( 'https://localhost:5000' , 5000 ) ;
416- await vi . runAllTimersAsync ( ) ;
441+ await vi . advanceTimersByTimeAsync ( 120_000 ) ;
417442
418443 expect ( mockIBClient . checkAuthenticationStatus ) . toHaveBeenCalledTimes ( 2 ) ;
419444 expect ( mockIBClient . reauthenticate ) . toHaveBeenCalledTimes ( 1 ) ;
0 commit comments