@@ -117,12 +117,14 @@ describe('AuthProvider', () => {
117117 } ) ;
118118 } ) ;
119119
120+ // Initialization and Configuration
120121 it ( 'should configure Amplify on mount' , async ( ) => {
121122 // Verify Amplify.configure is called when the provider mounts
122123 await renderWithProvider ( ) ;
123124 expect ( Amplify . configure ) . toHaveBeenCalled ( ) ;
124125 } ) ;
125126
127+ // Session Handling
126128 it ( 'should set isSignedIn to false if no valid tokens are returned' , async ( ) => {
127129 // Render without valid tokens
128130 await renderWithProvider ( ) ;
@@ -149,6 +151,172 @@ describe('AuthProvider', () => {
149151 } ) ;
150152 } ) ;
151153
154+ it ( 'should handle missing tokens during session fetch' , async ( ) => {
155+ // Simulate a session fetch with missing tokens
156+ const incompleteSession = { tokens : { } } ;
157+
158+ await renderWithProvider ( { sessionMock : incompleteSession } ) ;
159+
160+ await waitFor ( ( ) => {
161+ // Assert that the user is not signed in due to missing tokens
162+ expect ( screen . getByTestId ( 'isSignedIn' ) . textContent ) . toBe ( 'false' ) ;
163+ expect ( screen . getByTestId ( 'user' ) . textContent ) . toBe ( '' ) ;
164+ } ) ;
165+ } ) ;
166+
167+ // Error Handling
168+ it ( 'should handle fetchAuthSession failure' , async ( ) => {
169+ // Mock fetchAuthSession to throw an error
170+ ( fetchAuthSession as jest . Mock ) . mockRejectedValue ( new Error ( 'Session fetch failed' ) ) ;
171+
172+ await renderWithProvider ( ) ;
173+
174+ await waitFor ( ( ) => {
175+ // Assert that the user is not signed in due to session fetch failure
176+ expect ( screen . getByTestId ( 'isSignedIn' ) . textContent ) . toBe ( 'false' ) ;
177+ expect ( screen . getByTestId ( 'user' ) . textContent ) . toBe ( '' ) ;
178+ } ) ;
179+ } ) ;
180+
181+ it ( 'should handle getCurrentUser failure gracefully' , async ( ) => {
182+ // Mock getCurrentUser to throw an error
183+ ( getCurrentUser as jest . Mock ) . mockRejectedValue ( new Error ( 'User fetch failed' ) ) ;
184+
185+ await renderWithProvider ( {
186+ sessionMock : createTokenMocks ( ) ,
187+ } ) ;
188+
189+ await waitFor ( ( ) => {
190+ // Assert that valid tokens do not automatically result in user data due to user fetch failure
191+ expect ( screen . getByTestId ( 'isSignedIn' ) . textContent ) . toBe ( 'true' ) ;
192+ expect ( screen . getByTestId ( 'user' ) . textContent ) . toBe ( '' ) ;
193+ } ) ;
194+ } ) ;
195+
196+ it ( 'should log an error and reset state when fetching user session fails' , async ( ) => {
197+ // Mock console.error to track calls
198+ const consoleErrorSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
199+
200+ // Mock fetchAuthSession to throw an error
201+ const sessionError = new Error ( 'Session fetch failed' ) ;
202+ ( fetchAuthSession as jest . Mock ) . mockRejectedValueOnce ( sessionError ) ;
203+
204+ // Render the provider
205+ await renderWithProvider ( ) ;
206+
207+ // Wait for the state to be reset
208+ await waitFor ( ( ) => {
209+ // Verify that the state is reset correctly
210+ expect ( screen . getByTestId ( 'isSignedIn' ) . textContent ) . toBe ( 'false' ) ;
211+ expect ( screen . getByTestId ( 'user' ) . textContent ) . toBe ( '' ) ;
212+ } ) ;
213+
214+ // Verify that the error was logged
215+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
216+ 'Error fetching user session:' ,
217+ sessionError
218+ ) ;
219+
220+ // Restore the original console.error implementation
221+ consoleErrorSpy . mockRestore ( ) ;
222+ } ) ;
223+
224+ it ( 'should log an error if signOut fails' , async ( ) => {
225+ // Mock console.error to track calls
226+ const consoleErrorSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
227+
228+ // Mock signOut to throw an error
229+ const signOutError = new Error ( 'Sign out failed' ) ;
230+ ( signOut as jest . Mock ) . mockRejectedValue ( signOutError ) ;
231+
232+ let contextValue : any ;
233+
234+ const TestComponent = ( ) => {
235+ contextValue = useContext ( AuthContext ) ;
236+ return null ;
237+ } ;
238+
239+ // Render the provider
240+ await act ( async ( ) => {
241+ render (
242+ < AuthProvider >
243+ < TestComponent />
244+ </ AuthProvider >
245+ ) ;
246+ } ) ;
247+
248+ // Attempt to sign out and verify the logged error
249+ await act ( async ( ) => {
250+ await contextValue . cognitoSignOut ( ) ;
251+ } ) ;
252+
253+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith ( 'Failed to sign out:' , signOutError ) ; // Error logged
254+
255+ // Restore the original console.error implementation
256+ consoleErrorSpy . mockRestore ( ) ;
257+ } ) ;
258+
259+ // Token Handling
260+ it ( 'should log a warning and reset state when the ID token is expired' , async ( ) => {
261+ // Mock console.warn to track calls
262+ const consoleWarnSpy = jest . spyOn ( console , 'warn' ) . mockImplementation ( ) ;
263+
264+ // Create mock tokens with an expired ID token
265+ const expiredIdToken = {
266+ tokens : {
267+ idToken : {
268+ toString : ( ) => `header.${ btoa ( JSON . stringify ( { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ) ) } .signature` ,
269+ payload : { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ,
270+ } ,
271+ accessToken : {
272+ toString : ( ) => `header.${ btoa ( JSON . stringify ( { exp : Math . floor ( Date . now ( ) / 1000 ) + 3600 } ) ) } .signature` ,
273+ payload : { exp : Math . floor ( Date . now ( ) / 1000 ) + 3600 } ,
274+ } ,
275+ } ,
276+ } ;
277+
278+ // Render the provider with the expired ID token
279+ await renderWithProvider ( { sessionMock : expiredIdToken } ) ;
280+
281+ // Wait for the state to be reset and verify the warning
282+ await waitFor ( ( ) => {
283+ expect ( screen . getByTestId ( 'isSignedIn' ) . textContent ) . toBe ( 'false' ) ; // State reset
284+ expect ( screen . getByTestId ( 'user' ) . textContent ) . toBe ( '' ) ;
285+ expect ( consoleWarnSpy ) . toHaveBeenCalledWith (
286+ 'ID token is expired. Consider refreshing the token.'
287+ ) ; // Warning logged
288+ } ) ;
289+
290+ // Restore the original console.warn implementation
291+ consoleWarnSpy . mockRestore ( ) ;
292+ } ) ;
293+
294+ it ( 'should handle expired tokens' , async ( ) => {
295+ // Create mock expired tokens
296+ const expiredTokens = {
297+ tokens : {
298+ idToken : {
299+ toString : ( ) => `header.${ btoa ( JSON . stringify ( { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ) ) } .signature` ,
300+ payload : { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ,
301+ } ,
302+ accessToken : {
303+ toString : ( ) => `header.${ btoa ( JSON . stringify ( { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ) ) } .signature` ,
304+ payload : { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ,
305+ } ,
306+ } ,
307+ } ;
308+
309+ // Render with expired tokens
310+ await renderWithProvider ( { sessionMock : expiredTokens } ) ;
311+
312+ await waitFor ( ( ) => {
313+ // Verify signed-in state is false and user is cleared
314+ expect ( screen . getByTestId ( 'isSignedIn' ) . textContent ) . toBe ( 'false' ) ;
315+ expect ( screen . getByTestId ( 'user' ) . textContent ) . toBe ( '' ) ;
316+ } ) ;
317+ } ) ;
318+
319+ // Hub Events
152320 it ( 'should handle Hub event signInWithRedirect' , async ( ) => {
153321 // Mock session and user for a successful signInWithRedirect Hub event
154322 const mockSession = createTokenMocks ( ) ; // Create valid mock tokens
@@ -174,16 +342,18 @@ describe('AuthProvider', () => {
174342
175343 // Simulate the Hub event "signInWithRedirect"
176344 act ( ( ) => {
345+ // Simulate a successful Hub event for signInWithRedirect
177346 hubCallback ! ( { payload : { event : 'signInWithRedirect' } } ) ;
178347 } ) ;
179348
180349 // Wait for the context state to update and verify changes
181350 await waitFor ( ( ) => {
351+ // Assert that the user is signed in after the Hub event
182352 expect ( screen . getByTestId ( 'isSignedIn' ) . textContent ) . toBe ( 'true' ) ; // User is signed in
183353 expect ( screen . getByTestId ( 'user' ) . textContent ) . toBe ( 'UserPresent' ) ; // User object is present
184354 } ) ;
185355 } ) ;
186-
356+
187357 it ( 'should handle Hub event signInWithRedirect_failure' , async ( ) => {
188358 // Render the AuthProvider with a TestConsumer to observe context changes
189359 await renderWithProvider ( ) ;
@@ -200,37 +370,33 @@ describe('AuthProvider', () => {
200370
201371 // Wait for the context state to update and verify changes
202372 await waitFor ( ( ) => {
373+ // Assert that an error is set after the Hub event failure
203374 expect ( screen . getByTestId ( 'error' ) . textContent ) . toBe (
204375 'An error has occurred during the OAuth flow.' // Error state is updated
205376 ) ;
206377 } ) ;
207378 } ) ;
208379
209- it ( 'should handle expired tokens' , async ( ) => {
210- // Create mock expired tokens
211- const expiredTokens = {
212- tokens : {
213- idToken : {
214- toString : ( ) => `header.${ btoa ( JSON . stringify ( { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ) ) } .signature` ,
215- payload : { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ,
216- } ,
217- accessToken : {
218- toString : ( ) => `header.${ btoa ( JSON . stringify ( { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ) ) } .signature` ,
219- payload : { exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 } ,
220- } ,
221- } ,
222- } ;
380+ it ( 'should handle Hub event signedOut' , async ( ) => {
381+ // Simulate a Hub event for user sign-out
382+ await renderWithProvider ( ) ;
223383
224- // Render with expired tokens
225- await renderWithProvider ( { sessionMock : expiredTokens } ) ;
384+ if ( ! hubCallback ) {
385+ throw new Error ( 'hubCallback is not initialized' ) ;
386+ }
387+
388+ act ( ( ) => {
389+ hubCallback ! ( { payload : { event : 'signedOut' } } ) ;
390+ } ) ;
226391
227392 await waitFor ( ( ) => {
228- // Verify signed-in state is false and user is cleared
393+ // Assert that the user is signed out and state is cleared
229394 expect ( screen . getByTestId ( 'isSignedIn' ) . textContent ) . toBe ( 'false' ) ;
230395 expect ( screen . getByTestId ( 'user' ) . textContent ) . toBe ( '' ) ;
231396 } ) ;
232397 } ) ;
233398
399+ // Auth Functions
234400 it ( 'should provide cognitoSignIn and cognitoSignOut functions' , async ( ) => {
235401 let contextValue : any ;
236402
0 commit comments