user.service.test.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import { describe, it, expect, beforeEach, vi } from 'vitest';
  2. import { UserService } from '../../src/services/user.service';
  3. import { UserEntity } from '../../src/entities/user.entity';
  4. import { Role } from '../../src/entities/role.entity';
  5. import { DataSource, Repository } from 'typeorm';
  6. // Mock DataSource
  7. const mockDataSource = {
  8. getRepository: vi.fn()
  9. } as unknown as DataSource;
  10. // Mock repositories
  11. const mockUserRepository = {
  12. create: vi.fn(),
  13. save: vi.fn(),
  14. findOne: vi.fn(),
  15. update: vi.fn(),
  16. delete: vi.fn(),
  17. find: vi.fn(),
  18. findByIds: vi.fn()
  19. } as unknown as Repository<UserEntity>;
  20. const mockRoleRepository = {
  21. findByIds: vi.fn()
  22. } as unknown as Repository<Role>;
  23. describe('UserService', () => {
  24. let userService: UserService;
  25. beforeEach(() => {
  26. vi.clearAllMocks();
  27. // Setup mock repositories
  28. vi.mocked(mockDataSource.getRepository)
  29. .mockReturnValueOnce(mockUserRepository)
  30. .mockReturnValueOnce(mockRoleRepository);
  31. userService = new UserService(mockDataSource);
  32. });
  33. describe('createUser', () => {
  34. it('should create a user with hashed password', async () => {
  35. const userData = {
  36. username: 'testuser',
  37. password: 'password123',
  38. email: 'test@example.com'
  39. };
  40. const mockUser = {
  41. id: 1,
  42. ...userData,
  43. password: 'hashed_password'
  44. } as UserEntity;
  45. vi.mocked(mockUserRepository.create).mockReturnValue(mockUser);
  46. vi.mocked(mockUserRepository.save).mockResolvedValue(mockUser);
  47. const result = await userService.createUser(userData);
  48. expect(mockUserRepository.create).toHaveBeenCalledWith({
  49. ...userData,
  50. password: expect.not.stringMatching('password123') // Password should be hashed
  51. });
  52. expect(mockUserRepository.save).toHaveBeenCalledWith(mockUser);
  53. expect(result).toEqual(mockUser);
  54. });
  55. it('should throw error when creation fails', async () => {
  56. const userData = {
  57. username: 'testuser',
  58. password: 'password123'
  59. };
  60. vi.mocked(mockUserRepository.create).mockImplementation(() => {
  61. throw new Error('Database error');
  62. });
  63. await expect(userService.createUser(userData)).rejects.toThrow('Failed to create user');
  64. });
  65. });
  66. describe('getUserById', () => {
  67. it('should return user by id', async () => {
  68. const mockUser = {
  69. id: 1,
  70. username: 'testuser',
  71. roles: []
  72. } as UserEntity;
  73. vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);
  74. const result = await userService.getUserById(1);
  75. expect(mockUserRepository.findOne).toHaveBeenCalledWith({
  76. where: { id: 1 },
  77. relations: ['roles']
  78. });
  79. expect(result).toEqual(mockUser);
  80. });
  81. it('should return null when user not found', async () => {
  82. vi.mocked(mockUserRepository.findOne).mockResolvedValue(null);
  83. const result = await userService.getUserById(999);
  84. expect(result).toBeNull();
  85. });
  86. });
  87. describe('getUserByUsername', () => {
  88. it('should return user by username', async () => {
  89. const mockUser = {
  90. id: 1,
  91. username: 'testuser',
  92. roles: []
  93. } as UserEntity;
  94. vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);
  95. const result = await userService.getUserByUsername('testuser');
  96. expect(mockUserRepository.findOne).toHaveBeenCalledWith({
  97. where: { username: 'testuser' },
  98. relations: ['roles']
  99. });
  100. expect(result).toEqual(mockUser);
  101. });
  102. });
  103. describe('updateUser', () => {
  104. it('should update user with hashed password', async () => {
  105. const updateData = {
  106. password: 'newpassword',
  107. email: 'new@example.com'
  108. };
  109. const updatedUser = {
  110. id: 1,
  111. username: 'testuser',
  112. password: 'hashed_newpassword',
  113. email: 'new@example.com',
  114. roles: []
  115. } as UserEntity;
  116. vi.mocked(mockUserRepository.update).mockResolvedValue({ affected: 1 } as any);
  117. vi.mocked(mockUserRepository.findOne).mockResolvedValue(updatedUser);
  118. const result = await userService.updateUser(1, updateData);
  119. expect(mockUserRepository.update).toHaveBeenCalledWith(1, {
  120. ...updateData,
  121. password: expect.not.stringMatching('newpassword') // Password should be hashed
  122. });
  123. expect(result).toEqual(updatedUser);
  124. });
  125. });
  126. describe('deleteUser', () => {
  127. it('should delete user successfully', async () => {
  128. vi.mocked(mockUserRepository.delete).mockResolvedValue({ affected: 1 } as any);
  129. const result = await userService.deleteUser(1);
  130. expect(mockUserRepository.delete).toHaveBeenCalledWith(1);
  131. expect(result).toBe(true);
  132. });
  133. it('should return false when user not found', async () => {
  134. vi.mocked(mockUserRepository.delete).mockResolvedValue({ affected: 0 } as any);
  135. const result = await userService.deleteUser(999);
  136. expect(result).toBe(false);
  137. });
  138. });
  139. describe('assignRoles', () => {
  140. it('should assign roles to user', async () => {
  141. const mockUser = {
  142. id: 1,
  143. username: 'testuser',
  144. roles: []
  145. } as UserEntity;
  146. const mockRoles = [
  147. { id: 1, name: 'admin' } as Role,
  148. { id: 2, name: 'user' } as Role
  149. ];
  150. vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);
  151. vi.mocked(mockRoleRepository.findByIds).mockResolvedValue(mockRoles);
  152. vi.mocked(mockUserRepository.save).mockResolvedValue({
  153. ...mockUser,
  154. roles: mockRoles
  155. } as UserEntity);
  156. const result = await userService.assignRoles(1, [1, 2]);
  157. expect(mockRoleRepository.findByIds).toHaveBeenCalledWith([1, 2]);
  158. expect(mockUserRepository.save).toHaveBeenCalledWith({
  159. ...mockUser,
  160. roles: mockRoles
  161. });
  162. expect(result?.roles).toEqual(mockRoles);
  163. });
  164. it('should return null when user not found', async () => {
  165. vi.mocked(mockUserRepository.findOne).mockResolvedValue(null);
  166. const result = await userService.assignRoles(999, [1, 2]);
  167. expect(result).toBeNull();
  168. });
  169. });
  170. describe('getUsers', () => {
  171. it('should return all users', async () => {
  172. const mockUsers = [
  173. { id: 1, username: 'user1', roles: [] },
  174. { id: 2, username: 'user2', roles: [] }
  175. ] as UserEntity[];
  176. vi.mocked(mockUserRepository.find).mockResolvedValue(mockUsers);
  177. const result = await userService.getUsers();
  178. expect(mockUserRepository.find).toHaveBeenCalledWith({
  179. relations: ['roles']
  180. });
  181. expect(result).toEqual(mockUsers);
  182. });
  183. });
  184. describe('getUserByAccount', () => {
  185. it('should return user by username or email', async () => {
  186. const mockUser = {
  187. id: 1,
  188. username: 'testuser',
  189. email: 'test@example.com',
  190. roles: []
  191. } as UserEntity;
  192. vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);
  193. const result = await userService.getUserByAccount('testuser');
  194. expect(mockUserRepository.findOne).toHaveBeenCalledWith({
  195. where: [{ username: 'testuser' }, { email: 'testuser' }],
  196. relations: ['roles']
  197. });
  198. expect(result).toEqual(mockUser);
  199. });
  200. });
  201. });