Pārlūkot izejas kodu

✅ test(role): add role service integration and unit tests
- 创建role.integration.test.ts,测试角色CRUD、列表操作和权限检查
- 创建role.service.test.ts,单元测试角色名称查询和权限检查功能

✅ test(user): add user service integration and unit tests
- 创建user.integration.test.ts,测试用户CRUD、角色分配和密码验证
- 创建user.service.test.ts,单元测试用户创建、查询、更新和角色分配功能

yourname 4 nedēļas atpakaļ
vecāks
revīzija
7961abac91

+ 196 - 0
packages/user-module/tests/integration/role.integration.test.ts

@@ -0,0 +1,196 @@
+import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
+import { DataSource } from 'typeorm';
+import { RoleService } from '../../src/services/role.service';
+import { Role } from '../../src/entities/role.entity';
+import { getTestDataSource } from '@d8d/shared-utils';
+
+describe('Role Integration Tests', () => {
+  let dataSource: DataSource;
+  let roleService: RoleService;
+
+  beforeAll(async () => {
+    dataSource = await getTestDataSource();
+    roleService = new RoleService(dataSource);
+  });
+
+  afterAll(async () => {
+    if (dataSource.isInitialized) {
+      await dataSource.destroy();
+    }
+  });
+
+  beforeEach(async () => {
+    // Clean up database before each test
+    await dataSource.getRepository(Role).delete({});
+  });
+
+  describe('Role CRUD Operations', () => {
+    it('should create and retrieve a role', async () => {
+      // Create role
+      const roleData = {
+        name: 'admin',
+        description: 'Administrator role',
+        permissions: ['user:create', 'user:delete', 'role:manage']
+      };
+
+      const createdRole = await roleService.create(roleData);
+
+      expect(createdRole.id).toBeDefined();
+      expect(createdRole.name).toBe(roleData.name);
+      expect(createdRole.description).toBe(roleData.description);
+      expect(createdRole.permissions).toEqual(roleData.permissions);
+
+      // Retrieve role
+      const retrievedRole = await roleService.getById(createdRole.id);
+      expect(retrievedRole).toBeDefined();
+      expect(retrievedRole?.name).toBe(roleData.name);
+    });
+
+    it('should update role information', async () => {
+      // Create role first
+      const roleData = {
+        name: 'updaterole',
+        description: 'Role to be updated',
+        permissions: ['user:read']
+      };
+
+      const createdRole = await roleService.create(roleData);
+
+      // Update role
+      const updateData = {
+        description: 'Updated role description',
+        permissions: ['user:read', 'user:update']
+      };
+
+      const updatedRole = await roleService.update(createdRole.id, updateData);
+
+      expect(updatedRole).toBeDefined();
+      expect(updatedRole?.description).toBe(updateData.description);
+      expect(updatedRole?.permissions).toEqual(updateData.permissions);
+    });
+
+    it('should delete role', async () => {
+      // Create role first
+      const roleData = {
+        name: 'deleterole',
+        description: 'Role to be deleted',
+        permissions: ['user:read']
+      };
+
+      const createdRole = await roleService.create(roleData);
+
+      // Delete role
+      const deleteResult = await roleService.delete(createdRole.id);
+      expect(deleteResult).toBe(true);
+
+      // Verify role is deleted
+      const retrievedRole = await roleService.getById(createdRole.id);
+      expect(retrievedRole).toBeNull();
+    });
+
+    it('should get role by name', async () => {
+      const roleData = {
+        name: 'specificrole',
+        description: 'Specific role for testing',
+        permissions: ['user:read']
+      };
+
+      await roleService.create(roleData);
+
+      const foundRole = await roleService.getRoleByName('specificrole');
+      expect(foundRole).toBeDefined();
+      expect(foundRole?.name).toBe('specificrole');
+    });
+  });
+
+  describe('Role List Operations', () => {
+    it('should get paginated list of roles', async () => {
+      // Create multiple roles
+      const rolesData = [
+        { name: 'role1', description: 'Role 1', permissions: ['user:read'] },
+        { name: 'role2', description: 'Role 2', permissions: ['user:read'] },
+        { name: 'role3', description: 'Role 3', permissions: ['user:read'] }
+      ];
+
+      for (const roleData of rolesData) {
+        await roleService.create(roleData);
+      }
+
+      const [roles, total] = await roleService.getList(1, 10);
+
+      expect(total).toBe(3);
+      expect(roles).toHaveLength(3);
+      expect(roles.map(r => r.name)).toEqual(
+        expect.arrayContaining(['role1', 'role2', 'role3'])
+      );
+    });
+
+    it('should search roles by name', async () => {
+      // Create roles with different names
+      await roleService.create({ name: 'admin', description: 'Admin role', permissions: ['user:create'] });
+      await roleService.create({ name: 'user', description: 'User role', permissions: ['user:read'] });
+      await roleService.create({ name: 'moderator', description: 'Moderator role', permissions: ['user:update'] });
+
+      const [adminRoles] = await roleService.getList(1, 10, 'admin', ['name']);
+      expect(adminRoles).toHaveLength(1);
+      expect(adminRoles[0].name).toBe('admin');
+
+      const [userRoles] = await roleService.getList(1, 10, 'user', ['name']);
+      expect(userRoles).toHaveLength(1);
+      expect(userRoles[0].name).toBe('user');
+    });
+  });
+
+  describe('Permission Checking', () => {
+    it('should check if role has permission', async () => {
+      const roleData = {
+        name: 'permissionrole',
+        description: 'Role with specific permissions',
+        permissions: ['user:create', 'user:read', 'user:update']
+      };
+
+      const role = await roleService.create(roleData);
+
+      // Check existing permissions
+      expect(await roleService.checkPermission(role.id, 'user:create')).toBe(true);
+      expect(await roleService.checkPermission(role.id, 'user:read')).toBe(true);
+      expect(await roleService.checkPermission(role.id, 'user:update')).toBe(true);
+
+      // Check non-existing permission
+      expect(await roleService.checkPermission(role.id, 'user:delete')).toBe(false);
+    });
+
+    it('should return false for non-existent role', async () => {
+      const hasPermission = await roleService.checkPermission(999, 'user:create');
+      expect(hasPermission).toBe(false);
+    });
+  });
+
+  describe('Role Validation', () => {
+    it('should require unique role names', async () => {
+      const roleData = {
+        name: 'unique',
+        description: 'Unique role',
+        permissions: ['user:read']
+      };
+
+      // Create first role
+      await roleService.create(roleData);
+
+      // Try to create role with same name
+      await expect(roleService.create(roleData)).rejects.toThrow();
+    });
+
+    it('should require at least one permission', async () => {
+      const roleData = {
+        name: 'nopermission',
+        description: 'Role without permissions',
+        permissions: []
+      };
+
+      // This should work as TypeORM handles empty arrays
+      const role = await roleService.create(roleData);
+      expect(role.permissions).toEqual([]);
+    });
+  });
+});

