|
1 | 1 | import { |
2 | 2 | Auth0Client, ConnectAccountRedirectResult, |
3 | 3 | GetTokenSilentlyVerboseResponse, |
4 | | - ResponseType |
| 4 | + ResponseType, |
| 5 | + RefreshTokenMode |
5 | 6 | } from '@auth0/auth0-spa-js'; |
6 | 7 | import '@testing-library/jest-dom'; |
7 | 8 | import { act, render, renderHook, screen, waitFor } from '@testing-library/react'; |
@@ -61,6 +62,33 @@ describe('Auth0Provider', () => { |
61 | 62 | }); |
62 | 63 | }); |
63 | 64 |
|
| 65 | + it('should forward online-access options to Auth0Client', async () => { |
| 66 | + const opts = { |
| 67 | + clientId: 'foo', |
| 68 | + domain: 'bar', |
| 69 | + useRefreshTokens: true, |
| 70 | + useDpop: true, |
| 71 | + useMrrt: true, |
| 72 | + refreshTokenMode: RefreshTokenMode.Online, |
| 73 | + }; |
| 74 | + const wrapper = createWrapper(opts); |
| 75 | + renderHook(() => useContext(Auth0Context), { |
| 76 | + wrapper, |
| 77 | + }); |
| 78 | + await waitFor(() => { |
| 79 | + expect(Auth0Client).toHaveBeenCalledWith( |
| 80 | + expect.objectContaining({ |
| 81 | + clientId: 'foo', |
| 82 | + domain: 'bar', |
| 83 | + useRefreshTokens: true, |
| 84 | + useDpop: true, |
| 85 | + useMrrt: true, |
| 86 | + refreshTokenMode: 'online', |
| 87 | + }) |
| 88 | + ); |
| 89 | + }); |
| 90 | + }); |
| 91 | + |
64 | 92 | it('should support redirectUri', async () => { |
65 | 93 | const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined); |
66 | 94 | const opts = { |
@@ -603,6 +631,107 @@ describe('Auth0Provider', () => { |
603 | 631 | }); |
604 | 632 | }); |
605 | 633 |
|
| 634 | + it('should provide a revokeRefreshToken method', async () => { |
| 635 | + const user = { name: '__test_user__' }; |
| 636 | + clientMock.getUser.mockResolvedValue(user); |
| 637 | + const wrapper = createWrapper(); |
| 638 | + const { result } = renderHook( |
| 639 | + () => useContext(Auth0Context), |
| 640 | + { wrapper } |
| 641 | + ); |
| 642 | + await waitFor(() => { |
| 643 | + expect(result.current.revokeRefreshToken).toBeInstanceOf(Function); |
| 644 | + expect(result.current.isAuthenticated).toBe(true); |
| 645 | + }); |
| 646 | + await act(async () => { |
| 647 | + await result.current.revokeRefreshToken(); |
| 648 | + }); |
| 649 | + expect(clientMock.revokeRefreshToken).toHaveBeenCalled(); |
| 650 | + }); |
| 651 | + |
| 652 | + it('should forward options to revokeRefreshToken', async () => { |
| 653 | + const user = { name: '__test_user__' }; |
| 654 | + clientMock.getUser.mockResolvedValue(user); |
| 655 | + const wrapper = createWrapper(); |
| 656 | + const { result } = renderHook( |
| 657 | + () => useContext(Auth0Context), |
| 658 | + { wrapper } |
| 659 | + ); |
| 660 | + await waitFor(() => { |
| 661 | + expect(result.current.isAuthenticated).toBe(true); |
| 662 | + }); |
| 663 | + await act(async () => { |
| 664 | + await result.current.revokeRefreshToken({ audience: 'https://api.example.com' }); |
| 665 | + }); |
| 666 | + expect(clientMock.revokeRefreshToken).toHaveBeenCalledWith({ |
| 667 | + audience: 'https://api.example.com', |
| 668 | + }); |
| 669 | + }); |
| 670 | + |
| 671 | + it('should reflect cleared session state after revokeRefreshToken in online mode', async () => { |
| 672 | + // In online mode, revokeRefreshToken() clears the entire local session server-side. |
| 673 | + // getUser() reflects this by resolving to undefined afterward. |
| 674 | + const user = { name: '__test_user__' }; |
| 675 | + clientMock.getUser.mockResolvedValueOnce(user); |
| 676 | + const wrapper = createWrapper(); |
| 677 | + const { result } = renderHook( |
| 678 | + () => useContext(Auth0Context), |
| 679 | + { wrapper } |
| 680 | + ); |
| 681 | + await waitFor(() => { |
| 682 | + expect(result.current.isAuthenticated).toBe(true); |
| 683 | + }); |
| 684 | + clientMock.getUser.mockResolvedValueOnce(undefined); |
| 685 | + await act(async () => { |
| 686 | + await result.current.revokeRefreshToken(); |
| 687 | + }); |
| 688 | + await waitFor(() => { |
| 689 | + expect(result.current.isAuthenticated).toBe(false); |
| 690 | + expect(result.current.user).toBeUndefined(); |
| 691 | + }); |
| 692 | + }); |
| 693 | + |
| 694 | + it('should not change session state after revokeRefreshToken in offline mode', async () => { |
| 695 | + // In offline mode, revokeRefreshToken() only invalidates the refresh token; |
| 696 | + // the cached user/access token remain valid until they expire. |
| 697 | + const user = { name: '__test_user__' }; |
| 698 | + clientMock.getUser.mockResolvedValue(user); |
| 699 | + const wrapper = createWrapper(); |
| 700 | + const { result } = renderHook( |
| 701 | + () => useContext(Auth0Context), |
| 702 | + { wrapper } |
| 703 | + ); |
| 704 | + await waitFor(() => { |
| 705 | + expect(result.current.isAuthenticated).toBe(true); |
| 706 | + }); |
| 707 | + await act(async () => { |
| 708 | + await result.current.revokeRefreshToken(); |
| 709 | + }); |
| 710 | + expect(result.current.isAuthenticated).toBe(true); |
| 711 | + expect(result.current.user).toBe(user); |
| 712 | + }); |
| 713 | + |
| 714 | + it('should rethrow errors from revokeRefreshToken', async () => { |
| 715 | + const user = { name: '__test_user__' }; |
| 716 | + clientMock.getUser.mockResolvedValue(user); |
| 717 | + clientMock.revokeRefreshToken.mockRejectedValueOnce( |
| 718 | + new Error('The token has been revoked') |
| 719 | + ); |
| 720 | + const wrapper = createWrapper(); |
| 721 | + const { result } = renderHook( |
| 722 | + () => useContext(Auth0Context), |
| 723 | + { wrapper } |
| 724 | + ); |
| 725 | + await waitFor(() => { |
| 726 | + expect(result.current.isAuthenticated).toBe(true); |
| 727 | + }); |
| 728 | + await expect( |
| 729 | + act(async () => { |
| 730 | + await result.current.revokeRefreshToken(); |
| 731 | + }) |
| 732 | + ).rejects.toThrow('The token has been revoked'); |
| 733 | + }); |
| 734 | + |
606 | 735 | it('should provide a getAccessTokenSilently method', async () => { |
607 | 736 | clientMock.getTokenSilently.mockResolvedValue('token'); |
608 | 737 | const wrapper = createWrapper(); |
|
0 commit comments