- 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
138 lines
4.0 KiB
TypeScript
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();
|
|
});
|
|
});
|