Files
gongxue-base/apps/server/src/schedules/schedules.boundaries.spec.ts
wangziqi 2c0be0f033 test: 为边界条件修复添加测试用例
- rooms: parseRoomNumber 未知格式、楼层0、capacity ?? 测试
- occupancies: capacity undefined/null/0 fail-closed 防守测试
- schedules: startTime > endTime 拒绝测试
- attendance: 重叠检查按时间排序、raw[index] 移除测试
- expenses: 金额零/负/NaN 拒绝、period反转校验测试

5 modules, 40 tests, all passing
2026-07-20 15:30:41 +08:00

138 lines
4.0 KiB
TypeScript

import { BadRequestException } from '@nestjs/common';
import { SchedulesService } from './schedules.service';
import { ClassroomStatus } from '../entities';
function mockQueryBuilder<T>(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();
});
});