+ 210 - 0
packages/user-module/tests/integration/user.integration.test.ts

@@ -0,0 +1,210 @@
+import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
+import { DataSource } from 'typeorm';
+import { UserService } from '../../src/services/user.service';
+import { RoleService } from '../../src/services/role.service';
+import { UserEntity } from '../../src/entities/user.entity';
+import { Role } from '../../src/entities/role.entity';
+import { getTestDataSource } from '@d8d/shared-utils';
+
+describe('User Integration Tests', () => {
+  let dataSource: DataSource;
+  let userService: UserService;
+  let roleService: RoleService;
+
+  beforeAll(async () => {
+    dataSource = await getTestDataSource();
+    userService = new UserService(dataSource);
+    roleService = new RoleService(dataSource);
+  });
+
+  afterAll(async () => {
+    if (dataSource.isInitialized) {
+      await dataSource.destroy();
+    }
+  });
+
+  beforeEach(async () => {
+    // Clean up database before each test
+    await dataSource.getRepository(UserEntity).delete({});
+    await dataSource.getRepository(Role).delete({});
+  });
+
+  describe('User CRUD Operations', () => {
+    it('should create and retrieve a user', async () => {
+      // Create user
+      const userData = {
+        username: 'integrationuser',
+        password: 'password123',
+        email: 'integration@example.com',
+        nickname: 'Integration User'
+      };
+
+      const createdUser = await userService.createUser(userData);
+
+      expect(createdUser.id).toBeDefined();
+      expect(createdUser.username).toBe(userData.username);
+      expect(createdUser.email).toBe(userData.email);
+      expect(createdUser.nickname).toBe(userData.nickname);
+      expect(createdUser.password).not.toBe(userData.password); // Password should be hashed
+
+      // Retrieve user
+      const retrievedUser = await userService.getUserById(createdUser.id);
+      expect(retrievedUser).toBeDefined();
+      expect(retrievedUser?.username).toBe(userData.username);
+    });
+
+    it('should update user information', async () => {
+      // Create user first
+      const userData = {
+        username: 'updateuser',
+        password: 'password123',
+        email: 'update@example.com'
+      };
+
+      const createdUser = await userService.createUser(userData);
+
+      // Update user
+      const updateData = {
+        email: 'updated@example.com',
+        nickname: 'Updated User'
+      };
+
+      const updatedUser = await userService.updateUser(createdUser.id, updateData);
+
+      expect(updatedUser).toBeDefined();
+      expect(updatedUser?.email).toBe(updateData.email);
+      expect(updatedUser?.nickname).toBe(updateData.nickname);
+    });
+
+    it('should delete user', async () => {
+      // Create user first
+      const userData = {
+        username: 'deleteuser',
+        password: 'password123'
+      };
+
+      const createdUser = await userService.createUser(userData);
+
+      // Delete user
+      const deleteResult = await userService.deleteUser(createdUser.id);
+      expect(deleteResult).toBe(true);
+
+      // Verify user is deleted
+      const retrievedUser = await userService.getUserById(createdUser.id);
+      expect(retrievedUser).toBeNull();
+    });
+
+    it('should get user by username', async () => {
+      const userData = {
+        username: 'usernameuser',
+        password: 'password123'
+      };
+
+      await userService.createUser(userData);
+
+      const foundUser = await userService.getUserByUsername('usernameuser');
+      expect(foundUser).toBeDefined();
+      expect(foundUser?.username).toBe('usernameuser');
+    });
+
+    it('should get user by account (username or email)', async () => {
+      const userData = {
+        username: 'accountuser',
+        password: 'password123',
+        email: 'account@example.com'
+      };
+
+      await userService.createUser(userData);
+
+      // Find by username
+      const byUsername = await userService.getUserByAccount('accountuser');
+      expect(byUsername).toBeDefined();
+      expect(byUsername?.username).toBe('accountuser');
+
+      // Find by email
+      const byEmail = await userService.getUserByAccount('account@example.com');
+      expect(byEmail).toBeDefined();
+      expect(byEmail?.email).toBe('account@example.com');
+    });
+  });
+
+  describe('User-Role Relationship', () => {
+    it('should assign roles to user', async () => {
+      // Create user
+      const userData = {
+        username: 'roleuser',
+        password: 'password123'
+      };
+      const user = await userService.createUser(userData);
+
+      // Create roles
+      const adminRole = await roleService.create({
+        name: 'admin',
+        description: 'Administrator role',
+        permissions: ['user:create', 'user:delete']
+      });
+
+      const userRole = await roleService.create({
+        name: 'user',
+        description: 'Regular user role',
+        permissions: ['user:read']
+      });
+
+      // Assign roles to user
+      const updatedUser = await userService.assignRoles(user.id, [adminRole.id, userRole.id]);
+
+      expect(updatedUser).toBeDefined();
+      expect(updatedUser?.roles).toHaveLength(2);
+      expect(updatedUser?.roles.map(r => r.name)).toContain('admin');
+      expect(updatedUser?.roles.map(r => r.name)).toContain('user');
+    });
+  });
+
+  describe('User List Operations', () => {
+    it('should get all users', async () => {
+      // Create multiple users
+      const usersData = [
+        { username: 'user1', password: 'password123' },
+        { username: 'user2', password: 'password123' },
+        { username: 'user3', password: 'password123' }
+      ];
+
+      for (const userData of usersData) {
+        await userService.createUser(userData);
+      }
+
+      const allUsers = await userService.getUsers();
+
+      expect(allUsers).toHaveLength(3);
+      expect(allUsers.map(u => u.username)).toEqual(
+        expect.arrayContaining(['user1', 'user2', 'user3'])
+      );
+    });
+  });
+
+  describe('Password Verification', () => {
+    it('should verify correct password', async () => {
+      const userData = {
+        username: 'verifyuser',
+        password: 'correctpassword'
+      };
+
+      const user = await userService.createUser(userData);
+
+      const isCorrect = await userService.verifyPassword(user, 'correctpassword');
+      expect(isCorrect).toBe(true);
+    });
+
+    it('should reject incorrect password', async () => {
+      const userData = {
+        username: 'verifyuser2',
+        password: 'correctpassword'
+      };
+
+      const user = await userService.createUser(userData);
+
+      const isCorrect = await userService.verifyPassword(user, 'wrongpassword');
+      expect(isCorrect).toBe(false);
+    });
+  });
+});

