@@ -287,6 +287,134 @@ describe('utils', () => {
287287 // Should not have transitioned to LoggedIn
288288 expect ( utils . loggedIn . value ) . not . toBe ( utils . AppState . LoggedIn ) ;
289289 } ) ;
290+
291+ it ( 'catch block logs error and maintains logged in state when tokens exist' , async ( ) => {
292+ const consoleLogSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { /* no-op */ } ) ;
293+ const consoleErrorSpy = vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { /* no-op */ } ) ;
294+
295+ // Mock localStorage to return loginSalt
296+ ( window . localStorage . getItem as any ) . mockImplementation ( ( key : string ) =>
297+ key === 'loginSalt' ? 'test-salt' : null
298+ ) ;
299+
300+ // Mock service worker to return secret
301+ const postMessage = vi . fn ( ( msg : any ) => {
302+ if ( msg . type === MessageType . RequestSecret ) {
303+ triggerSWMessage ( { type : MessageType . RequestSecret , data : 'test-secret' } ) ;
304+ }
305+ } ) ;
306+ withServiceWorker ( { postMessage } as any ) ;
307+
308+ // Mock fetchClient to throw an error
309+ const testError = new Error ( 'Network timeout' ) ;
310+ ( utils . fetchClient as any ) . GET = vi . fn ( async ( path : string ) => {
311+ if ( path === '/auth/jwt_refresh' ) {
312+ throw testError ;
313+ }
314+ return { error : null , response : { status : 200 } } ;
315+ } ) ;
316+
317+ // Set initial state
318+ utils . loggedIn . value = utils . AppState . Loading ;
319+
320+ await utils . refresh_access_token ( ) ;
321+
322+ // Verify logging
323+ expect ( consoleLogSpy ) . toHaveBeenCalledWith ( 'Failed to refresh access token:' , testError ) ;
324+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith ( testError ) ;
325+
326+ // Verify state is set to LoggedIn when both tokens exist
327+ expect ( utils . loggedIn . value ) . toBe ( utils . AppState . LoggedIn ) ;
328+
329+ consoleLogSpy . mockRestore ( ) ;
330+ consoleErrorSpy . mockRestore ( ) ;
331+ } ) ;
332+
333+ it ( 'catch block calls logout when loginSalt is missing' , async ( ) => {
334+ const consoleLogSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { /* no-op */ } ) ;
335+ const consoleErrorSpy = vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { /* no-op */ } ) ;
336+
337+ // Mock localStorage to return no loginSalt
338+ ( window . localStorage . getItem as any ) . mockImplementation ( ( ) => null ) ;
339+
340+ // Mock service worker to return secret (but loginSalt is missing)
341+ const postMessage = vi . fn ( ( msg : any ) => {
342+ if ( msg . type === MessageType . RequestSecret ) {
343+ triggerSWMessage ( { type : MessageType . RequestSecret , data : 'test-secret' } ) ;
344+ }
345+ } ) ;
346+ withServiceWorker ( { postMessage } as any ) ;
347+
348+ // Mock fetchClient to throw an error
349+ const testError = new Error ( 'Network timeout' ) ;
350+ ( utils . fetchClient as any ) . GET = vi . fn ( async ( path : string ) => {
351+ if ( path === '/auth/jwt_refresh' ) {
352+ throw testError ;
353+ }
354+ return { error : null , response : { status : 200 } } ;
355+ } ) ;
356+
357+ // Mock logout function
358+ const logoutModule = await import ( '../components/Navbar' ) ;
359+ const logoutSpy = vi . spyOn ( logoutModule , 'logout' ) . mockImplementation ( async ( ) => { /* no-op */ } ) ;
360+
361+ await utils . refresh_access_token ( ) ;
362+
363+ // Verify logging
364+ expect ( consoleLogSpy ) . toHaveBeenCalledWith ( 'Failed to refresh access token:' , testError ) ;
365+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith ( testError ) ;
366+
367+ // Verify logout was called
368+ expect ( logoutSpy ) . toHaveBeenCalledWith ( false ) ;
369+
370+ consoleLogSpy . mockRestore ( ) ;
371+ consoleErrorSpy . mockRestore ( ) ;
372+ logoutSpy . mockRestore ( ) ;
373+ } ) ;
374+
375+ it ( 'catch block calls logout when secret is missing' , async ( ) => {
376+ const consoleLogSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { /* no-op */ } ) ;
377+ const consoleErrorSpy = vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { /* no-op */ } ) ;
378+
379+ // Mock localStorage to return loginSalt
380+ ( window . localStorage . getItem as any ) . mockImplementation ( ( key : string ) =>
381+ key === 'loginSalt' ? 'test-salt' : null
382+ ) ;
383+
384+ // Mock service worker to return no secret
385+ const postMessage = vi . fn ( ( msg : any ) => {
386+ if ( msg . type === MessageType . RequestSecret ) {
387+ triggerSWMessage ( { type : MessageType . RequestSecret , data : '' } ) ;
388+ }
389+ } ) ;
390+ withServiceWorker ( { postMessage } as any ) ;
391+
392+ // Mock fetchClient to throw an error
393+ const testError = new Error ( 'Network timeout' ) ;
394+ ( utils . fetchClient as any ) . GET = vi . fn ( async ( path : string ) => {
395+ if ( path === '/auth/jwt_refresh' ) {
396+ throw testError ;
397+ }
398+ return { error : null , response : { status : 200 } } ;
399+ } ) ;
400+
401+ // Mock logout function
402+ const logoutModule = await import ( '../components/Navbar' ) ;
403+ const logoutSpy = vi . spyOn ( logoutModule , 'logout' ) . mockImplementation ( async ( ) => { /* no-op */ } ) ;
404+
405+ await utils . refresh_access_token ( ) ;
406+
407+ // Verify logging
408+ expect ( consoleLogSpy ) . toHaveBeenCalledWith ( 'Failed to refresh access token:' , testError ) ;
409+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith ( testError ) ;
410+
411+ // Verify logout was called
412+ expect ( logoutSpy ) . toHaveBeenCalledWith ( false ) ;
413+
414+ consoleLogSpy . mockRestore ( ) ;
415+ consoleErrorSpy . mockRestore ( ) ;
416+ logoutSpy . mockRestore ( ) ;
417+ } ) ;
290418 } ) ;
291419
292420 describe ( 'get_salt & get_salt_for_user' , ( ) => {
0 commit comments