userManagement.integration.test.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import { describe, it, expect, vi, beforeEach } from 'vitest';
  2. import { render, screen, fireEvent, waitFor } from '@testing-library/react';
  3. import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
  4. import { UserManagement } from '../../src/components/UserManagement';
  5. import { userClient, userClientManager } from '../../src/api/userClient';
  6. // 完整的mock响应对象
  7. const createMockResponse = (status: number, data?: any) => ({
  8. status,
  9. ok: status >= 200 && status < 300,
  10. body: null,
  11. bodyUsed: false,
  12. statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
  13. headers: new Headers(),
  14. url: '',
  15. redirected: false,
  16. type: 'basic' as ResponseType,
  17. json: async () => data || {},
  18. text: async () => '',
  19. blob: async () => new Blob(),
  20. arrayBuffer: async () => new ArrayBuffer(0),
  21. formData: async () => new FormData(),
  22. clone: function() { return this; }
  23. });
  24. // Mock API client
  25. vi.mock('../../src/api/userClient', () => {
  26. const mockUserClient = {
  27. index: {
  28. $get: vi.fn(() => Promise.resolve({ status: 200, body: null })),
  29. $post: vi.fn(() => Promise.resolve({ status: 201, body: null })),
  30. },
  31. ':id': {
  32. $put: vi.fn(() => Promise.resolve({ status: 200, body: null })),
  33. $delete: vi.fn(() => Promise.resolve({ status: 204, body: null })),
  34. },
  35. };
  36. const mockUserClientManager = {
  37. get: vi.fn(() => mockUserClient),
  38. };
  39. return {
  40. userClientManager: mockUserClientManager,
  41. userClient: mockUserClient,
  42. };
  43. });
  44. // Mock toast
  45. vi.mock('sonner', () => ({
  46. toast: {
  47. success: vi.fn(() => {}),
  48. error: vi.fn(() => {}),
  49. },
  50. }));
  51. const createTestQueryClient = () =>
  52. new QueryClient({
  53. defaultOptions: {
  54. queries: {
  55. retry: false,
  56. },
  57. },
  58. });
  59. const renderWithProviders = (component: React.ReactElement) => {
  60. const queryClient = createTestQueryClient();
  61. return render(
  62. <QueryClientProvider client={queryClient}>
  63. {component as any}
  64. </QueryClientProvider>
  65. );
  66. };
  67. describe('用户管理集成测试', () => {
  68. beforeEach(() => {
  69. vi.clearAllMocks();
  70. });
  71. it('应该完成完整的用户CRUD流程', async () => {
  72. const mockUsers = {
  73. data: [
  74. {
  75. id: 1,
  76. username: 'existinguser',
  77. nickname: 'Existing User',
  78. email: 'existing@example.com',
  79. phone: '1234567890',
  80. name: 'Existing Name',
  81. isDisabled: 0,
  82. createdAt: '2024-01-01T00:00:00Z',
  83. roles: [{ id: 1, name: 'admin' }],
  84. avatarFile: null,
  85. },
  86. ],
  87. pagination: {
  88. total: 1,
  89. page: 1,
  90. pageSize: 10,
  91. },
  92. };
  93. const { toast } = await import('sonner');
  94. // Mock initial user list
  95. (userClientManager.get().index.$get as any).mockResolvedValue(createMockResponse(200, mockUsers));
  96. renderWithProviders(<UserManagement />);
  97. // Wait for initial data to load
  98. await waitFor(() => {
  99. expect(screen.getByText('existinguser')).toBeInTheDocument();
  100. });
  101. // Test create user
  102. const createButton = screen.getByTestId('create-user-button');
  103. fireEvent.click(createButton);
  104. // Fill create form
  105. const usernameInput = screen.getByPlaceholderText('请输入用户名');
  106. const passwordInput = screen.getByPlaceholderText('请输入密码');
  107. const emailInput = screen.getByPlaceholderText('请输入邮箱');
  108. fireEvent.change(usernameInput, { target: { value: 'newuser' } });
  109. fireEvent.change(passwordInput, { target: { value: 'password123' } });
  110. fireEvent.change(emailInput, { target: { value: 'new@example.com' } });
  111. // Mock successful creation
  112. (userClient.index.$post as any).mockResolvedValue(createMockResponse(201, { id: 2, username: 'newuser' }));
  113. const submitButton = screen.getByTestId('create-user-submit-button');
  114. fireEvent.click(submitButton);
  115. await waitFor(() => {
  116. expect(userClient.index.$post).toHaveBeenCalledWith({
  117. json: {
  118. username: 'newuser',
  119. password: 'password123',
  120. email: 'new@example.com',
  121. nickname: null,
  122. phone: null,
  123. name: null,
  124. companyId: null,
  125. isDisabled: 0,
  126. },
  127. });
  128. expect(toast.success).toHaveBeenCalledWith('用户创建成功');
  129. });
  130. // Test edit user
  131. const editButtons = screen.getAllByRole('button', { name: '编辑用户' });
  132. fireEvent.click(editButtons[0]);
  133. // Verify edit form is populated
  134. await waitFor(() => {
  135. expect(screen.getByDisplayValue('existinguser')).toBeInTheDocument();
  136. });
  137. // Update user
  138. const updateUsernameInput = screen.getByDisplayValue('existinguser');
  139. fireEvent.change(updateUsernameInput, { target: { value: 'updateduser' } });
  140. // Mock successful update
  141. (userClient[':id']['$put'] as any).mockResolvedValue(createMockResponse(200));
  142. const updateButton = screen.getByText('更新用户');
  143. fireEvent.click(updateButton);
  144. await waitFor(() => {
  145. expect(userClient[':id']['$put']).toHaveBeenCalledWith({
  146. param: { id: 1 },
  147. json: {
  148. username: 'updateduser',
  149. nickname: 'Existing User',
  150. email: 'existing@example.com',
  151. phone: '1234567890',
  152. name: 'Existing Name',
  153. password: undefined,
  154. avatarFileId: undefined,
  155. isDisabled: 0,
  156. },
  157. });
  158. expect(toast.success).toHaveBeenCalledWith('用户更新成功');
  159. });
  160. // Test delete user
  161. const deleteButtons = screen.getAllByRole('button', { name: '删除用户' });
  162. fireEvent.click(deleteButtons[0]);
  163. // Confirm deletion
  164. expect(screen.getByText('确认删除')).toBeInTheDocument();
  165. // Mock successful deletion
  166. (userClient[':id']['$delete'] as any).mockResolvedValue({
  167. status: 204,
  168. });
  169. const confirmDeleteButton = screen.getByText('删除');
  170. fireEvent.click(confirmDeleteButton);
  171. await waitFor(() => {
  172. expect(userClient[':id']['$delete']).toHaveBeenCalledWith({
  173. param: { id: 1 },
  174. });
  175. expect(toast.success).toHaveBeenCalledWith('用户删除成功');
  176. });
  177. });
  178. it('应该优雅处理API错误', async () => {
  179. const { userClient } = await import('../../src/api/userClient');
  180. const { toast } = await import('sonner');
  181. // Mock API error
  182. (userClientManager.get().index.$get as any).mockRejectedValue(new Error('API Error'));
  183. renderWithProviders(<UserManagement />);
  184. // Should handle error without crashing
  185. await waitFor(() => {
  186. expect(screen.getByText('用户管理')).toBeInTheDocument();
  187. });
  188. // Test create user error
  189. const createButton = screen.getByText('创建用户');
  190. fireEvent.click(createButton);
  191. const usernameInput = screen.getByPlaceholderText('请输入用户名');
  192. const passwordInput = screen.getByPlaceholderText('请输入密码');
  193. fireEvent.change(usernameInput, { target: { value: 'testuser' } });
  194. fireEvent.change(passwordInput, { target: { value: 'password' } });
  195. // Mock creation error
  196. (userClient.index.$post as any).mockRejectedValue(new Error('Creation failed'));
  197. const submitButton = screen.getByTestId('create-user-submit-button');
  198. fireEvent.click(submitButton);
  199. await waitFor(() => {
  200. expect(toast.error).toHaveBeenCalledWith('创建失败,请重试');
  201. });
  202. });
  203. it('应该处理搜索和过滤器集成', async () => {
  204. const { userClient } = await import('../../src/api/userClient');
  205. const mockUsers = {
  206. data: [],
  207. pagination: { total: 0, page: 1, pageSize: 10 },
  208. };
  209. (userClientManager.get().index.$get as any).mockResolvedValue(createMockResponse(200, mockUsers));
  210. renderWithProviders(<UserManagement />);
  211. // Test search
  212. const searchInput = screen.getByPlaceholderText('搜索用户名、昵称或邮箱...');
  213. fireEvent.change(searchInput, { target: { value: 'searchterm' } });
  214. await waitFor(() => {
  215. expect(userClientManager.get().index.$get).toHaveBeenCalledWith({
  216. query: {
  217. page: 1,
  218. pageSize: 10,
  219. keyword: 'searchterm',
  220. filters: undefined,
  221. },
  222. });
  223. });
  224. // Test filter
  225. const filterButton = screen.getByTestId('advanced-filter-button');
  226. fireEvent.click(filterButton);
  227. // Wait for filter panel to appear
  228. await waitFor(() => {
  229. expect(screen.getByTestId('status-filter-trigger')).toBeInTheDocument();
  230. }, { timeout: 2000 });
  231. // Apply status filter
  232. const statusSelect = screen.getByTestId('status-filter-trigger');
  233. fireEvent.click(statusSelect);
  234. const enabledOption = screen.getByTestId('status-enabled-option');
  235. fireEvent.click(enabledOption);
  236. await waitFor(() => {
  237. expect(userClientManager.get().index.$get).toHaveBeenCalledWith({
  238. query: {
  239. page: 1,
  240. pageSize: 10,
  241. keyword: 'searchterm',
  242. filters: expect.stringContaining('isDisabled'),
  243. },
  244. });
  245. });
  246. });
  247. });