import { describe, it, expect, beforeEach } from 'vitest'; import { testClient } from 'hono/testing'; import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities, } from '@d8d/shared-test-util'; import { RoleMt, UserEntityMt, UserServiceMt } from '@d8d/core-module-mt/user-module-mt'; import { FileMt } from '@d8d/core-module-mt/file-module-mt'; import authRoutes from '../../src/routes/index.mt'; import { AuthService } from '../../src/services/index.mt'; import { DisabledStatus } from '@d8d/shared-types'; import { TestDataFactory } from '../utils/test-data-factory'; // 设置集成测试钩子 setupIntegrationDatabaseHooksWithEntities([UserEntityMt, RoleMt, FileMt]) describe('认证API集成测试 (使用hono/testing)', () => { let client: ReturnType>; let authService: AuthService; let userService: UserServiceMt; let testToken: string; let testUser: any; beforeEach(async () => { // 创建测试客户端 client = testClient(authRoutes); // 获取数据源 const dataSource = await IntegrationTestDatabase.getDataSource(); // 确保数据源已初始化 if (!dataSource.isInitialized) { await dataSource.initialize(); } // 初始化服务 userService = new UserServiceMt(dataSource); authService = new AuthService(userService); // 创建测试用户前先删除可能存在的重复用户 const userRepository = dataSource.getRepository(UserEntityMt); await userRepository.delete({ username: 'testuser' }); testUser = await TestDataFactory.createTestUser(dataSource, { username: 'testuser', password: 'TestPassword123!', email: 'testuser@example.com' }); // 生成测试用户的token testToken = authService.generateToken(testUser); }); describe('登录端点测试 (POST /login)', () => { it('应该使用正确凭据成功登录', async () => { const loginData = { username: 'testuser', password: 'TestPassword123!' }; const response = await client.login.$post({ json: loginData }); expect(response.status).toBe(200); if (response.status === 200) { const responseData = await response.json(); expect(responseData).toHaveProperty('token'); expect(responseData).toHaveProperty('user'); expect(responseData.user.username).toBe('testuser'); expect(responseData.user.email).toBe('testuser@example.com'); expect(typeof responseData.token).toBe('string'); expect(responseData.token.length).toBeGreaterThan(0); } }); it('应该拒绝错误密码的登录', async () => { const loginData = { username: 'testuser', password: 'WrongPassword123!' }; const response = await client.login.$post({ json: loginData }); // 认证失败应该返回401 expect(response.status).toBe(401); if (response.status === 401){ const responseData = await response.json(); expect(responseData.message).toContain('用户名或密码错误'); } }); it('应该拒绝不存在的用户登录', async () => { const loginData = { username: 'nonexistent_user', password: 'TestPassword123!' }; const response = await client.login.$post({ json: loginData }); // 认证失败应该返回401 expect(response.status).toBe(401); if (response.status === 401){ const responseData = await response.json(); expect(responseData.message).toContain('用户名或密码错误'); } }); it('应该拒绝禁用账户的登录', async () => { // 创建禁用账户 const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 先删除可能存在的重复用户 const userRepository = dataSource.getRepository(UserEntityMt); await userRepository.delete({ username: 'disabled_user' }); await TestDataFactory.createTestUser(dataSource, { username: 'disabled_user', password: 'TestPassword123!', email: 'disabled@example.com', isDisabled: DisabledStatus.DISABLED }); const loginData = { username: 'disabled_user', password: 'TestPassword123!' }; const response = await client.login.$post({ json: loginData }); // 禁用账户应该返回401状态码 expect(response.status).toBe(401); if (response.status === 401) { const responseData = await response.json(); expect(responseData.message).toContain('账户已禁用'); } }); }); describe('令牌验证端点测试 (GET /sso-verify)', () => { it('应该成功验证有效令牌', async () => { const response = await client['sso-verify'].$get( {}, { headers: { 'Authorization': `Bearer ${testToken}` } } ); expect(response.status).toBe(200); if (response.status === 200) { const responseText = await response.text(); expect(responseText).toBe('OK'); } }); it('应该拒绝无效令牌', async () => { const response = await client['sso-verify'].$get( {}, { headers: { 'Authorization': 'Bearer invalid.token.here' } } ); expect(response.status).toBe(401); if (response.status === 401) { const responseData = await response.json(); expect(responseData.message).toContain('令牌验证失败'); } }); it('应该拒绝过期令牌', async () => { // 创建立即过期的令牌 const expiredToken = authService.generateToken(testUser, '1ms'); // 等待令牌过期 await new Promise(resolve => setTimeout(resolve, 10)); const response = await client['sso-verify'].$get( {}, { headers: { 'Authorization': `Bearer ${expiredToken}` } } ); expect(response.status).toBe(401); if (response.status === 401) { const responseData = await response.json(); expect(responseData.message).toContain('令牌验证失败'); } }); }); describe('用户信息端点测试 (GET /me)', () => { it('应该成功获取用户信息', async () => { const response = await client.me.$get( {}, { headers: { 'Authorization': `Bearer ${testToken}` } } ); expect(response.status).toBe(200); if (response.status === 200) { const responseData = await response.json(); expect(responseData).toHaveProperty('username'); expect(responseData).toHaveProperty('email'); expect(responseData.username).toBe('testuser'); expect(responseData.email).toBe('testuser@example.com'); } }); it('应该拒绝无令牌的用户信息请求', async () => { const response = await client.me.$get(); expect(response.status).toBe(401); if (response.status === 401) { const responseData = await response.json(); expect(responseData.message).toContain('Authorization header missing'); } }); it('应该拒绝无效令牌的用户信息请求', async () => { const response = await client.me.$get( {}, { headers: { 'Authorization': 'Bearer invalid.token.here' } } ); expect(response.status).toBe(401); if (response.status === 401) { const responseData = await response.json(); expect(responseData.message).toContain('Invalid token'); } }); }); describe('角色权限验证测试', () => { it('应该为不同角色的用户生成包含正确角色信息的令牌', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 创建管理员角色 const adminRole = await TestDataFactory.createTestRole(dataSource, { name: 'admin', permissions: ['user:create', 'user:delete', 'user:update'] }); // 创建普通用户角色 const userRole = await TestDataFactory.createTestRole(dataSource, { name: 'user', permissions: ['user:read'] }); // 创建管理员用户 const adminUser = await TestDataFactory.createTestUser(dataSource, { username: 'admin_user', password: 'TestPassword123!', email: 'admin@example.com' }); // 创建普通用户 const regularUser = await TestDataFactory.createTestUser(dataSource, { username: 'regular_user', password: 'TestPassword123!', email: 'regular@example.com' }); // 分配角色 await userService.assignRoles(adminUser.id, [adminRole.id]); await userService.assignRoles(regularUser.id, [userRole.id]); // 重新加载用户以确保角色信息正确加载 const adminUserWithRoles = await userService.getUserById(adminUser.id); const regularUserWithRoles = await userService.getUserById(regularUser.id); // 生成令牌并验证角色信息 const adminToken = authService.generateToken(adminUserWithRoles!); const regularToken = authService.generateToken(regularUserWithRoles!); // 验证管理员令牌包含admin角色 const adminDecoded = authService.verifyToken(adminToken); expect(adminDecoded.roles).toContain('admin'); // 验证普通用户令牌包含user角色 const regularDecoded = authService.verifyToken(regularToken); expect(regularDecoded.roles).toContain('user'); }); }); describe('错误处理测试', () => { it('应该正确处理认证失败错误', async () => { const loginData = { username: 'testuser', password: 'WrongPassword' }; const response = await client.login.$post({ json: loginData }); expect(response.status).toBe(401); if (response.status === 401) { const responseData = await response.json(); expect(responseData).toHaveProperty('code', 401); expect(responseData).toHaveProperty('message'); expect(responseData.message).toContain('用户名或密码错误'); } }); it('应该正确处理令牌过期错误', async () => { // 模拟过期令牌 const expiredToken = 'expired.jwt.token.here'; const response = await client['sso-verify'].$get( {}, { headers: { 'Authorization': `Bearer ${expiredToken}` } } ); expect(response.status).toBe(401); if (response.status === 401) { const responseData = await response.json(); expect(responseData).toHaveProperty('code', 401); expect(responseData.message).toContain('令牌验证失败'); } }); it('应该正确处理权限不足错误', async () => { // 创建普通用户(无管理员权限) const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 先删除可能存在的重复用户 const userRepository = dataSource.getRepository(UserEntityMt); await userRepository.delete({ username: 'regular_user' }); const regularUser = await TestDataFactory.createTestUser(dataSource, { username: 'regular_user', password: 'TestPassword123!', email: 'regular@example.com' }); const regularToken = authService.generateToken(regularUser); // 尝试访问需要认证的端点(这里使用/me端点) const response = await client.me.$get( {}, { headers: { 'Authorization': `Bearer ${regularToken}` } } ); // 普通用户应该能够访问自己的信息 expect(response.status).toBe(200); }); }); describe('性能基准测试', () => { it('登录操作响应时间应小于200ms', async () => { const loginData = { username: 'testuser', password: 'TestPassword123!' }; const startTime = Date.now(); const response = await client.login.$post({ json: loginData }); const endTime = Date.now(); const responseTime = endTime - startTime; expect(response.status).toBe(200); expect(responseTime).toBeLessThan(200); // 响应时间应小于200ms }); it('令牌验证操作响应时间应小于200ms', async () => { const startTime = Date.now(); const response = await client['sso-verify'].$get( {}, { headers: { 'Authorization': `Bearer ${testToken}` } } ); const endTime = Date.now(); const responseTime = endTime - startTime; expect(response.status).toBe(200); expect(responseTime).toBeLessThan(200); // 响应时间应小于200ms }); }); describe('租户隔离测试', () => { it('应该成功注册不同租户的用户', async () => { // 租户1的用户注册 const tenant1Response = await client.register.$post({ json: { username: 'tenant1_user', password: 'password123', email: 'tenant1@example.com' } }, { headers: { 'X-Tenant-Id': '1' } }); expect(tenant1Response.status).toBe(201); if (tenant1Response.status === 201) { const tenant1Data = await tenant1Response.json(); expect(tenant1Data.token).toBeDefined(); expect(tenant1Data.user.username).toBe('tenant1_user'); } // 租户2的用户注册 const tenant2Response = await client.register.$post({ json: { username: 'tenant2_user', password: 'password123', email: 'tenant2@example.com' } }, { headers: { 'X-Tenant-Id': '2' } }); expect(tenant2Response.status).toBe(201); if (tenant2Response.status === 201) { const tenant2Data = await tenant2Response.json(); expect(tenant2Data.token).toBeDefined(); expect(tenant2Data.user.username).toBe('tenant2_user'); } }); it('应该成功登录到正确的租户', async () => { // 先注册租户1的用户 await client.register.$post({ json: { username: 'login_tenant1', password: 'password123', email: 'login1@example.com' } }, { headers: { 'X-Tenant-Id': '1' } }); // 租户1的用户登录 const tenant1Response = await client.login.$post({ json: { username: 'login_tenant1', password: 'password123' } }, { headers: { 'X-Tenant-Id': '1' } }); expect(tenant1Response.status).toBe(200); if (tenant1Response.status === 200) { const tenant1Data = await tenant1Response.json(); expect(tenant1Data.token).toBeDefined(); expect(tenant1Data.user.username).toBe('login_tenant1'); } // 先注册租户2的用户 await client.register.$post({ json: { username: 'login_tenant2', password: 'password123', email: 'login2@example.com' } }, { headers: { 'X-Tenant-Id': '2' } }); // 租户2的用户登录 const tenant2Response = await client.login.$post({ json: { username: 'login_tenant2', password: 'password123' } }, { headers: { 'X-Tenant-Id': '2' } }); expect(tenant2Response.status).toBe(200); if (tenant2Response.status === 200) { const tenant2Data = await tenant2Response.json(); expect(tenant2Data.token).toBeDefined(); expect(tenant2Data.user.username).toBe('login_tenant2'); } }); it('应该拒绝跨租户登录', async () => { // 先注册租户1的用户 await client.register.$post({ json: { username: 'cross_tenant_user', password: 'password123', email: 'cross@example.com' } }, { headers: { 'X-Tenant-Id': '1' } }); // 尝试用租户1的用户登录到租户2 const crossTenantResponse = await client.login.$post({ json: { username: 'cross_tenant_user', password: 'password123' } }, { headers: { 'X-Tenant-Id': '2' } }); expect(crossTenantResponse.status).toBe(401); if (crossTenantResponse.status === 401) { const errorData = await crossTenantResponse.json(); expect(errorData.message).toBe('用户名或密码错误'); } }); it('应该允许无租户ID的登录(向后兼容)', async () => { // 创建无租户的用户 const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 先删除可能存在的重复用户 const userRepository = dataSource.getRepository(UserEntityMt); await userRepository.delete({ username: 'notenant' }); await TestDataFactory.createTestUser(dataSource, { username: 'notenant', password: 'password123', email: 'notenant@example.com' }); // 无租户ID登录 const response = await client.login.$post({ json: { username: 'notenant', password: 'password123' } }); expect(response.status).toBe(200); if (response.status === 200) { const data = await response.json(); expect(data.token).toBeDefined(); expect(data.user.username).toBe('notenant'); } }); it('应该在认证后设置租户上下文', async () => { // 先注册用户 await client.register.$post({ json: { username: 'context_user', password: 'password123', email: 'context@example.com' } }, { headers: { 'X-Tenant-Id': '1' } }); // 先登录获取token const loginResponse = await client.login.$post({ json: { username: 'context_user', password: 'password123' } }, { headers: { 'X-Tenant-Id': '1' } }); const loginData = await loginResponse.json(); if ('token' in loginData) { const token = loginData.token; // 使用token访问需要认证的端点 const meResponse = await client.me.$get( {}, { headers: { 'Authorization': `Bearer ${token}` } } ); expect(meResponse.status).toBe(200); if (meResponse.status === 200) { const meData = await meResponse.json(); expect(meData.tenantId).toBe(1); // 应该包含租户ID } } }); it('应该在JWT token中包含租户ID', async () => { // 先注册用户 await client.register.$post({ json: { username: 'jwt_user', password: 'password123', email: 'jwt@example.com' } }, { headers: { 'X-Tenant-Id': '1' } }); const response = await client.login.$post({ json: { username: 'jwt_user', password: 'password123' } }, { headers: { 'X-Tenant-Id': '1' } }); const data = await response.json(); if ('token' in data) { const token = data.token; // 解码token验证租户ID const { JWTUtil } = await import('@d8d/shared-utils'); const decoded = JWTUtil.decodeToken(token); expect(decoded?.tenantId).toBe(1); } }); }); });