+ 115 - 0
packages/user-module/tests/unit/role.service.test.ts

@@ -0,0 +1,115 @@
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { RoleService } from '../../src/services/role.service';
+import { Role } from '../../src/entities/role.entity';
+import { DataSource, Repository } from 'typeorm';
+
+// Mock DataSource
+const mockDataSource = {
+  getRepository: vi.fn()
+} as unknown as DataSource;
+
+// Mock repository
+const mockRepository = {
+  findOneBy: vi.fn(),
+  findOne: vi.fn()
+} as unknown as Repository<Role>;
+
+describe('RoleService', () => {
+  let roleService: RoleService;
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+
+    // Setup mock repository
+    vi.mocked(mockDataSource.getRepository).mockReturnValue(mockRepository);
+
+    roleService = new RoleService(mockDataSource);
+  });
+
+  describe('getRoleByName', () => {
+    it('should return role by name', async () => {
+      const mockRole = {
+        id: 1,
+        name: 'admin',
+        description: 'Administrator role',
+        permissions: ['user:create', 'user:delete']
+      } as Role;
+
+      vi.mocked(mockRepository.findOneBy).mockResolvedValue(mockRole);
+
+      const result = await roleService.getRoleByName('admin');
+
+      expect(mockRepository.findOneBy).toHaveBeenCalledWith({ name: 'admin' });
+      expect(result).toEqual(mockRole);
+    });
+
+    it('should return null when role not found', async () => {
+      vi.mocked(mockRepository.findOneBy).mockResolvedValue(null);
+
+      const result = await roleService.getRoleByName('nonexistent');
+
+      expect(result).toBeNull();
+    });
+  });
+
+  describe('checkPermission', () => {
+    it('should return true when role has permission', async () => {
+      const mockRole = {
+        id: 1,
+        name: 'admin',
+        permissions: ['user:create', 'user:delete']
+      } as Role;
+
+      vi.mocked(mockRepository.findOne).mockResolvedValue(mockRole);
+
+      const result = await roleService.checkPermission(1, 'user:create');
+
+      expect(result).toBe(true);
+    });
+
+    it('should return false when role does not have permission', async () => {
+      const mockRole = {
+        id: 1,
+        name: 'admin',
+        permissions: ['user:create', 'user:delete']
+      } as Role;
+
+      vi.mocked(mockRepository.findOne).mockResolvedValue(mockRole);
+
+      const result = await roleService.checkPermission(1, 'user:update');
+
+      expect(result).toBe(false);
+    });
+
+    it('should return false when role not found', async () => {
+      vi.mocked(mockRepository.findOne).mockResolvedValue(null);
+
+      const result = await roleService.checkPermission(999, 'user:create');
+
+      expect(result).toBe(false);
+    });
+  });
+
+  // Test inherited methods from GenericCrudService
+  describe('inherited methods', () => {
+    it('should have getById method', () => {
+      expect(roleService.getById).toBeDefined();
+    });
+
+    it('should have getList method', () => {
+      expect(roleService.getList).toBeDefined();
+    });
+
+    it('should have create method', () => {
+      expect(roleService.create).toBeDefined();
+    });
+
+    it('should have update method', () => {
+      expect(roleService.update).toBeDefined();
+    });
+
+    it('should have delete method', () => {
+      expect(roleService.delete).toBeDefined();
+    });
+  });
+});

