@@ -223,4 +223,178 @@ describe('app initialization bootstrap', () => {
223223
224224 consoleErrorSpy . mockRestore ( )
225225 } )
226+
227+ it ( 'returns an already resolved wait promise before initialization starts' , async ( ) => {
228+ const { waitForHydroServerInitialization } = await import ( '../appInitialization' )
229+
230+ await expect ( waitForHydroServerInitialization ( ) ) . resolves . toBeUndefined ( )
231+ } )
232+
233+ it ( 'resets the user when the authenticated user request returns 401' , async ( ) => {
234+ sessionState . isAuthenticated = true
235+ sessionState . hasAccessToken = true
236+ createHydroServerMock . mockResolvedValue ( undefined )
237+ fetchAllVocabulariesMock . mockResolvedValue ( undefined )
238+ userGetMock . mockResolvedValue ( {
239+ status : 401 ,
240+ data : { email : 'ignored@example.com' } ,
241+ } )
242+ listAllItemsMock . mockResolvedValue ( [
243+ { id : 'workspace-1' , name : 'Workspace 1' } ,
244+ ] )
245+
246+ const { startAppInitialization } = await import ( '../appInitialization' )
247+
248+ await startAppInitialization ( )
249+
250+ const userStore = useUserStore ( )
251+ expect ( userStore . user . email ) . toBe ( '' )
252+ } )
253+
254+ it ( 'records user fetch failures without breaking workspace bootstrap' , async ( ) => {
255+ const userError = new Error ( 'user request failed' )
256+ const consoleErrorSpy = vi
257+ . spyOn ( console , 'error' )
258+ . mockImplementation ( ( ) => undefined )
259+
260+ sessionState . isAuthenticated = true
261+ sessionState . hasAccessToken = true
262+ createHydroServerMock . mockResolvedValue ( undefined )
263+ fetchAllVocabulariesMock . mockResolvedValue ( undefined )
264+ userGetMock . mockRejectedValue ( userError )
265+ listAllItemsMock . mockResolvedValue ( [
266+ { id : 'workspace-1' , name : 'Workspace 1' } ,
267+ ] )
268+
269+ const {
270+ hasBootstrappedWorkspaces,
271+ initializationError,
272+ startAppInitialization,
273+ } = await import ( '../appInitialization' )
274+
275+ await startAppInitialization ( )
276+
277+ expect ( initializationError . value ) . toBe ( userError )
278+ expect ( hasBootstrappedWorkspaces . value ) . toBe ( true )
279+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
280+ 'Error fetching user' ,
281+ userError
282+ )
283+
284+ consoleErrorSpy . mockRestore ( )
285+ } )
286+
287+ it ( 'records workspace fetch failures without breaking user bootstrap' , async ( ) => {
288+ const workspaceError = new Error ( 'workspace request failed' )
289+ const consoleErrorSpy = vi
290+ . spyOn ( console , 'error' )
291+ . mockImplementation ( ( ) => undefined )
292+
293+ sessionState . isAuthenticated = true
294+ sessionState . hasAccessToken = true
295+ createHydroServerMock . mockResolvedValue ( undefined )
296+ fetchAllVocabulariesMock . mockResolvedValue ( undefined )
297+ userGetMock . mockResolvedValue ( {
298+ status : 200 ,
299+ data : { email : 'user@example.com' } ,
300+ } )
301+ listAllItemsMock . mockRejectedValue ( workspaceError )
302+
303+ const {
304+ hasBootstrappedWorkspaces,
305+ initializationError,
306+ startAppInitialization,
307+ } = await import ( '../appInitialization' )
308+
309+ await startAppInitialization ( )
310+
311+ const userStore = useUserStore ( )
312+ expect ( userStore . user . email ) . toBe ( 'user@example.com' )
313+ expect ( hasBootstrappedWorkspaces . value ) . toBe ( false )
314+ expect ( initializationError . value ) . toBe ( workspaceError )
315+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
316+ 'Error fetching workspaces' ,
317+ workspaceError
318+ )
319+
320+ consoleErrorSpy . mockRestore ( )
321+ } )
322+
323+ it ( 'records vocabulary fetch failures' , async ( ) => {
324+ const vocabularyError = new Error ( 'vocabulary request failed' )
325+ const consoleErrorSpy = vi
326+ . spyOn ( console , 'error' )
327+ . mockImplementation ( ( ) => undefined )
328+
329+ createHydroServerMock . mockResolvedValue ( undefined )
330+ fetchAllVocabulariesMock . mockRejectedValue ( vocabularyError )
331+
332+ const { initializationError, startAppInitialization } = await import (
333+ '../appInitialization'
334+ )
335+
336+ await startAppInitialization ( )
337+
338+ expect ( initializationError . value ) . toBe ( vocabularyError )
339+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
340+ 'Error fetching vocabularies' ,
341+ vocabularyError
342+ )
343+
344+ consoleErrorSpy . mockRestore ( )
345+ } )
346+
347+ it ( 'reuses the initialization promise and only attaches session listeners once' , async ( ) => {
348+ sessionState . isAuthenticated = false
349+ createHydroServerMock . mockResolvedValue ( undefined )
350+ fetchAllVocabulariesMock . mockResolvedValue ( undefined )
351+
352+ const { startAppInitialization } = await import ( '../appInitialization' )
353+
354+ const first = startAppInitialization ( )
355+ const second = startAppInitialization ( )
356+
357+ await first
358+ await second
359+
360+ expect ( first ) . toBe ( second )
361+ expect ( createHydroServerMock ) . toHaveBeenCalledTimes ( 1 )
362+ expect ( onMock ) . toHaveBeenCalledTimes ( 2 )
363+ } )
364+
365+ it ( 'refreshes authenticated state when session change and expiry callbacks fire' , async ( ) => {
366+ sessionState . isAuthenticated = true
367+ sessionState . hasAccessToken = true
368+ createHydroServerMock . mockResolvedValue ( undefined )
369+ fetchAllVocabulariesMock . mockResolvedValue ( undefined )
370+ userGetMock . mockResolvedValue ( {
371+ status : 200 ,
372+ data : { email : 'callback@example.com' } ,
373+ } )
374+ listAllItemsMock . mockResolvedValue ( [
375+ { id : 'workspace-1' , name : 'Workspace 1' } ,
376+ ] )
377+
378+ const { startAppInitialization } = await import ( '../appInitialization' )
379+
380+ await startAppInitialization ( )
381+
382+ const sessionChanged = onMock . mock . calls . find (
383+ ( [ eventName ] ) => eventName === 'session:changed'
384+ ) ?. [ 1 ] as ( ( ) => void ) | undefined
385+ const sessionExpired = onMock . mock . calls . find (
386+ ( [ eventName ] ) => eventName === 'session:expired'
387+ ) ?. [ 1 ] as ( ( ) => void ) | undefined
388+
389+ expect ( sessionChanged ) . toBeTypeOf ( 'function' )
390+ expect ( sessionExpired ) . toBeTypeOf ( 'function' )
391+
392+ sessionChanged ?.( )
393+ sessionExpired ?.( )
394+ await Promise . resolve ( )
395+ await Promise . resolve ( )
396+
397+ expect ( userGetMock ) . toHaveBeenCalledTimes ( 3 )
398+ expect ( listAllItemsMock ) . toHaveBeenCalledTimes ( 3 )
399+ } )
226400} )
0 commit comments