AuthProvider.test.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  2. import { screen, act, fireEvent } from '@testing-library/react';
  3. import { AuthProvider, useAuth } from '../../src/hooks/AuthProvider';
  4. import React from 'react';
  5. import { renderWithProviders } from '../test-utils';
  6. // Mock axios
  7. vi.mock('axios', () => ({
  8. default: {
  9. defaults: {
  10. headers: {
  11. common: {},
  12. },
  13. },
  14. },
  15. }));
  16. // Mock auth client
  17. vi.mock('../../src/api/authClient', () => ({
  18. authClient: {
  19. login: {
  20. $post: vi.fn(),
  21. },
  22. logout: {
  23. $post: vi.fn(),
  24. },
  25. me: {
  26. $get: vi.fn(),
  27. },
  28. },
  29. }));
  30. // Mock localStorage
  31. const localStorageMock = {
  32. getItem: vi.fn(),
  33. setItem: vi.fn(),
  34. removeItem: vi.fn(),
  35. clear: vi.fn(),
  36. };
  37. Object.defineProperty(window, 'localStorage', {
  38. value: localStorageMock,
  39. });
  40. // Test component that uses useAuth
  41. const TestComponent = () => {
  42. const auth = useAuth();
  43. return (
  44. <div>
  45. <div data-testid="user">{auth.user ? 'authenticated' : 'unauthenticated'}</div>
  46. <div data-testid="isAuthenticated">{auth.isAuthenticated ? 'true' : 'false'}</div>
  47. <div data-testid="isLoading">{auth.isLoading ? 'true' : 'false'}</div>
  48. <div data-testid="tenantId">{auth.tenantId || 'no-tenant'}</div>
  49. <button onClick={() => auth.login('test', 'password')}>Login</button>
  50. <button onClick={() => auth.login('test', 'password', 1)}>Login with Tenant</button>
  51. <button onClick={() => auth.setTenantId(2)}>Set Tenant</button>
  52. <button onClick={() => auth.logout()}>Logout</button>
  53. </div>
  54. );
  55. };
  56. describe('AuthProvider', () => {
  57. beforeEach(() => {
  58. vi.clearAllMocks();
  59. localStorageMock.getItem.mockReturnValue(null);
  60. });
  61. afterEach(() => {
  62. vi.restoreAllMocks();
  63. });
  64. it('应该提供认证上下文', () => {
  65. renderWithProviders(
  66. <AuthProvider>
  67. <TestComponent />
  68. </AuthProvider>
  69. );
  70. expect(screen.getByTestId('user')).toHaveTextContent('unauthenticated');
  71. expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('false');
  72. });
  73. it('应该在没有AuthProvider时抛出错误', () => {
  74. // 抑制控制台错误输出
  75. const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
  76. expect(() => {
  77. renderWithProviders(<TestComponent />);
  78. }).toThrow('useAuth必须在AuthProvider内部使用');
  79. consoleError.mockRestore();
  80. });
  81. it('应该处理登录成功', async () => {
  82. const mockLoginResponse = {
  83. status: 200,
  84. json: vi.fn().mockResolvedValue({
  85. token: 'test-token',
  86. user: { id: 1, username: 'test', email: 'test@example.com' },
  87. }),
  88. };
  89. const { authClient } = await import('../../src/api/authClient');
  90. (authClient.login.$post as any).mockResolvedValue(mockLoginResponse);
  91. renderWithProviders(
  92. <AuthProvider>
  93. <TestComponent />
  94. </AuthProvider>
  95. );
  96. const loginButton = screen.getByText('Login');
  97. await act(async () => {
  98. fireEvent.click(loginButton);
  99. });
  100. expect(authClient.login.$post).toHaveBeenCalledWith({
  101. json: {
  102. username: 'test',
  103. password: 'password',
  104. },
  105. });
  106. });
  107. it('应该处理登录失败', async () => {
  108. const mockLoginResponse = {
  109. status: 401,
  110. json: vi.fn().mockResolvedValue({
  111. message: 'Invalid credentials',
  112. }),
  113. };
  114. const { authClient } = await import('../../src/api/authClient');
  115. (authClient.login.$post as any).mockResolvedValue(mockLoginResponse);
  116. renderWithProviders(
  117. <AuthProvider>
  118. <TestComponent />
  119. </AuthProvider>
  120. );
  121. const loginButton = screen.getByText('Login');
  122. await act(async () => {
  123. fireEvent.click(loginButton);
  124. });
  125. expect(authClient.login.$post).toHaveBeenCalledWith({
  126. json: {
  127. username: 'test',
  128. password: 'password',
  129. },
  130. });
  131. });
  132. it('应该处理登出', async () => {
  133. const { authClient } = await import('../../src/api/authClient');
  134. (authClient.logout.$post as any).mockResolvedValue({});
  135. localStorageMock.getItem.mockReturnValue('test-token');
  136. renderWithProviders(
  137. <AuthProvider>
  138. <TestComponent />
  139. </AuthProvider>
  140. );
  141. const logoutButton = screen.getByText('Logout');
  142. await act(async () => {
  143. fireEvent.click(logoutButton);
  144. });
  145. expect(authClient.logout.$post).toHaveBeenCalled();
  146. expect(localStorageMock.removeItem).toHaveBeenCalledWith('token');
  147. });
  148. it('应该支持租户ID设置', () => {
  149. renderWithProviders(
  150. <AuthProvider>
  151. <TestComponent />
  152. </AuthProvider>
  153. );
  154. expect(screen.getByTestId('tenantId')).toHaveTextContent('no-tenant');
  155. const setTenantButton = screen.getByText('Set Tenant');
  156. fireEvent.click(setTenantButton);
  157. expect(screen.getByTestId('tenantId')).toHaveTextContent('2');
  158. });
  159. it('应该支持带租户ID的登录', async () => {
  160. const mockLoginResponse = {
  161. status: 200,
  162. json: vi.fn().mockResolvedValue({
  163. token: 'test-token',
  164. user: { id: 1, username: 'test', email: 'test@example.com', tenantId: 1 },
  165. }),
  166. };
  167. const { authClient } = await import('../../src/api/authClient');
  168. (authClient.login.$post as any).mockResolvedValue(mockLoginResponse);
  169. renderWithProviders(
  170. <AuthProvider>
  171. <TestComponent />
  172. </AuthProvider>
  173. );
  174. const loginWithTenantButton = screen.getByText('Login with Tenant');
  175. await act(async () => {
  176. fireEvent.click(loginWithTenantButton);
  177. });
  178. expect(authClient.login.$post).toHaveBeenCalledWith({
  179. json: {
  180. username: 'test',
  181. password: 'password',
  182. },
  183. header: {
  184. 'X-Tenant-Id': '1',
  185. },
  186. });
  187. });
  188. });