users.integration.test.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
  2. import { OpenAPIHono } from '@hono/zod-openapi';
  3. import { createApiClient, ApiClient } from '../../__test_utils__/api-client';
  4. import { createMockDataSource } from '../../__test_utils__/test-db';
  5. // import '../../utils/generic-crud.service'
  6. // Mock 数据源
  7. vi.mock('../../../data-source', () => {
  8. const mockDataSource = createMockDataSource();
  9. return {
  10. AppDataSource: mockDataSource
  11. };
  12. });
  13. // Mock 用户服务
  14. vi.mock('../../modules/users/user.service', () => ({
  15. UserService: vi.fn().mockImplementation(() => ({
  16. getUserById: vi.fn().mockResolvedValue(null),
  17. createUser: vi.fn().mockResolvedValue({ id: 1, username: 'testuser' }),
  18. updateUser: vi.fn().mockResolvedValue({ affected: 1 }),
  19. deleteUser: vi.fn().mockResolvedValue({ affected: 1 })
  20. }))
  21. }));
  22. // Mock 认证中间件 - 使用工厂函数确保每个测试有独立的mock实例
  23. vi.mock('../../middleware/auth.middleware', () => ({
  24. authMiddleware: vi.fn().mockImplementation((_c, next) => next())
  25. }));
  26. // Mock 通用CRUD服务
  27. const mockCrudService = {
  28. getList: vi.fn(),
  29. getById: vi.fn(),
  30. create: vi.fn(),
  31. update: vi.fn(),
  32. delete: vi.fn()
  33. };
  34. vi.mock('../../utils/generic-crud.service', () => ({
  35. GenericCrudService: vi.fn().mockImplementation(() => mockCrudService)
  36. }));
  37. describe('Users API Integration Tests', () => {
  38. let app: OpenAPIHono;
  39. let apiClient: ApiClient;
  40. beforeEach(async () => {
  41. vi.clearAllMocks();
  42. // 重置认证中间件mock
  43. const { authMiddleware } = await import('../../middleware/auth.middleware');
  44. vi.mocked(authMiddleware).mockImplementation((_c: any, next: any) => next());
  45. // 重置CRUD服务mock
  46. mockCrudService.getList.mockResolvedValue([[], 0]);
  47. mockCrudService.getById.mockResolvedValue(null);
  48. mockCrudService.create.mockResolvedValue({ id: 1, username: 'testuser' });
  49. mockCrudService.update.mockResolvedValue({ affected: 1 });
  50. mockCrudService.delete.mockResolvedValue({ affected: 1 });
  51. // 动态导入用户路由
  52. const userRoutes = await import('../users/index');
  53. // 使用导入的应用实例
  54. app = userRoutes.default;
  55. // 创建API客户端
  56. apiClient = createApiClient(app, {
  57. authToken: 'test-token-123'
  58. });
  59. });
  60. afterEach(() => {
  61. vi.resetAllMocks();
  62. });
  63. describe('GET /users', () => {
  64. it('应该返回用户列表和分页信息', async () => {
  65. const mockUsers = [
  66. {
  67. id: 1,
  68. username: 'user1',
  69. password: 'password123',
  70. phone: null,
  71. email: 'user1@example.com',
  72. nickname: null,
  73. name: null,
  74. avatar: null,
  75. isDisabled: 0,
  76. isDeleted: 0,
  77. roles: [],
  78. createdAt: new Date('2024-01-01T00:00:00.000Z'),
  79. updatedAt: new Date('2024-01-01T00:00:00.000Z')
  80. },
  81. {
  82. id: 2,
  83. username: 'user2',
  84. password: 'password123',
  85. phone: null,
  86. email: 'user2@example.com',
  87. nickname: null,
  88. name: null,
  89. avatar: null,
  90. isDisabled: 0,
  91. isDeleted: 0,
  92. roles: [],
  93. createdAt: new Date('2024-01-02T00:00:00.000Z'),
  94. updatedAt: new Date('2024-01-02T00:00:00.000Z')
  95. }
  96. ];
  97. mockCrudService.getList.mockResolvedValue([mockUsers, 2]);
  98. const response = await apiClient.get('/?page=1&pageSize=10');
  99. if (response.status !== 200) {
  100. // 使用 process.stderr.write 绕过 console mock
  101. process.stderr.write(`Response status: ${response.status}\n`);
  102. process.stderr.write(`Response data: ${JSON.stringify(response.data, null, 2)}\n`);
  103. }
  104. expect(response.status).toBe(200);
  105. expect(response.data).toEqual({
  106. data: mockUsers.map(user => ({
  107. ...user,
  108. createdAt: user.createdAt.toISOString(),
  109. updatedAt: user.updatedAt.toISOString()
  110. })),
  111. pagination: {
  112. total: 2,
  113. current: 1,
  114. pageSize: 10
  115. }
  116. });
  117. });
  118. it('应该验证分页参数', async () => {
  119. mockCrudService.getList.mockResolvedValue([[], 0]);
  120. const response = await apiClient.get('/?page=0&pageSize=0');
  121. expect(response.status).toBe(400);
  122. expect(response.data).toMatchObject({
  123. success: false,
  124. error: expect.any(Object)
  125. });
  126. });
  127. it('应该支持关键词搜索', async () => {
  128. mockCrudService.getList.mockResolvedValue([[], 0]);
  129. const response = await apiClient.get('/?page=1&pageSize=10&keyword=admin');
  130. expect(response.status).toBe(200);
  131. expect(mockCrudService.getList).toHaveBeenCalledWith(
  132. 1,
  133. 10,
  134. 'admin',
  135. ['username', 'nickname', 'phone', 'email'],
  136. undefined,
  137. ['roles'],
  138. expect.any(Object),
  139. undefined
  140. );
  141. });
  142. });
  143. describe('GET /users/:id', () => {
  144. it('应该返回特定用户信息', async () => {
  145. const mockUser = { id: 1, username: 'testuser', email: 'test@example.com' };
  146. mockCrudService.getById.mockResolvedValue(mockUser);
  147. const response = await apiClient.get('/1');
  148. expect(response.status).toBe(200);
  149. expect(response.data).toEqual(mockUser);
  150. expect(mockCrudService.getById).toHaveBeenCalledWith(1, ['roles']);
  151. });
  152. it('应该在用户不存在时返回404', async () => {
  153. mockCrudService.getById.mockResolvedValue(null);
  154. const response = await apiClient.get('/999');
  155. expect(response.status).toBe(404);
  156. expect(response.data).toMatchObject({
  157. code: 404,
  158. message: expect.any(String)
  159. });
  160. });
  161. it('应该验证用户ID格式', async () => {
  162. const response = await apiClient.get('/invalid');
  163. expect(response.status).toBe(400);
  164. expect(response.data).toMatchObject({
  165. success: false,
  166. error: expect.any(Object)
  167. });
  168. });
  169. });
  170. describe('错误处理', () => {
  171. it('应该在服务错误时返回500状态码', async () => {
  172. mockCrudService.getList.mockRejectedValue(new Error('Database error'));
  173. const response = await apiClient.get('/?page=1&pageSize=10');
  174. expect(response.status).toBe(500);
  175. expect(response.data).toMatchObject({
  176. code: 500,
  177. message: 'Database error'
  178. });
  179. });
  180. it('应该在未知错误时返回通用错误消息', async () => {
  181. mockCrudService.getList.mockRejectedValue('Unknown error');
  182. const response = await apiClient.get('/?page=1&pageSize=10');
  183. expect(response.status).toBe(500);
  184. expect(response.data).toMatchObject({
  185. code: 500,
  186. message: '获取用户列表失败'
  187. });
  188. });
  189. });
  190. describe('认证和授权', () => {
  191. it('应该在缺少认证令牌时返回401', async () => {
  192. apiClient.clearAuthToken();
  193. const response = await apiClient.get('/');
  194. expect(response.status).toBe(401);
  195. expect(response.data).toMatchObject({
  196. code: 401,
  197. message: expect.any(String)
  198. });
  199. });
  200. it('应该在无效令牌时返回401', async () => {
  201. // 模拟认证中间件验证失败
  202. const { authMiddleware } = await import('../../middleware/auth.middleware');
  203. vi.mocked(authMiddleware).mockImplementation((c: any) => {
  204. return c.json({ error: 'Invalid token' }, 401);
  205. });
  206. const response = await apiClient.get('/');
  207. expect(response.status).toBe(401);
  208. expect(response.data).toEqual({ error: 'Invalid token' });
  209. });
  210. });
  211. });