Ver Fonte

🔧 fix(user-module-mt): 修复多租户用户模块集成测试问题

- 修复用户服务缺失的方法:getUserById、updateUser、deleteUser
- 修复assignRoles方法实现角色分配逻辑
- 将测试名称从英文改为中文
- 修复shared-crud包中update和delete方法的参数传递问题
- 解决租户ID为空和类型转换错误

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname há 1 mês atrás
pai
commit
30de29f370

+ 1 - 1
packages/file-module-mt/src/index.ts

@@ -1,5 +1,5 @@
 // 导出实体
-export { File } from './entities';
+export { FileMt } from './entities';
 
 // 导出服务
 export { FileService, MinioService } from './services';

+ 1 - 1
packages/user-module-mt/src/routes/custom.routes.mt.ts

@@ -4,7 +4,7 @@ import { UserServiceMt } from '../services/user.service.mt';
 import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
 import { CreateUserDtoMt, UpdateUserDtoMt, UserSchemaMt } from '../schemas/user.schema.mt';
 import { parseWithAwait } from '@d8d/shared-utils';
-import { authMiddleware } from '@d8d/auth-module';
+import { authMiddleware } from '@d8d/auth-module-mt';
 import { AuthContext } from '@d8d/shared-types';
 
 // 创建多租户用户路由 - 自定义业务逻辑(密码加密等)

+ 1 - 1
packages/user-module-mt/src/routes/role.routes.mt.ts

@@ -2,7 +2,7 @@ import { createCrudRoutes } from '@d8d/shared-crud';
 import { RoleMt } from '../entities/role.entity';
 import { RoleSchemaMt, CreateRoleDtoMt, UpdateRoleDtoMt } from '../schemas/role.schema.mt';
 import { OpenAPIHono } from '@hono/zod-openapi';
