import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { OpenAPIHono } from '@hono/zod-openapi'; import { createApiClient, ApiClient } from '../../__test_utils__/api-client'; import { createMockDataSource } from '../../__test_utils__/test-db'; // import '../../utils/generic-crud.service' // Mock 数据源 vi.mock('../../../data-source', () => { const mockDataSource = createMockDataSource(); return { AppDataSource: mockDataSource }; }); // Mock 用户服务 vi.mock('../../modules/users/user.service', () => ({ UserService: vi.fn().mockImplementation(() => ({ getUserById: vi.fn().mockResolvedValue(null), createUser: vi.fn().mockResolvedValue({ id: 1, username: 'testuser' }), updateUser: vi.fn().mockResolvedValue({ affected: 1 }), deleteUser: vi.fn().mockResolvedValue({ affected: 1 }) })) })); // Mock 认证中间件 - 使用工厂函数确保每个测试有独立的mock实例 vi.mock('../../middleware/auth.middleware', () => ({ authMiddleware: vi.fn().mockImplementation((_c, next) => next()) })); // Mock 通用CRUD服务 const mockCrudService = { getList: vi.fn(), getById: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn() }; vi.mock('../../utils/generic-crud.service', () => ({ GenericCrudService: vi.fn().mockImplementation(() => mockCrudService) })); describe('Users API Integration Tests', () => { let app: OpenAPIHono; let apiClient: ApiClient; beforeEach(async () => { vi.clearAllMocks(); // 重置认证中间件mock const { authMiddleware } = await import('../../middleware/auth.middleware'); vi.mocked(authMiddleware).mockImplementation(async (c, next) => { // 模拟认证成功,设置用户信息 c.set('user', { id: 1, username: 'testuser' } as any); await next(); }); // 重置CRUD服务mock mockCrudService.getList.mockResolvedValue([[], 0]); mockCrudService.getById.mockResolvedValue(null); mockCrudService.create.mockResolvedValue({ id: 1, username: 'testuser' }); mockCrudService.update.mockResolvedValue({ affected: 1 }); mockCrudService.delete.mockResolvedValue({ affected: 1 }); // 动态导入用户路由 const userRoutes = await import('../users/index'); // 使用导入的应用实例 app = userRoutes.default; // 创建API客户端 apiClient = createApiClient(app, { authToken: 'test-token-123' }); }); afterEach(() => { vi.resetAllMocks(); }); describe('GET /users', () => { it('应该返回用户列表和分页信息', async () => { const mockUsers = [ { id: 1, username: 'user1', password: 'password123', phone: null, email: 'user1@example.com', nickname: null, name: null, avatar: null, isDisabled: 0, isDeleted: 0, roles: [], createdAt: new Date('2024-01-01T00:00:00.000Z'), updatedAt: new Date('2024-01-01T00:00:00.000Z') }, { id: 2, username: 'user2', password: 'password123', phone: null, email: 'user2@example.com', nickname: null, name: null, avatar: null, isDisabled: 0, isDeleted: 0, roles: [], createdAt: new Date('2024-01-02T00:00:00.000Z'), updatedAt: new Date('2024-01-02T00:00:00.000Z') } ]; mockCrudService.getList.mockResolvedValue([mockUsers, 2]); const response = await apiClient.get('/?page=1&pageSize=10'); if (response.status !== 200) { // 使用 process.stderr.write 绕过 console mock process.stderr.write(`Response status: ${response.status}\n`); process.stderr.write(`Response data: ${JSON.stringify(response.data, null, 2)}\n`); } expect(response.status).toBe(200); expect(response.data).toEqual({ data: mockUsers.map(user => ({ ...user, createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt.toISOString() })), pagination: { total: 2, current: 1, pageSize: 10 } }); }); it('应该验证分页参数', async () => { mockCrudService.getList.mockResolvedValue([[], 0]); const response = await apiClient.get('/?page=0&pageSize=0'); expect(response.status).toBe(400); expect(response.data).toMatchObject({ success: false, error: expect.any(Object) }); }); it('应该支持关键词搜索', async () => { mockCrudService.getList.mockResolvedValue([[], 0]); const response = await apiClient.get('/?page=1&pageSize=10&keyword=admin'); expect(response.status).toBe(200); expect(mockCrudService.getList).toHaveBeenCalledWith( 1, 10, 'admin', ['username', 'nickname', 'phone', 'email'], undefined, ['roles'], expect.any(Object), undefined ); }); }); describe('GET /users/:id', () => { it('应该返回特定用户信息', async () => { const mockUser = { id: 1, username: 'testuser', password: 'password123', phone: null, email: 'test@example.com', nickname: null, name: null, avatar: null, isDisabled: 0, isDeleted: 0, roles: [], createdAt: new Date('2024-01-01T00:00:00.000Z'), updatedAt: new Date('2024-01-01T00:00:00.000Z') }; mockCrudService.getById.mockResolvedValue(mockUser); const response = await apiClient.get('/1'); expect(response.status).toBe(200); expect(response.data).toEqual({ ...mockUser, createdAt: mockUser.createdAt.toISOString(), updatedAt: mockUser.updatedAt.toISOString() }); expect(mockCrudService.getById).toHaveBeenCalledWith(1, ['roles']); }); it('应该在用户不存在时返回404', async () => { mockCrudService.getById.mockResolvedValue(null); const response = await apiClient.get('/999'); expect(response.status).toBe(404); expect(response.data).toMatchObject({ code: 404, message: expect.any(String) }); }); it('应该验证用户ID格式', async () => { const response = await apiClient.get('/invalid'); expect(response.status).toBe(400); expect(response.data).toMatchObject({ success: false, error: expect.any(Object) }); }); }); describe('错误处理', () => { it('应该在服务错误时返回500状态码', async () => { mockCrudService.getList.mockRejectedValue(new Error('Database error')); const response = await apiClient.get('/?page=1&pageSize=10'); expect(response.status).toBe(500); expect(response.data).toMatchObject({ code: 500, message: 'Database error' }); }); it('应该在未知错误时返回通用错误消息', async () => { mockCrudService.getList.mockRejectedValue('Unknown error'); const response = await apiClient.get('/?page=1&pageSize=10'); expect(response.status).toBe(500); expect(response.data).toMatchObject({ code: 500, message: '获取列表失败' }); }); }); describe('认证和授权', () => { it('应该在缺少认证令牌时返回401', async () => { // 临时覆盖认证中间件mock,模拟认证失败 const { authMiddleware } = await import('../../middleware/auth.middleware'); vi.mocked(authMiddleware).mockImplementation(async (c: any) => { return c.json({ message: 'Authorization header missing' }, 401); }); apiClient.clearAuthToken(); const response = await apiClient.get('/'); expect(response.status).toBe(401); expect(response.data).toMatchObject({ message: 'Authorization header missing' }); }); it('应该在无效令牌时返回401', async () => { // 模拟认证中间件验证失败 const { authMiddleware } = await import('../../middleware/auth.middleware'); vi.mocked(authMiddleware).mockImplementation((c: any) => { return c.json({ error: 'Invalid token' }, 401); }); const response = await apiClient.get('/'); expect(response.status).toBe(401); expect(response.data).toEqual({ error: 'Invalid token' }); }); }); });