generic-crud.service.test.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import { describe, it, expect, vi, beforeEach } from 'vitest';
  2. import { DataSource, Repository, ObjectLiteral } from 'typeorm';
  3. import { GenericCrudService, UserTrackingOptions, RelationFieldOptions } from '../../src/services/generic-crud.service';
  4. // 测试实体类
  5. class TestEntity implements ObjectLiteral {
  6. id!: number;
  7. name!: string;
  8. createdBy?: string;
  9. updatedBy?: string;
  10. userId?: string;
  11. }
  12. // 测试关联实体类
  13. class RelatedEntity implements ObjectLiteral {
  14. id!: number;
  15. name!: string;
  16. }
  17. describe('GenericCrudService', () => {
  18. let mockDataSource: DataSource;
  19. let mockRepository: Repository<TestEntity>;
  20. let crudService: GenericCrudService<TestEntity>;
  21. beforeEach(() => {
  22. // 创建模拟数据源和仓库
  23. mockRepository = {
  24. createQueryBuilder: vi.fn().mockReturnValue({
  25. leftJoinAndSelect: vi.fn().mockReturnThis(),
  26. andWhere: vi.fn().mockReturnThis(),
  27. orderBy: vi.fn().mockReturnThis(),
  28. skip: vi.fn().mockReturnThis(),
  29. take: vi.fn().mockReturnThis(),
  30. getManyAndCount: vi.fn().mockResolvedValue([[], 0])
  31. }),
  32. findOne: vi.fn().mockResolvedValue(null),
  33. create: vi.fn().mockImplementation((data) => ({ ...data, id: 1 })),
  34. save: vi.fn().mockImplementation((entity) => Promise.resolve(entity)),
  35. update: vi.fn().mockResolvedValue({ affected: 1 }),
  36. delete: vi.fn().mockResolvedValue({ affected: 1 })
  37. } as any;
  38. mockDataSource = {
  39. getRepository: vi.fn().mockReturnValue(mockRepository)
  40. } as any;
  41. crudService = new GenericCrudService(mockDataSource, TestEntity);
  42. });
  43. describe('构造函数', () => {
  44. it('应该正确初始化仓库', () => {
  45. expect(mockDataSource.getRepository).toHaveBeenCalledWith(TestEntity);
  46. });
  47. it('应该支持用户跟踪选项', () => {
  48. const userTrackingOptions: UserTrackingOptions = {
  49. createdByField: 'creator',
  50. updatedByField: 'updater',
  51. userIdField: 'ownerId'
  52. };
  53. const serviceWithTracking = new GenericCrudService(
  54. mockDataSource,
  55. TestEntity,
  56. { userTracking: userTrackingOptions }
  57. );
  58. expect(serviceWithTracking).toBeDefined();
  59. });
  60. it('应该支持关联字段选项', () => {
  61. const relationFields: RelationFieldOptions = {
  62. relatedIds: {
  63. relationName: 'relatedEntities',
  64. targetEntity: RelatedEntity
  65. }
  66. };
  67. const serviceWithRelations = new GenericCrudService(
  68. mockDataSource,
  69. TestEntity,
  70. { relationFields }
  71. );
  72. expect(serviceWithRelations).toBeDefined();
  73. });
  74. });
  75. describe('getList 方法', () => {
  76. it('应该调用查询构建器获取列表', async () => {
  77. const mockQueryBuilder = {
  78. leftJoinAndSelect: vi.fn().mockReturnThis(),
  79. andWhere: vi.fn().mockReturnThis(),
  80. orderBy: vi.fn().mockReturnThis(),
  81. skip: vi.fn().mockReturnThis(),
  82. take: vi.fn().mockReturnThis(),
  83. getManyAndCount: vi.fn().mockResolvedValue([[], 0])
  84. };
  85. vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder as any);
  86. const [data, total] = await crudService.getList(1, 10);
  87. expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('entity');
  88. expect(mockQueryBuilder.skip).toHaveBeenCalledWith(0);
  89. expect(mockQueryBuilder.take).toHaveBeenCalledWith(10);
  90. expect(data).toEqual([]);
  91. expect(total).toBe(0);
  92. });
  93. it('应该支持关键词搜索', async () => {
  94. const mockQueryBuilder = {
  95. leftJoinAndSelect: vi.fn().mockReturnThis(),
  96. andWhere: vi.fn().mockReturnThis(),
  97. orderBy: vi.fn().mockReturnThis(),
  98. skip: vi.fn().mockReturnThis(),
  99. take: vi.fn().mockReturnThis(),
  100. getManyAndCount: vi.fn().mockResolvedValue([[], 0])
  101. };
  102. vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder as any);
  103. await crudService.getList(1, 10, 'test', ['name']);
  104. expect(mockQueryBuilder.andWhere).toHaveBeenCalled();
  105. });
  106. });
  107. describe('getById 方法', () => {
  108. it('应该根据ID获取实体', async () => {
  109. const testEntity = { id: 1, name: 'Test' };
  110. vi.mocked(mockRepository.findOne).mockResolvedValue(testEntity as any);
  111. const result = await crudService.getById(1);
  112. expect(mockRepository.findOne).toHaveBeenCalledWith({
  113. where: { id: 1 },
  114. relations: []
  115. });
  116. expect(result).toEqual(testEntity);
  117. });
  118. it('应该支持关联关系', async () => {
  119. await crudService.getById(1, ['relatedEntity']);
  120. expect(mockRepository.findOne).toHaveBeenCalledWith({
  121. where: { id: 1 },
  122. relations: ['relatedEntity']
  123. });
  124. });
  125. });
  126. describe('create 方法', () => {
  127. it('应该创建新实体', async () => {
  128. const createData = { name: 'New Entity' };
  129. const savedEntity = { id: 1, ...createData };
  130. vi.mocked(mockRepository.create).mockReturnValue(savedEntity as any);
  131. vi.mocked(mockRepository.save).mockResolvedValue(savedEntity as any);
  132. const result = await crudService.create(createData);
  133. expect(mockRepository.create).toHaveBeenCalledWith(createData);
  134. expect(mockRepository.save).toHaveBeenCalledWith(savedEntity);
  135. expect(result).toEqual(savedEntity);
  136. });
  137. it('应该支持用户跟踪', async () => {
  138. const userTrackingOptions: UserTrackingOptions = {
  139. createdByField: 'createdBy',
  140. updatedByField: 'updatedBy'
  141. };
  142. const serviceWithTracking = new GenericCrudService(
  143. mockDataSource,
  144. TestEntity,
  145. { userTracking: userTrackingOptions }
  146. );
  147. const createData = { name: 'New Entity' };
  148. const savedEntity = { id: 1, ...createData, createdBy: 'user123', updatedBy: 'user123' };
  149. vi.mocked(mockRepository.create).mockReturnValue(savedEntity as any);
  150. vi.mocked(mockRepository.save).mockResolvedValue(savedEntity as any);
  151. await serviceWithTracking.create(createData, 'user123');
  152. expect(mockRepository.create).toHaveBeenCalledWith({
  153. ...createData,
  154. createdBy: 'user123',
  155. updatedBy: 'user123',
  156. userId: 'user123'
  157. });
  158. });
  159. });
  160. describe('update 方法', () => {
  161. it('应该更新现有实体', async () => {
  162. const updateData = { name: 'Updated Entity' };
  163. const existingEntity = { id: 1, name: 'Original Entity' };
  164. const updatedEntity = { ...existingEntity, ...updateData };
  165. vi.mocked(mockRepository.findOne).mockResolvedValue(existingEntity as any);
  166. vi.mocked(mockRepository.save).mockResolvedValue(updatedEntity as any);
  167. const result = await crudService.update(1, updateData);
  168. expect(mockRepository.update).toHaveBeenCalledWith(1, updateData);
  169. expect(mockRepository.findOne).toHaveBeenCalledWith({
  170. where: { id: 1 },
  171. relations: []
  172. });
  173. expect(result).toEqual(updatedEntity);
  174. });
  175. it('应该返回 null 当实体不存在时', async () => {
  176. vi.mocked(mockRepository.findOne).mockResolvedValue(null);
  177. const result = await crudService.update(999, { name: 'Updated' });
  178. expect(result).toBeNull();
  179. });
  180. });
  181. describe('delete 方法', () => {
  182. it('应该删除实体', async () => {
  183. vi.mocked(mockRepository.delete).mockResolvedValue({ affected: 1 } as any);
  184. const result = await crudService.delete(1);
  185. expect(mockRepository.delete).toHaveBeenCalledWith(1);
  186. expect(result).toBe(true);
  187. });
  188. it('应该返回 false 当实体不存在时', async () => {
  189. vi.mocked(mockRepository.delete).mockResolvedValue({ affected: 0 } as any);
  190. const result = await crudService.delete(999);
  191. expect(result).toBe(false);
  192. });
  193. });
  194. describe('createQueryBuilder 方法', () => {
  195. it('应该返回查询构建器', () => {
  196. const mockQueryBuilder = {} as any;
  197. vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder);
  198. const result = crudService.createQueryBuilder('test');
  199. expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('test');
  200. expect(result).toBe(mockQueryBuilder);
  201. });
  202. it('应该使用默认别名', () => {
  203. const mockQueryBuilder = {} as any;
  204. vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder);
  205. crudService.createQueryBuilder();
  206. expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('entity');
  207. });
  208. });
  209. });