import { describe, it, expect, vi, beforeEach } from 'vitest'; import { DataSource, Repository, ObjectLiteral } from 'typeorm'; import { GenericCrudService, UserTrackingOptions, RelationFieldOptions } from '../../src/services/generic-crud.service'; // 测试实体类 class TestEntity implements ObjectLiteral { id!: number; name!: string; createdBy?: string; updatedBy?: string; userId?: string; } // 测试关联实体类 class RelatedEntity implements ObjectLiteral { id!: number; name!: string; } describe('GenericCrudService', () => { let mockDataSource: DataSource; let mockRepository: Repository; let crudService: GenericCrudService; beforeEach(() => { // 创建模拟数据源和仓库 mockRepository = { createQueryBuilder: vi.fn().mockReturnValue({ leftJoinAndSelect: vi.fn().mockReturnThis(), andWhere: vi.fn().mockReturnThis(), orderBy: vi.fn().mockReturnThis(), skip: vi.fn().mockReturnThis(), take: vi.fn().mockReturnThis(), getManyAndCount: vi.fn().mockResolvedValue([[], 0]) }), findOne: vi.fn().mockResolvedValue(null), create: vi.fn().mockImplementation((data) => ({ ...data, id: 1 })), save: vi.fn().mockImplementation((entity) => Promise.resolve(entity)), update: vi.fn().mockResolvedValue({ affected: 1 }), delete: vi.fn().mockResolvedValue({ affected: 1 }) } as any; mockDataSource = { getRepository: vi.fn().mockReturnValue(mockRepository) } as any; crudService = new GenericCrudService(mockDataSource, TestEntity); }); describe('构造函数', () => { it('应该正确初始化仓库', () => { expect(mockDataSource.getRepository).toHaveBeenCalledWith(TestEntity); }); it('应该支持用户跟踪选项', () => { const userTrackingOptions: UserTrackingOptions = { createdByField: 'creator', updatedByField: 'updater', userIdField: 'ownerId' }; const serviceWithTracking = new GenericCrudService( mockDataSource, TestEntity, { userTracking: userTrackingOptions } ); expect(serviceWithTracking).toBeDefined(); }); it('应该支持关联字段选项', () => { const relationFields: RelationFieldOptions = { relatedIds: { relationName: 'relatedEntities', targetEntity: RelatedEntity } }; const serviceWithRelations = new GenericCrudService( mockDataSource, TestEntity, { relationFields } ); expect(serviceWithRelations).toBeDefined(); }); }); describe('getList 方法', () => { it('应该调用查询构建器获取列表', async () => { const mockQueryBuilder = { leftJoinAndSelect: vi.fn().mockReturnThis(), andWhere: vi.fn().mockReturnThis(), orderBy: vi.fn().mockReturnThis(), skip: vi.fn().mockReturnThis(), take: vi.fn().mockReturnThis(), getManyAndCount: vi.fn().mockResolvedValue([[], 0]) }; vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder as any); const [data, total] = await crudService.getList(1, 10); expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('entity'); expect(mockQueryBuilder.skip).toHaveBeenCalledWith(0); expect(mockQueryBuilder.take).toHaveBeenCalledWith(10); expect(data).toEqual([]); expect(total).toBe(0); }); it('应该支持关键词搜索', async () => { const mockQueryBuilder = { leftJoinAndSelect: vi.fn().mockReturnThis(), andWhere: vi.fn().mockReturnThis(), orderBy: vi.fn().mockReturnThis(), skip: vi.fn().mockReturnThis(), take: vi.fn().mockReturnThis(), getManyAndCount: vi.fn().mockResolvedValue([[], 0]) }; vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder as any); await crudService.getList(1, 10, 'test', ['name']); expect(mockQueryBuilder.andWhere).toHaveBeenCalled(); }); }); describe('getById 方法', () => { it('应该根据ID获取实体', async () => { const testEntity = { id: 1, name: 'Test' }; vi.mocked(mockRepository.findOne).mockResolvedValue(testEntity as any); const result = await crudService.getById(1); expect(mockRepository.findOne).toHaveBeenCalledWith({ where: { id: 1 }, relations: [] }); expect(result).toEqual(testEntity); }); it('应该支持关联关系', async () => { await crudService.getById(1, ['relatedEntity']); expect(mockRepository.findOne).toHaveBeenCalledWith({ where: { id: 1 }, relations: ['relatedEntity'] }); }); }); describe('create 方法', () => { it('应该创建新实体', async () => { const createData = { name: 'New Entity' }; const savedEntity = { id: 1, ...createData }; vi.mocked(mockRepository.create).mockReturnValue(savedEntity as any); vi.mocked(mockRepository.save).mockResolvedValue(savedEntity as any); const result = await crudService.create(createData); expect(mockRepository.create).toHaveBeenCalledWith(createData); expect(mockRepository.save).toHaveBeenCalledWith(savedEntity); expect(result).toEqual(savedEntity); }); it('应该支持用户跟踪', async () => { const userTrackingOptions: UserTrackingOptions = { createdByField: 'createdBy', updatedByField: 'updatedBy' }; const serviceWithTracking = new GenericCrudService( mockDataSource, TestEntity, { userTracking: userTrackingOptions } ); const createData = { name: 'New Entity' }; const savedEntity = { id: 1, ...createData, createdBy: 'user123', updatedBy: 'user123' }; vi.mocked(mockRepository.create).mockReturnValue(savedEntity as any); vi.mocked(mockRepository.save).mockResolvedValue(savedEntity as any); await serviceWithTracking.create(createData, 'user123'); expect(mockRepository.create).toHaveBeenCalledWith({ ...createData, createdBy: 'user123', updatedBy: 'user123', userId: 'user123' }); }); }); describe('update 方法', () => { it('应该更新现有实体', async () => { const updateData = { name: 'Updated Entity' }; const existingEntity = { id: 1, name: 'Original Entity' }; const updatedEntity = { ...existingEntity, ...updateData }; vi.mocked(mockRepository.findOne).mockResolvedValue(existingEntity as any); vi.mocked(mockRepository.save).mockResolvedValue(updatedEntity as any); const result = await crudService.update(1, updateData); expect(mockRepository.update).toHaveBeenCalledWith(1, updateData); expect(mockRepository.findOne).toHaveBeenCalledWith({ where: { id: 1 }, relations: [] }); expect(result).toEqual(updatedEntity); }); it('应该返回 null 当实体不存在时', async () => { vi.mocked(mockRepository.findOne).mockResolvedValue(null); const result = await crudService.update(999, { name: 'Updated' }); expect(result).toBeNull(); }); }); describe('delete 方法', () => { it('应该删除实体', async () => { vi.mocked(mockRepository.delete).mockResolvedValue({ affected: 1 } as any); const result = await crudService.delete(1); expect(mockRepository.delete).toHaveBeenCalledWith(1); expect(result).toBe(true); }); it('应该返回 false 当实体不存在时', async () => { vi.mocked(mockRepository.delete).mockResolvedValue({ affected: 0 } as any); const result = await crudService.delete(999); expect(result).toBe(false); }); }); describe('createQueryBuilder 方法', () => { it('应该返回查询构建器', () => { const mockQueryBuilder = {} as any; vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder); const result = crudService.createQueryBuilder('test'); expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('test'); expect(result).toBe(mockQueryBuilder); }); it('应该使用默认别名', () => { const mockQueryBuilder = {} as any; vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder); crudService.createQueryBuilder(); expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('entity'); }); }); });