import { describe, it, expect, vi, beforeEach } from 'vitest'; import type { Mock } from 'vitest'; import { ClassesService } from '../../src/classes/classes.service.js'; import type { ClassesRepository } from '../../src/classes/classes.repository.js'; import { ValidationError, NotFoundError } from '../../src/shared/errors/application-error.js'; describe('ClassesService', () => { let service: ClassesService; let mockRepository: { create: Mock; findById: Mock; list: Mock; update: Mock; delete: Mock; }; beforeEach(() => { mockRepository = { create: vi.fn(), findById: vi.fn(), list: vi.fn(), update: vi.fn(), delete: vi.fn(), }; // 测试场景:mock 对象通过 unknown 转换为 ClassesRepository(规则允许测试中 as 转换) service = new ClassesService(mockRepository as unknown as ClassesRepository); }); describe('create', () => { it('should create a class successfully', async () => { const dto = { name: 'Class 1A', gradeId: '550e8400-e29b-41d4-a716-446655440000' }; const mockClass = { id: 'cls-1', name: 'Class 1A', gradeId: '550e8400-e29b-41d4-a716-446655440000', createdAt: new Date('2026-01-01'), updatedAt: new Date('2026-01-01'), }; mockRepository.create.mockResolvedValue(mockClass); const result = await service.create(dto); expect(result.id).toBe('cls-1'); expect(mockRepository.create).toHaveBeenCalledWith(expect.objectContaining(dto)); }); }); describe('update', () => { it('should throw ValidationError when no fields provided', async () => { await expect(service.update('cls-1', {})).rejects.toThrow(ValidationError); }); it('should update class successfully', async () => { const mockClass = { id: 'cls-1', name: 'Updated', gradeId: 'g1', createdAt: new Date(), updatedAt: new Date(), }; mockRepository.update.mockResolvedValue(mockClass); const result = await service.update('cls-1', { name: 'Updated' }); expect(result.name).toBe('Updated'); expect(mockRepository.update).toHaveBeenCalledWith('cls-1', { name: 'Updated' }); }); }); describe('getById', () => { it('should throw NotFoundError when class not found', async () => { mockRepository.findById.mockResolvedValue(undefined); await expect(service.getById('not-exist')).rejects.toThrow(NotFoundError); }); }); describe('list', () => { it('should list classes without gradeId filter', async () => { mockRepository.list.mockResolvedValue([]); await service.list(); expect(mockRepository.list).toHaveBeenCalledWith(undefined); }); it('should list classes with gradeId filter', async () => { mockRepository.list.mockResolvedValue([]); await service.list('grade-1'); expect(mockRepository.list).toHaveBeenCalledWith('grade-1'); }); }); describe('delete', () => { it('should throw NotFoundError when class not found', async () => { mockRepository.findById.mockResolvedValue(undefined); await expect(service.delete('not-exist')).rejects.toThrow(NotFoundError); }); it('should delete class', async () => { mockRepository.findById.mockResolvedValue({ id: 'cls-1' }); mockRepository.delete.mockResolvedValue(undefined); await service.delete('cls-1'); expect(mockRepository.delete).toHaveBeenCalledWith('cls-1'); }); }); });