system-config-management.integration.test.tsx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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 { SystemConfigManagement } from '../../src/components/SystemConfigManagement';
  5. import { systemConfigClientManager } from '../../src/api/systemConfigClient';
  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/systemConfigClient', () => {
  26. const mockSystemConfigClient = {
  27. index: {
  28. $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
  29. data: [
  30. {
  31. id: 1,
  32. configKey: 'wx.mini.app.id',
  33. configValue: 'wx1234567890',
  34. description: '微信小程序AppID',
  35. tenantId: 1,
  36. createdBy: 1,
  37. updatedBy: 1,
  38. createdAt: '2024-01-01T00:00:00Z',
  39. updatedAt: '2024-01-01T00:00:00Z'
  40. }
  41. ],
  42. pagination: {
  43. page: 1,
  44. pageSize: 10,
  45. total: 1,
  46. totalPages: 1
  47. }
  48. }))),
  49. $post: vi.fn(() => Promise.resolve(createMockResponse(201, { id: 2, configKey: 'wx.mini.app.secret', configValue: 'secret123' }))),
  50. },
  51. ':id': {
  52. $put: vi.fn(() => Promise.resolve(createMockResponse(200, { id: 1, configKey: 'wx.mini.app.id', configValue: 'updated_value' }))),
  53. $delete: vi.fn(() => Promise.resolve(createMockResponse(204))),
  54. },
  55. };
  56. const mockSystemConfigClientManager = {
  57. get: vi.fn(() => mockSystemConfigClient),
  58. };
  59. return {
  60. systemConfigClientManager: mockSystemConfigClientManager,
  61. systemConfigClient: mockSystemConfigClient,
  62. };
  63. });
  64. // Mock toast
  65. vi.mock('sonner', () => ({
  66. toast: {
  67. success: vi.fn(() => {}),
  68. error: vi.fn(() => {}),
  69. },
  70. }));
  71. const createTestQueryClient = () =>
  72. new QueryClient({
  73. defaultOptions: {
  74. queries: {
  75. retry: false,
  76. },
  77. },
  78. });
  79. const renderWithProviders = (component: React.ReactElement) => {
  80. const queryClient = createTestQueryClient();
  81. return render(
  82. <QueryClientProvider client={queryClient}>
  83. {component as any}
  84. </QueryClientProvider>
  85. );
  86. };
  87. describe('系统配置管理集成测试', () => {
  88. beforeEach(() => {
  89. vi.clearAllMocks();
  90. });
  91. it('应该完成完整的系统配置CRUD流程', async () => {
  92. const mockSystemConfigs = {
  93. data: [
  94. {
  95. id: 1,
  96. configKey: 'wx.mini.app.id',
  97. configValue: 'wx1234567890',
  98. description: '微信小程序AppID',
  99. tenantId: 1,
  100. createdBy: 1,
  101. updatedBy: 1,
  102. createdAt: '2024-01-01T00:00:00Z',
  103. updatedAt: '2024-01-01T00:00:00Z'
  104. },
  105. ],
  106. pagination: {
  107. total: 1,
  108. page: 1,
  109. pageSize: 10,
  110. },
  111. };
  112. const { toast } = await import('sonner');
  113. // Mock initial system config list
  114. const client = systemConfigClientManager.get();
  115. (client.index.$get as any).mockResolvedValue({
  116. ...createMockResponse(200),
  117. json: async () => mockSystemConfigs
  118. });
  119. renderWithProviders(<SystemConfigManagement />);
  120. // Wait for initial data to load
  121. await waitFor(() => {
  122. expect(screen.getByText('wx.mini.app.id')).toBeInTheDocument();
  123. });
  124. // Test create system config
  125. const createButton = screen.getByText('创建配置');
  126. fireEvent.click(createButton);
  127. // Fill create form
  128. const configKeyInput = screen.getByTestId('config-key-input');
  129. const configValueInput = screen.getByTestId('config-value-input');
  130. const descriptionInput = screen.getByTestId('description-input');
  131. fireEvent.change(configKeyInput, { target: { value: 'wx.mini.app.secret' } });
  132. fireEvent.change(configValueInput, { target: { value: 'secret123' } });
  133. fireEvent.change(descriptionInput, { target: { value: '微信小程序AppSecret' } });
  134. // Mock successful creation
  135. (client.index.$post as any).mockResolvedValue(createMockResponse(201, {
  136. id: 2,
  137. configKey: 'wx.mini.app.secret',
  138. configValue: 'secret123'
  139. }));
  140. const submitButton = screen.getByTestId('create-submit-button');
  141. fireEvent.click(submitButton);
  142. await waitFor(() => {
  143. expect(client.index.$post).toHaveBeenCalledWith({
  144. json: {
  145. configKey: 'wx.mini.app.secret',
  146. configValue: 'secret123',
  147. description: '微信小程序AppSecret'
  148. },
  149. });
  150. expect(toast.success).toHaveBeenCalledWith('系统配置创建成功');
  151. });
  152. // Test edit system config
  153. const editButton = screen.getByTestId('edit-button-1');
  154. fireEvent.click(editButton);
  155. // Verify edit form is populated
  156. await waitFor(() => {
  157. expect(screen.getByDisplayValue('wx.mini.app.id')).toBeInTheDocument();
  158. });
  159. // Update system config
  160. const updateConfigValueInput = screen.getByDisplayValue('wx1234567890');
  161. fireEvent.change(updateConfigValueInput, { target: { value: 'updated_value' } });
  162. // Mock successful update
  163. (client[':id']['$put'] as any).mockResolvedValue(createMockResponse(200));
  164. const updateButton = screen.getByTestId('update-submit-button');
  165. fireEvent.click(updateButton);
  166. await waitFor(() => {
  167. expect(client[':id']['$put']).toHaveBeenCalledWith({
  168. param: { id: 1 },
  169. json: {
  170. configKey: 'wx.mini.app.id',
  171. configValue: 'updated_value',
  172. description: '微信小程序AppID'
  173. },
  174. });
  175. expect(toast.success).toHaveBeenCalledWith('系统配置更新成功');
  176. });
  177. // Test delete system config
  178. const deleteButton = screen.getByTestId('delete-button-1');
  179. fireEvent.click(deleteButton);
  180. // Confirm deletion
  181. expect(screen.getByText('确认删除')).toBeInTheDocument();
  182. // Mock successful deletion
  183. (client[':id']['$delete'] as any).mockResolvedValue({
  184. status: 204,
  185. ok: true,
  186. body: null,
  187. bodyUsed: false,
  188. statusText: 'No Content',
  189. headers: new Headers(),
  190. url: '',
  191. redirected: false,
  192. type: 'basic' as ResponseType,
  193. json: async () => ({}),
  194. text: async () => '',
  195. blob: async () => new Blob(),
  196. arrayBuffer: async () => new ArrayBuffer(0),
  197. formData: async () => new FormData(),
  198. clone: function() { return this; }
  199. });
  200. const confirmDeleteButton = screen.getByTestId('confirm-delete-button');
  201. fireEvent.click(confirmDeleteButton);
  202. await waitFor(() => {
  203. expect(client[':id']['$delete']).toHaveBeenCalledWith({
  204. param: { id: 1 },
  205. });
  206. expect(toast.success).toHaveBeenCalledWith('系统配置删除成功');
  207. });
  208. });
  209. it('应该优雅处理API错误', async () => {
  210. const client = systemConfigClientManager.get();
  211. const { toast } = await import('sonner');
  212. // Mock API error
  213. (client.index.$get as any).mockRejectedValue(new Error('API Error'));
  214. renderWithProviders(<SystemConfigManagement />);
  215. // Should handle error without crashing
  216. await waitFor(() => {
  217. expect(screen.getByText('系统配置管理')).toBeInTheDocument();
  218. });
  219. // Test create system config error
  220. const createButton = screen.getByText('创建配置');
  221. fireEvent.click(createButton);
  222. const configKeyInput = screen.getByTestId('config-key-input');
  223. const configValueInput = screen.getByTestId('config-value-input');
  224. fireEvent.change(configKeyInput, { target: { value: 'wx.mini.app.id' } });
  225. fireEvent.change(configValueInput, { target: { value: 'test_value' } });
  226. // Mock creation error
  227. (client.index.$post as any).mockRejectedValue(new Error('Creation failed'));
  228. const submitButton = screen.getByTestId('create-submit-button');
  229. fireEvent.click(submitButton);
  230. await waitFor(() => {
  231. expect(toast.error).toHaveBeenCalledWith('Creation failed');
  232. });
  233. });
  234. it('应该处理搜索功能', async () => {
  235. const client = systemConfigClientManager.get();
  236. const mockSystemConfigs = {
  237. data: [],
  238. pagination: { total: 0, page: 1, pageSize: 10 },
  239. };
  240. (client.index.$get as any).mockResolvedValue(createMockResponse(200, mockSystemConfigs));
  241. renderWithProviders(<SystemConfigManagement />);
  242. // Test search
  243. const searchInput = screen.getByTestId('search-input');
  244. fireEvent.change(searchInput, { target: { value: '微信' } });
  245. const searchButton = screen.getByText('搜索');
  246. fireEvent.click(searchButton);
  247. await waitFor(() => {
  248. expect(client.index.$get).toHaveBeenCalledWith({
  249. query: {
  250. page: 1,
  251. pageSize: 10,
  252. keyword: '微信',
  253. },
  254. });
  255. });
  256. });
  257. });