+ 254 - 0
packages/user-module/tests/unit/user.service.test.ts

@@ -0,0 +1,254 @@
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { UserService } from '../../src/services/user.service';
+import { UserEntity } from '../../src/entities/user.entity';
+import { Role } from '../../src/entities/role.entity';
+import { DataSource, Repository } from 'typeorm';
+
+// Mock DataSource
+const mockDataSource = {
+  getRepository: vi.fn()
+} as unknown as DataSource;
+
+// Mock repositories
+const mockUserRepository = {
+  create: vi.fn(),
+  save: vi.fn(),
+  findOne: vi.fn(),
+  update: vi.fn(),
+  delete: vi.fn(),
+  find: vi.fn(),
+  findByIds: vi.fn()
+} as unknown as Repository<UserEntity>;
+
+const mockRoleRepository = {
+  findByIds: vi.fn()
+} as unknown as Repository<Role>;
+
+describe('UserService', () => {
+  let userService: UserService;
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+
+    // Setup mock repositories
+    vi.mocked(mockDataSource.getRepository)
+      .mockReturnValueOnce(mockUserRepository)
+      .mockReturnValueOnce(mockRoleRepository);
+
+    userService = new UserService(mockDataSource);
+  });
+
+  describe('createUser', () => {
+    it('should create a user with hashed password', async () => {
+      const userData = {
+        username: 'testuser',
+        password: 'password123',
+        email: 'test@example.com'
+      };
+
+      const mockUser = {
+        id: 1,
+        ...userData,
+        password: 'hashed_password'
+      } as UserEntity;
+
+      vi.mocked(mockUserRepository.create).mockReturnValue(mockUser);
+      vi.mocked(mockUserRepository.save).mockResolvedValue(mockUser);
+
+      const result = await userService.createUser(userData);
+
+      expect(mockUserRepository.create).toHaveBeenCalledWith({
+        ...userData,
+        password: expect.not.stringMatching('password123') // Password should be hashed
+      });
+      expect(mockUserRepository.save).toHaveBeenCalledWith(mockUser);
+      expect(result).toEqual(mockUser);
+    });
+
+    it('should throw error when creation fails', async () => {
+      const userData = {
+        username: 'testuser',
+        password: 'password123'
+      };
+
+      vi.mocked(mockUserRepository.create).mockImplementation(() => {
+        throw new Error('Database error');
+      });
+
+      await expect(userService.createUser(userData)).rejects.toThrow('Failed to create user');
+    });
+  });
+
+  describe('getUserById', () => {
+    it('should return user by id', async () => {
+      const mockUser = {
+        id: 1,
+        username: 'testuser',
+        roles: []
+      } as UserEntity;
+
+      vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);
+
+      const result = await userService.getUserById(1);
+
+      expect(mockUserRepository.findOne).toHaveBeenCalledWith({
+        where: { id: 1 },
+        relations: ['roles']
+      });
+      expect(result).toEqual(mockUser);
+    });
+
+    it('should return null when user not found', async () => {
+      vi.mocked(mockUserRepository.findOne).mockResolvedValue(null);
+
+      const result = await userService.getUserById(999);
+
+      expect(result).toBeNull();
+    });
+  });
+
+  describe('getUserByUsername', () => {
+    it('should return user by username', async () => {
+      const mockUser = {
+        id: 1,
+        username: 'testuser',
+        roles: []
+      } as UserEntity;
+
+      vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);
+
+      const result = await userService.getUserByUsername('testuser');
+
+      expect(mockUserRepository.findOne).toHaveBeenCalledWith({
+        where: { username: 'testuser' },
+        relations: ['roles']
+      });
+      expect(result).toEqual(mockUser);
+    });
+  });
+
+  describe('updateUser', () => {
+    it('should update user with hashed password', async () => {
+      const updateData = {
+        password: 'newpassword',
+        email: 'new@example.com'
+      };
+
+      const updatedUser = {
+        id: 1,
+        username: 'testuser',
+        password: 'hashed_newpassword',
+        email: 'new@example.com',
+        roles: []
+      } as UserEntity;
+
+      vi.mocked(mockUserRepository.update).mockResolvedValue({ affected: 1 } as any);
+      vi.mocked(mockUserRepository.findOne).mockResolvedValue(updatedUser);
+
+      const result = await userService.updateUser(1, updateData);
+
+      expect(mockUserRepository.update).toHaveBeenCalledWith(1, {
+        ...updateData,
+        password: expect.not.stringMatching('newpassword') // Password should be hashed
+      });
+      expect(result).toEqual(updatedUser);
+    });
+  });
+
+  describe('deleteUser', () => {
+    it('should delete user successfully', async () => {
+      vi.mocked(mockUserRepository.delete).mockResolvedValue({ affected: 1 } as any);
+
+      const result = await userService.deleteUser(1);
+
+      expect(mockUserRepository.delete).toHaveBeenCalledWith(1);
+      expect(result).toBe(true);
+    });
+
+    it('should return false when user not found', async () => {
+      vi.mocked(mockUserRepository.delete).mockResolvedValue({ affected: 0 } as any);
+
+      const result = await userService.deleteUser(999);
+
+      expect(result).toBe(false);
+    });
+  });
+
+  describe('assignRoles', () => {
+    it('should assign roles to user', async () => {
+      const mockUser = {
+        id: 1,
+        username: 'testuser',
+        roles: []
+      } as UserEntity;
+
+      const mockRoles = [
+        { id: 1, name: 'admin' } as Role,
+        { id: 2, name: 'user' } as Role
+      ];
+
+      vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);
+      vi.mocked(mockRoleRepository.findByIds).mockResolvedValue(mockRoles);
+      vi.mocked(mockUserRepository.save).mockResolvedValue({
+        ...mockUser,
+        roles: mockRoles
+      } as UserEntity);
+
+      const result = await userService.assignRoles(1, [1, 2]);
+
+      expect(mockRoleRepository.findByIds).toHaveBeenCalledWith([1, 2]);
+      expect(mockUserRepository.save).toHaveBeenCalledWith({
+        ...mockUser,
+        roles: mockRoles
+      });
+      expect(result?.roles).toEqual(mockRoles);
+    });
+
+    it('should return null when user not found', async () => {
+      vi.mocked(mockUserRepository.findOne).mockResolvedValue(null);
+
+      const result = await userService.assignRoles(999, [1, 2]);
+
+      expect(result).toBeNull();
+    });
+  });
+
+  describe('getUsers', () => {
+    it('should return all users', async () => {
+      const mockUsers = [
+        { id: 1, username: 'user1', roles: [] },
+        { id: 2, username: 'user2', roles: [] }
+      ] as UserEntity[];
+
+      vi.mocked(mockUserRepository.find).mockResolvedValue(mockUsers);
+
+      const result = await userService.getUsers();
+
+      expect(mockUserRepository.find).toHaveBeenCalledWith({
+        relations: ['roles']
+      });
+      expect(result).toEqual(mockUsers);
+    });
+  });
+
+  describe('getUserByAccount', () => {
+    it('should return user by username or email', async () => {
+      const mockUser = {
+        id: 1,
+        username: 'testuser',
+        email: 'test@example.com',
+        roles: []
+      } as UserEntity;
+
+      vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);
+
+      const result = await userService.getUserByAccount('testuser');
+
+      expect(mockUserRepository.findOne).toHaveBeenCalledWith({
+        where: [{ username: 'testuser' }, { email: 'testuser' }],
+        relations: ['roles']
+      });
+      expect(result).toEqual(mockUser);
+    });
+  });
+});