| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- 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' });
- });
- });
- });
|