@@ -27,6 +27,7 @@ vi.mock('$lib/api/client.js', () => ({
2727 clearAuthToken : vi . fn ( ) ,
2828 ApiError : class extends Error {
2929 status : number ;
30+ retryable = false ;
3031 constructor ( status : number , message : string ) {
3132 super ( message ) ;
3233 this . status = status ;
@@ -41,10 +42,11 @@ import {
4142 getUser ,
4243 logout as apiLogout ,
4344} from '$lib/api/auth.js' ;
44- import { setAuthToken , clearAuthToken } from '$lib/api/client.js' ;
45+ import { setAuthToken , clearAuthToken , ApiError } from '$lib/api/client.js' ;
4546
4647describe ( 'AuthStore' , ( ) => {
4748 beforeEach ( ( ) => {
49+ vi . useRealTimers ( ) ;
4850 for ( const k of Object . keys ( store ) ) delete store [ k ] ;
4951 vi . clearAllMocks ( ) ;
5052 } ) ;
@@ -91,18 +93,62 @@ describe('AuthStore', () => {
9193 expect ( auth . user ) . toEqual ( { id : '1' , username : 'admin' } ) ;
9294 } ) ;
9395
94- it ( 'clears invalid token when getUser fails' , async ( ) => {
95- store [ LOCAL_STORAGE_KEYS . authToken ] = 'expired-token' ;
96+ it . each ( [ 401 , 403 ] ) (
97+ 'clears an invalid token after an authoritative %i rejection' ,
98+ async ( status ) => {
99+ store [ LOCAL_STORAGE_KEYS . authToken ] = 'expired-token' ;
100+ vi . mocked ( getAuthStatus ) . mockResolvedValue ( {
101+ needsSetup : false ,
102+ isAuthenticated : false ,
103+ authDisabled : false ,
104+ } ) ;
105+ vi . mocked ( getUser ) . mockRejectedValue ( new ApiError ( status , 'Rejected' ) ) ;
106+ const auth = new AuthStore ( ) ;
107+ await auth . checkAuthStatus ( ) ;
108+ expect ( auth . token ) . toBeNull ( ) ;
109+ expect ( clearAuthToken ) . toHaveBeenCalled ( ) ;
110+ } ,
111+ ) ;
112+
113+ it ( 'keeps the token and retries when auth status is temporarily unreachable' , async ( ) => {
114+ vi . useFakeTimers ( ) ;
115+ store [ LOCAL_STORAGE_KEYS . authToken ] = 'saved-token' ;
116+ vi . mocked ( getAuthStatus ) . mockRejectedValue ( new TypeError ( 'Failed to fetch' ) ) ;
117+ const auth = new AuthStore ( ) ;
118+
119+ const check = auth . checkAuthStatus ( ) ;
120+ await vi . runAllTimersAsync ( ) ;
121+ await check ;
122+
123+ expect ( getAuthStatus ) . toHaveBeenCalledTimes ( 5 ) ;
124+ expect ( auth . token ) . toBe ( 'saved-token' ) ;
125+ expect ( auth . isUnavailable ) . toBe ( true ) ;
126+ expect ( auth . error ) . toBe ( 'Network error. Please check your connection.' ) ;
127+ expect ( clearAuthToken ) . not . toHaveBeenCalled ( ) ;
128+ } ) ;
129+
130+ it ( 'recovers from transient user validation without clearing the token' , async ( ) => {
131+ vi . useFakeTimers ( ) ;
132+ store [ LOCAL_STORAGE_KEYS . authToken ] = 'saved-token' ;
96133 vi . mocked ( getAuthStatus ) . mockResolvedValue ( {
97134 needsSetup : false ,
98- isAuthenticated : false ,
135+ isAuthenticated : true ,
99136 authDisabled : false ,
100137 } ) ;
101- vi . mocked ( getUser ) . mockRejectedValue ( new Error ( '401' ) ) ;
138+ vi . mocked ( getUser )
139+ . mockRejectedValueOnce ( new TypeError ( 'Failed to fetch' ) )
140+ . mockResolvedValueOnce ( { user : { id : '1' , username : 'admin' } } ) ;
102141 const auth = new AuthStore ( ) ;
103- await auth . checkAuthStatus ( ) ;
104- expect ( auth . token ) . toBeNull ( ) ;
105- expect ( clearAuthToken ) . toHaveBeenCalled ( ) ;
142+
143+ const check = auth . checkAuthStatus ( ) ;
144+ await vi . runAllTimersAsync ( ) ;
145+ await check ;
146+
147+ expect ( getUser ) . toHaveBeenCalledTimes ( 2 ) ;
148+ expect ( auth . token ) . toBe ( 'saved-token' ) ;
149+ expect ( auth . user ) . toEqual ( { id : '1' , username : 'admin' } ) ;
150+ expect ( auth . isUnavailable ) . toBe ( false ) ;
151+ expect ( clearAuthToken ) . not . toHaveBeenCalled ( ) ;
106152 } ) ;
107153
108154 it ( 'enters app mode without token when auth is disabled by server config' , async ( ) => {
@@ -143,6 +189,16 @@ describe('AuthStore', () => {
143189 expect ( result . success ) . toBe ( false ) ;
144190 expect ( auth . error ) . toBeTruthy ( ) ;
145191 } ) ;
192+
193+ it ( 'maps transport failures to a useful network error' , async ( ) => {
194+ vi . mocked ( apiLogin ) . mockRejectedValue ( new TypeError ( 'Failed to fetch' ) ) ;
195+ const auth = new AuthStore ( ) ;
196+ const result = await auth . login ( 'admin' , 'pass' ) ;
197+ expect ( result ) . toEqual ( {
198+ success : false ,
199+ error : 'Network error. Please check your connection.' ,
200+ } ) ;
201+ } ) ;
146202 } ) ;
147203
148204 describe ( 'register' , ( ) => {
0 commit comments