|
@@ -1,264 +0,0 @@
|
|
|
-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<TestEntity>;
|
|
|
|
|
- let crudService: GenericCrudService<TestEntity>;
|
|
|
|
|
-
|
|
|
|
|
- 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');
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
-});
|
|
|