users.integration.test.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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(async (c, next) => {
  45. // 模拟认证成功,设置用户信息
  46. c.set('user', { id: 1, username: 'testuser' } as any);
  47. await next();
  48. });
  49. // 重置CRUD服务mock
  50. mockCrudService.getList.mockResolvedValue([[], 0]);
  51. mockCrudService.getById.mockResolvedValue(null);
  52. mockCrudService.create.mockResolvedValue({ id: 1, username: 'testuser' });
  53. mockCrudService.update.mockResolvedValue({ affected: 1 });
  54. mockCrudService.delete.mockResolvedValue({ affected: 1 });
  55. // 动态导入用户路由
  56. const userRoutes = await import('../users/index');
  57. // 使用导入的应用实例
  58. app = userRoutes.default;
  59. // 创建API客户端
  60. apiClient = createApiClient(app, {
  61. authToken: 'test-token-123'
  62. });
  63. });
  64. afterEach(() => {
  65. vi.resetAllMocks();
  66. });
  67. describe('GET /users', () => {
  68. it('应该返回用户列表和分页信息', async () => {
  69. const mockUsers = [
  70. {
  71. id: 1,
  72. username: 'user1',
  73. password: 'password123',
  74. phone: null,
  75. email: 'user1@example.com',
  76. nickname: null,
  77. name: null,
  78. avatar: null,
  79. isDisabled: 0,
  80. isDeleted: 0,
  81. roles: [],
  82. createdAt: new Date('2024-01-01T00:00:00.000Z'),
  83. updatedAt: new Date('2024-01-01T00:00:00.000Z')
  84. },
  85. {
  86. id: 2,
  87. username: 'user2',
  88. password: 'password123',
  89. phone: null,
  90. email: 'user2@example.com',
  91. nickname: null,
  92. name: null,
  93. avatar: null,
  94. isDisabled: 0,
  95. isDeleted: 0,
  96. roles: [],
  97. createdAt: new Date('2024-01-02T00:00:00.000Z'),
  98. updatedAt: new Date('2024-01-02T00:00:00.000Z')
  99. }
  100. ];
  101. mockCrudService.getList.mockResolvedValue([mockUsers, 2]);
  102. const response = await apiClient.get('/?page=1&pageSize=10');
  103. if (response.status !== 200) {
  104. // 使用 process.stderr.write 绕过 console mock
  105. process.stderr.write(`Response status: ${response.status}\n`);
  106. process.stderr.write(`Response data: ${JSON.stringify(response.data, null, 2)}\n`);
  107. }
  108. expect(response.status).toBe(200);
  109. expect(response.data).toEqual({
  110. data: mockUsers.map(user => ({
  111. ...user,
  112. createdAt: user.createdAt.toISOString(),
  113. updatedAt: user.updatedAt.toISOString()
  114. })),
  115. pagination: {
  116. total: 2,
  117. current: 1,
  118. pageSize: 10
  119. }
  120. });
  121. });
  122. it('应该验证分页参数', async () => {
  123. mockCrudService.getList.mockResolvedValue([[], 0]);
  124. const response = await apiClient.get('/?page=0&pageSize=0');
  125. expect(response.status).toBe(400);
  126. expect(response.data).toMatchObject({
  127. success: false,
  128. error: expect.any(Object)
  129. });
  130. });
  131. it('应该支持关键词搜索', async () => {
  132. mockCrudService.getList.mockResolvedValue([[], 0]);
  133. const response = await apiClient.get('/?page=1&pageSize=10&keyword=admin');
  134. expect(response.status).toBe(200);
  135. expect(mockCrudService.getList).toHaveBeenCalledWith(
  136. 1,
  137. 10,
  138. 'admin',
  139. ['username', 'nickname', 'phone', 'email'],
  140. undefined,
  141. ['roles'],
  142. expect.any(Object),
  143. undefined
  144. );
  145. });
  146. });
  147. describe('GET /users/:id', () => {
  148. it('应该返回特定用户信息', async () => {
  149. const mockUser = {
  150. id: 1,
  151. username: 'testuser',
  152. password: 'password123',
  153. phone: null,
  154. email: 'test@example.com',
  155. nickname: null,
  156. name: null,
  157. avatar: null,
  158. isDisabled: 0,
  159. isDeleted: 0,
  160. roles: [],
  161. createdAt: new Date('2024-01-01T00:00:00.000Z'),
  162. updatedAt: new Date('2024-01-01T00:00:00.000Z')
  163. };
  164. mockCrudService.getById.mockResolvedValue(mockUser);
  165. const response = await apiClient.get('/1');
  166. expect(response.status).toBe(200);
  167. expect(response.data).toEqual({
  168. ...mockUser,
  169. createdAt: mockUser.createdAt.toISOString(),
  170. updatedAt: mockUser.updatedAt.toISOString()
  171. });
  172. expect(mockCrudService.getById).toHaveBeenCalledWith(1, ['roles']);
  173. });
  174. it('应该在用户不存在时返回404', async () => {
  175. mockCrudService.getById.mockResolvedValue(null);
  176. const response = await apiClient.get('/999');
  177. expect(response.status).toBe(404);
  178. expect(response.data).toMatchObject({
  179. code: 404,
  180. message: expect.any(String)
  181. });
  182. });
  183. it('应该验证用户ID格式', async () => {
  184. const response = await apiClient.get('/invalid');
  185. expect(response.status).toBe(400);
  186. expect(response.data).toMatchObject({
  187. success: false,
  188. error: expect.any(Object)
  189. });
  190. });
  191. });
  192. describe('错误处理', () => {
  193. it('应该在服务错误时返回500状态码', async () => {
  194. mockCrudService.getList.mockRejectedValue(new Error('Database error'));
  195. const response = await apiClient.get('/?page=1&pageSize=10');
  196. expect(response.status).toBe(500);
  197. expect(response.data).toMatchObject({
  198. code: 500,
  199. message: 'Database error'
  200. });
  201. });
  202. it('应该在未知错误时返回通用错误消息', async () => {
  203. mockCrudService.getList.mockRejectedValue('Unknown error');
  204. const response = await apiClient.get('/?page=1&pageSize=10');
  205. expect(response.status).toBe(500);
  206. expect(response.data).toMatchObject({
  207. code: 500,
  208. message: '获取列表失败'
  209. });
  210. });
  211. });
  212. describe('认证和授权', () => {
  213. it('应该在缺少认证令牌时返回401', async () => {
  214. // 临时覆盖认证中间件mock,模拟认证失败
  215. const { authMiddleware } = await import('../../middleware/auth.middleware');
  216. vi.mocked(authMiddleware).mockImplementation(async (c: any) => {
  217. return c.json({ message: 'Authorization header missing' }, 401);
  218. });
  219. apiClient.clearAuthToken();
  220. const response = await apiClient.get('/');
  221. expect(response.status).toBe(401);
  222. expect(response.data).toMatchObject({
  223. message: 'Authorization header missing'
  224. });
  225. });
  226. it('应该在无效令牌时返回401', async () => {
  227. // 模拟认证中间件验证失败
  228. const { authMiddleware } = await import('../../middleware/auth.middleware');
  229. vi.mocked(authMiddleware).mockImplementation((c: any) => {
  230. return c.json({ error: 'Invalid token' }, 401);
  231. });
  232. const response = await apiClient.get('/');
  233. expect(response.status).toBe(401);
  234. expect(response.data).toEqual({ error: 'Invalid token' });
  235. });
  236. });
  237. });