import { BadRequestException } from '@nestjs/common'; import { SchedulesService } from './schedules.service'; import { ClassroomStatus } from '../entities'; function mockQueryBuilder(results: T[] = []) { const qb = { where: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), addOrderBy: jest.fn().mockReturnThis(), getMany: jest.fn().mockResolvedValue(results), }; return qb; } describe('SchedulesService — boundary conditions', () => { const validDto = { classId: 1, classroomId: 2, weekDay: 1, startTime: '09:00', endTime: '10:00', startDate: '2026-07-01', endDate: '2026-07-31', subject: '数学', teacherId: 1, }; /** Minimal service where only scheduleRepo.create is mocked, used for early-rejection tests. */ function makeMinimalService() { const scheduleRepo = { create: jest.fn() }; return { service: new SchedulesService( scheduleRepo as never, {} as never, {} as never, {} as never, {} as never, {} as never, ), scheduleRepo, }; } /** Full service where all downstream mocks are wired so create can succeed end-to-end. */ function makeFullService() { const scheduleRepo = { createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder([])), create: jest.fn(), save: jest.fn(), findOne: jest.fn(), }; const classroomRepo = { findOne: jest.fn().mockResolvedValue({ id: 2, status: ClassroomStatus.AVAILABLE, }), }; const classTeacherRepo = { findOne: jest.fn().mockResolvedValue({ classId: 1, userId: 1, }), find: jest.fn().mockResolvedValue([]), }; const rentalRepo = { createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder([])), }; return { service: new SchedulesService( scheduleRepo as never, {} as never, classroomRepo as never, rentalRepo as never, classTeacherRepo as never, {} as never, ), scheduleRepo, }; } it('create rejects reversed startTime/endTime before repository access', async () => { const { service, scheduleRepo } = makeMinimalService(); await expect( service.create({ ...validDto, startTime: '14:00', endTime: '13:00' }), ).rejects.toThrow(BadRequestException); await expect( service.create({ ...validDto, startTime: '14:00', endTime: '13:00' }), ).rejects.toThrow('上课时间不能晚于下课时间'); // Validation short-circuits before any repository call. expect(scheduleRepo.create).not.toHaveBeenCalled(); }); it('create allows startTime < endTime and proceeds through full create flow', async () => { const { service, scheduleRepo } = makeFullService(); const savedSchedule = { id: 1, ...validDto, startTime: '09:00', endTime: '12:00', status: 'active', }; scheduleRepo.create.mockReturnValue(savedSchedule); scheduleRepo.save.mockResolvedValue(savedSchedule); scheduleRepo.findOne.mockResolvedValue(savedSchedule); const result = await service.create({ ...validDto, startTime: '09:00', endTime: '12:00', }); // Should not throw during validation. expect(result).toMatchObject({ startTime: '09:00', endTime: '12:00' }); expect(scheduleRepo.create).toHaveBeenCalled(); }); it('create rejects equal startTime/endTime before repository access', async () => { const { service, scheduleRepo } = makeMinimalService(); await expect( service.create({ ...validDto, startTime: '09:00', endTime: '09:00' }), ).rejects.toThrow(BadRequestException); await expect( service.create({ ...validDto, startTime: '09:00', endTime: '09:00' }), ).rejects.toThrow('上课时间和下课时间不能相同'); // Validation short-circuits before any repository call. expect(scheduleRepo.create).not.toHaveBeenCalled(); }); });