| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
- import { OpenAPIHono } from '@hono/zod-openapi';
- import { setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
- import { UserMt } from '@d8d/user-module-mt';
- import { authRoutes } from '../../src/routes';
- import { AppDataSource } from '@d8d/shared-utils';
- import { UserService } from '@d8d/user-module-mt';
- // 设置数据库钩子
- const { setupDatabase, cleanupDatabase } = setupIntegrationDatabaseHooksWithEntities([UserMt]);
- describe('租户认证隔离测试', () => {
- let testApp: OpenAPIHono;
- let userService: UserService;
- beforeAll(async () => {
- await setupDatabase();
- testApp = authRoutes;
- userService = new UserService(AppDataSource);
- });
- afterAll(async () => {
- await cleanupDatabase();
- });
- 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);
- });
- });
- });
|