import { describe, it, expect, beforeEach } from 'vitest'; import { OpenAPIHono } from '@hono/zod-openapi'; import { setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util'; import { UserEntityMt } from '@d8d/user-module-mt'; import { FileMt } from '@d8d/file-module-mt'; import { authRoutes } from '../../src/routes'; import { AppDataSource } from '@d8d/shared-utils'; import { UserService } from '@d8d/user-module-mt'; // 设置数据库钩子 setupIntegrationDatabaseHooksWithEntities([UserEntityMt, FileMt]); describe('租户认证隔离测试', () => { let testApp: OpenAPIHono; let userService: UserService; beforeEach(async () => { testApp = authRoutes; userService = new UserService(AppDataSource); }); describe('用户注册和登录', () => { it('应该成功注册不同租户的用户', async () => { // 租户1的用户注册 const tenant1Response = await testApp.request('/register', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Tenant-Id': '1' }, body: JSON.stringify({ username: 'user1', password: 'password123', email: 'user1@example.com' }) }); expect(tenant1Response.status).toBe(201); const tenant1Data = await tenant1Response.json(); expect(tenant1Data.token).toBeDefined(); expect(tenant1Data.user.username).toBe('user1'); // 租户2的用户注册 const tenant2Response = await testApp.request('/register', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Tenant-Id': '2' }, body: JSON.stringify({ username: 'user2', password: 'password123', email: 'user2@example.com' }) }); expect(tenant2Response.status).toBe(201); const tenant2Data = await tenant2Response.json(); expect(tenant2Data.token).toBeDefined(); expect(tenant2Data.user.username).toBe('user2'); }); it('应该成功登录到正确的租户', async () => { // 租户1的用户登录 const tenant1Response = await testApp.request('/login', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Tenant-Id': '1' }, body: JSON.stringify({ username: 'user1', password: 'password123' }) }); expect(tenant1Response.status).toBe(200); const tenant1Data = await tenant1Response.json(); expect(tenant1Data.token).toBeDefined(); expect(tenant1Data.user.username).toBe('user1'); // 租户2的用户登录 const tenant2Response = await testApp.request('/login', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Tenant-Id': '2' }, body: JSON.stringify({ username: 'user2', password: 'password123' }) }); expect(tenant2Response.status).toBe(200); const tenant2Data = await tenant2Response.json(); expect(tenant2Data.token).toBeDefined(); expect(tenant2Data.user.username).toBe('user2'); }); it('应该拒绝跨租户登录', async () => { // 尝试用租户1的用户登录到租户2 const crossTenantResponse = await testApp.request('/login', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Tenant-Id': '2' }, body: JSON.stringify({ username: 'user1', password: 'password123' }) }); expect(crossTenantResponse.status).toBe(401); const errorData = await crossTenantResponse.json(); expect(errorData.message).toBe('用户不属于该租户'); }); it('应该允许无租户ID的登录(向后兼容)', async () => { // 创建无租户的用户 const noTenantUser = await userService.createUser({ username: 'notenant', password: 'password123', email: 'notenant@example.com' }); // 无租户ID登录 const response = await testApp.request('/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'notenant', password: 'password123' }) }); expect(response.status).toBe(200); const data = await response.json(); expect(data.token).toBeDefined(); expect(data.user.username).toBe('notenant'); }); }); describe('认证中间件租户上下文', () => { it('应该在认证后设置租户上下文', async () => { // 先登录获取token const loginResponse = await testApp.request('/login', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Tenant-Id': '1' }, body: JSON.stringify({ username: 'user1', password: 'password123' }) }); const loginData = await loginResponse.json(); const token = loginData.token; // 使用token访问需要认证的端点 const meResponse = await testApp.request('/me', { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); expect(meResponse.status).toBe(200); const meData = await meResponse.json(); expect(meData.tenantId).toBe(1); // 应该包含租户ID }); }); describe('JWT Token租户信息', () => { it('应该在JWT token中包含租户ID', async () => { const response = await testApp.request('/login', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Tenant-Id': '1' }, body: JSON.stringify({ username: 'user1', password: 'password123' }) }); const data = await response.json(); const token = data.token; // 解码token验证租户ID const { JWTUtil } = await import('@d8d/shared-utils'); const decoded = JWTUtil.decodeToken(token); expect(decoded?.tenantId).toBe(1); }); }); });