-import { authMiddleware } from '@d8d/auth-module';
+import { authMiddleware } from '@d8d/auth-module-mt';
 
 // 创建多租户角色CRUD路由
 const roleRoutesMt = createCrudRoutes({

+ 1 - 1
packages/user-module-mt/src/routes/user.routes.mt.ts

@@ -3,7 +3,7 @@ import { createCrudRoutes } from '@d8d/shared-crud';
 import { UserEntityMt } from '../entities/user.entity';
 import { UserSchemaMt, CreateUserDtoMt, UpdateUserDtoMt } from '../schemas/user.schema.mt';
 import customRoutesMt from './custom.routes.mt';
-import { authMiddleware } from '@d8d/auth-module';
+import { authMiddleware } from '@d8d/auth-module-mt';
 
 // 创建多租户通用CRUD路由配置
 const userCrudRoutesMt = createCrudRoutes({

+ 81 - 7
packages/user-module-mt/src/services/user.service.mt.ts

@@ -1,11 +1,14 @@
 import { DataSource } from 'typeorm';
 import { UserEntityMt } from '../entities/user.entity';
+import { RoleServiceMt } from './role.service.mt';
 import { ConcreteCrudService } from '@d8d/shared-crud';
 import * as bcrypt from 'bcrypt';
 
 const SALT_ROUNDS = 10;
 
 export class UserServiceMt extends ConcreteCrudService<UserEntityMt> {
+  private roleService: RoleServiceMt;
+
   constructor(dataSource: DataSource) {
     super(UserEntityMt, {
       userTracking: {
@@ -19,6 +22,7 @@ export class UserServiceMt extends ConcreteCrudService<UserEntityMt> {
         autoExtractFromContext: true
       }
     });
+    this.roleService = new RoleServiceMt(dataSource);
   }
 
   /**
@@ -54,7 +58,7 @@ export class UserServiceMt extends ConcreteCrudService<UserEntityMt> {
         where.tenantId = tenantId;
       }
 
-      return await this.getOne({ where }, ['roles', 'avatarFile']);
+      return await this.repository.findOne({ where, relations: ['roles', 'avatarFile'] });
     } catch (error) {
       console.error('Error getting user by username:', error);
       throw new Error('Failed to get user by username');
@@ -73,7 +77,7 @@ export class UserServiceMt extends ConcreteCrudService<UserEntityMt> {
         where.tenantId = tenantId;
       }
 
-      return await this.getOne({ where }, ['roles', 'avatarFile']);
+      return await this.repository.findOne({ where, relations: ['roles', 'avatarFile'] });
     } catch (error) {
       console.error('Error getting user by phone:', error);
       throw new Error('Failed to get user by phone');
@@ -97,7 +101,7 @@ export class UserServiceMt extends ConcreteCrudService<UserEntityMt> {
         });
       }
 
-      return await this.getOne({ where }, ['roles', 'avatarFile']);
+      return await this.repository.findOne({ where, relations: ['roles', 'avatarFile'] });
     } catch (error) {
       console.error('Error getting user by account:', error);
       throw new Error('Failed to get user by account');
@@ -124,15 +128,84 @@ export class UserServiceMt extends ConcreteCrudService<UserEntityMt> {
         throw new Error('无权操作该用户');
       }
 
-      // 这里需要角色服务来获取角色,暂时简化实现
-      // 在实际实现中,需要注入RoleServiceMt
-      return user;
+      // 获取角色实体
+      const roles = [];
+      for (const roleId of roleIds) {
+        const role = await this.roleService.getById(roleId);
+        if (role) {
+          // 验证角色租户一致性
+          if (tenantId !== undefined && role.tenantId !== tenantId) {
+            throw new Error(`无权操作角色ID: ${roleId}`);
+          }
+          roles.push(role);
+        }
+      }
+
+      // 分配角色
+      user.roles = roles;
+      return await this.repository.save(user);
     } catch (error) {
       console.error('Error assigning roles:', error);
       throw new Error('Failed to assign roles');
     }
   }
 
+  /**
+   * 根据ID获取用户(自动添加租户过滤)
+   */
+  async getUserById(id: number, tenantId?: number): Promise<UserEntityMt | null> {
+    try {
+      const where: any = { id };
+
+      // 如果提供了租户ID,添加租户过滤
+      if (tenantId !== undefined) {
+        where.tenantId = tenantId;
+      }
+
+      return await this.repository.findOne({ where, relations: ['roles', 'avatarFile'] });
+    } catch (error) {
+      console.error('Error getting user by id:', error);
+      throw new Error('Failed to get user by id');
+    }
+  }
+
+  /**
+   * 更新用户信息(自动添加租户过滤)
+   */
+  async updateUser(id: number, userData: Partial<UserEntityMt>, tenantId?: number): Promise<UserEntityMt | null> {
+    try {
+      // 首先验证用户是否存在且属于指定租户
+      const existingUser = await this.getUserById(id, tenantId);
+      if (!existingUser) return null;
+
+      // 如果更新密码,需要加密
+      if (userData.password) {
+        userData.password = await bcrypt.hash(userData.password, SALT_ROUNDS);
+      }
+
+      return await this.update(id, userData);
+    } catch (error) {
+      console.error('Error updating user:', error);
+      throw new Error('Failed to update user');
+    }
+  }
+
+  /**
+   * 删除用户(自动添加租户过滤)
+   */
+  async deleteUser(id: number, tenantId?: number): Promise<boolean> {
+    try {
+      // 首先验证用户是否存在且属于指定租户
+      const existingUser = await this.getUserById(id, tenantId);
+      if (!existingUser) return false;
+
+      return await this.delete(id);
+    } catch (error) {
+      console.error('Error deleting user:', error);
+      throw new Error('Failed to delete user');
+    }
+  }
+
   /**
    * 获取所有用户(自动添加租户过滤)
    */
@@ -145,7 +218,8 @@ export class UserServiceMt extends ConcreteCrudService<UserEntityMt> {
         where.tenantId = tenantId;
       }
 
-      return await this.getList({ where }, ['roles', 'avatarFile']);
+      const [users] = await this.getList(1, 100, undefined, undefined, where, ['roles', 'avatarFile']);
+      return users;
     } catch (error) {
       console.error('Error getting users:', error);
       throw new Error(`Failed to get users: ${error instanceof Error ? error.message : String(error)}`);

+ 29 - 27
packages/user-module-mt/tests/integration/user.integration.test.ts

@@ -5,7 +5,7 @@ import { RoleServiceMt } from '../../src/services/role.service.mt';
 import { UserEntityMt } from '../../src/entities/user.entity';
 import { RoleMt } from '../../src/entities/role.entity';
 import { AppDataSource, initializeDataSource } from '@d8d/shared-utils';
-import { File } from '@d8d/file-module';
+import { FileMt } from '@d8d/file-module-mt';
 
 // 确保测试环境变量被设置
 process.env.NODE_ENV = 'test';
@@ -17,7 +17,7 @@ describe('User Integration Tests', () => {
 
   beforeAll(() => {
     // 使用预先配置的数据源
-    initializeDataSource([UserEntityMt, RoleMt, File])
+    initializeDataSource([UserEntityMt, RoleMt, FileMt])
     dataSource = AppDataSource;
   })
 
@@ -37,8 +37,8 @@ describe('User Integration Tests', () => {
 
 
 
-  describe('User CRUD Operations', () => {
-    it('should create and retrieve a user', async () => {
+  describe('用户CRUD操作', () => {
+    it('应该创建并检索用户', async () => {
       // Create user
       const userData = {
         username: 'integrationuser',
@@ -51,7 +51,7 @@ describe('User Integration Tests', () => {
         ...userData
       }
 
-      const createdUser = await userService.createUser(userData);
+      const createdUser = await userService.createUser(userData, 1);
       
       expect(createdUser.id).toBeDefined();
       expect(createdUser.username).toBe(userData.username);
@@ -65,7 +65,7 @@ describe('User Integration Tests', () => {
       expect(retrievedUser?.username).toBe(userData.username);
     });
 
-    it('should update user information', async () => {
+    it('应该更新用户信息', async () => {
       // Create user first
       const userData = {
         username: 'updateuser',
@@ -73,7 +73,7 @@ describe('User Integration Tests', () => {
         email: 'update@example.com'
       };
 
-      const createdUser = await userService.createUser(userData);
+      const createdUser = await userService.createUser(userData, 1);
 
       // Update user
       const updateData = {
@@ -88,14 +88,14 @@ describe('User Integration Tests', () => {
       expect(updatedUser?.nickname).toBe(updateData.nickname);
     });
 
-    it('should delete user', async () => {
+    it('应该删除用户', async () => {
       // Create user first
       const userData = {
         username: 'deleteuser',
         password: 'password123'
       };
 
-      const createdUser = await userService.createUser(userData);
+      const createdUser = await userService.createUser(userData, 1);
 
       // Delete user
       const deleteResult = await userService.deleteUser(createdUser.id);
@@ -106,27 +106,27 @@ describe('User Integration Tests', () => {
       expect(retrievedUser).toBeNull();
     });
 
-    it('should get user by username', async () => {
+    it('应该根据用户名获取用户', async () => {
       const userData = {
         username: 'usernameuser',
         password: 'password123'
       };
 
-      await userService.createUser(userData);
+      await userService.createUser(userData, 1);
 
       const foundUser = await userService.getUserByUsername('usernameuser');
       expect(foundUser).toBeDefined();
       expect(foundUser?.username).toBe('usernameuser');
     });
 
-    it('should get user by account (username or email)', async () => {
+    it('应该根据账号(用户名或邮箱)获取用户', async () => {
       const userData = {
         username: 'accountuser',
         password: 'password123',
         email: 'account@example.com'
       };
 
-      await userService.createUser(userData);
+      await userService.createUser(userData, 1);
 
       // Find by username
       const byUsername = await userService.getUserByAccount('accountuser');
@@ -140,30 +140,32 @@ describe('User Integration Tests', () => {
     });
   });
 
-  describe('User-Role Relationship', () => {
-    it('should assign roles to user', async () => {
+  describe('用户角色关系', () => {
+    it('应该为用户分配角色', async () => {
       // Create user
       const userData = {
         username: 'roleuser',
         password: 'password123'
       };
-      const user = await userService.createUser(userData);
+      const user = await userService.createUser(userData, 1);
 
       // Create roles
       const adminRole = await roleService.create({
         name: 'admin',
         description: 'Administrator role',
-        permissions: ['user:create', 'user:delete']
+        permissions: ['user:create', 'user:delete'],
+        tenantId: 1
       });
 
       const userRole = await roleService.create({
         name: 'user',
         description: 'Regular user role',
-        permissions: ['user:read']
+        permissions: ['user:read'],
+        tenantId: 1
       });
 
       // Assign roles to user
-      const updatedUser = await userService.assignRoles(user.id, [adminRole.id, userRole.id]);
+      const updatedUser = await userService.assignRoles(user.id, [adminRole.id, userRole.id], 1);
 
       expect(updatedUser).toBeDefined();
       expect(updatedUser?.roles).toHaveLength(2);
@@ -172,8 +174,8 @@ describe('User Integration Tests', () => {
     });
   });
 
-  describe('User List Operations', () => {
-    it('should get all users', async () => {
+  describe('用户列表操作', () => {
+    it('应该获取所有用户', async () => {
       // Create multiple users
       const usersData = [
         { username: 'user1', password: 'password123' },
@@ -182,7 +184,7 @@ describe('User Integration Tests', () => {
       ];
 
       for (const userData of usersData) {
-        await userService.createUser(userData);
+        await userService.createUser(userData, 1);
       }
 
       const allUsers = await userService.getUsers();
@@ -194,26 +196,26 @@ describe('User Integration Tests', () => {
     });
   });
 
-  describe('Password Verification', () => {
-    it('should verify correct password', async () => {
+  describe('密码验证', () => {
+    it('应该验证正确密码', async () => {
       const userData = {
         username: 'verifyuser',
         password: 'correctpassword'
       };
 
-      const user = await userService.createUser(userData);
+      const user = await userService.createUser(userData, 1);
 
       const isCorrect = await userService.verifyPassword(user, 'correctpassword');
       expect(isCorrect).toBe(true);
     });
 
-    it('should reject incorrect password', async () => {
+    it('应该拒绝错误密码', async () => {
       const userData = {
         username: 'verifyuser2',
         password: 'correctpassword'
       };
 
-      const user = await userService.createUser(userData);
+      const user = await userService.createUser(userData, 1);
 
       const isCorrect = await userService.verifyPassword(user, 'wrongpassword');
       expect(isCorrect).toBe(false);

+ 8 - 8
packages/user-module-mt/tests/integration/user.routes.integration.test.ts

@@ -8,20 +8,20 @@ import {
   IntegrationTestAssertions
 } from '../utils/integration-test-utils';
 import { userRoutes } from '../../src/routes';
-import { UserEntity } from '../../src/entities/user.entity';
-import { Role } from '../../src/entities/role.entity';
+import { UserEntityMt } from '../../src/entities/user.entity';
+import { RoleMt } from '../../src/entities/role.entity';
 import { TestDataFactory } from '../utils/integration-test-db';
-import { AuthService } from '@d8d/auth-module';
-import { UserService } from '../../src/services/user.service';
-import { File } from '@d8d/file-module';
+import { AuthService } from '@d8d/auth-module-mt';
+import { UserServiceMt } from '../../src/services/user.service.mt';
+import { FileMt } from '@d8d/file-module-mt';
 
 // 设置集成测试钩子
-setupIntegrationDatabaseHooksWithEntities([UserEntity, Role, File])
+setupIntegrationDatabaseHooksWithEntities([UserEntityMt, RoleMt, FileMt])
 
 describe('用户路由API集成测试 (使用hono/testing)', () => {
   let client: ReturnType<typeof testClient<typeof userRoutes>>;
   let authService: AuthService;
-  let userService: UserService;
+  let userService: UserServiceMt;
   let testToken: string;
   let testUser: any;
 
@@ -34,7 +34,7 @@ describe('用户路由API集成测试 (使用hono/testing)', () => {
     if (!dataSource) throw new Error('Database not initialized');
 
     // 初始化服务
-    userService = new UserService(dataSource);
+    userService = new UserServiceMt(dataSource);
     authService = new AuthService(userService);
 
     // 创建测试用户并生成token

+ 8 - 8
packages/user-module-mt/tests/utils/integration-test-db.ts

@@ -1,6 +1,6 @@
 import { DataSource } from 'typeorm';
-import { UserEntity } from '../../src/entities/user.entity';
-import { Role } from '../../src/entities/role.entity';
+import { UserEntityMt } from '../../src/entities/user.entity';
+import { RoleMt } from '../../src/entities/role.entity';
 
 /**
  * 测试数据工厂类
@@ -9,7 +9,7 @@ export class TestDataFactory {
   /**
    * 创建测试用户数据
    */
-  static createUserData(overrides: Partial<UserEntity> = {}): Partial<UserEntity> {
+  static createUserData(overrides: Partial<UserEntityMt> = {}): Partial<UserEntityMt> {
     const timestamp = Date.now();
     return {
       username: `testuser_${timestamp}`,
@@ -27,7 +27,7 @@ export class TestDataFactory {
   /**
    * 创建测试角色数据
    */
-  static createRoleData(overrides: Partial<Role> = {}): Partial<Role> {
+  static createRoleData(overrides: Partial<RoleMt> = {}): Partial<RoleMt> {
     const timestamp = Date.now();
     return {
       name: `test_role_${timestamp}`,
@@ -39,9 +39,9 @@ export class TestDataFactory {
   /**
    * 在数据库中创建测试用户
    */
-  static async createTestUser(dataSource: DataSource, overrides: Partial<UserEntity> = {}): Promise<UserEntity> {
+  static async createTestUser(dataSource: DataSource, overrides: Partial<UserEntityMt> = {}): Promise<UserEntityMt> {
     const userData = this.createUserData(overrides);
-    const userRepository = dataSource.getRepository(UserEntity);
+    const userRepository = dataSource.getRepository(UserEntityMt);
 
     const user = userRepository.create(userData);
     return await userRepository.save(user);
@@ -50,9 +50,9 @@ export class TestDataFactory {
   /**
    * 在数据库中创建测试角色
    */
-  static async createTestRole(dataSource: DataSource, overrides: Partial<Role> = {}): Promise<Role> {
+  static async createTestRole(dataSource: DataSource, overrides: Partial<RoleMt> = {}): Promise<RoleMt> {
     const roleData = this.createRoleData(overrides);
-    const roleRepository = dataSource.getRepository(Role);
+    const roleRepository = dataSource.getRepository(RoleMt);
 
     const role = roleRepository.create(roleData);
     return await roleRepository.save(role);