forked from wangziqi/gongxue-base
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
This commit is contained in:
137
apps/server/src/schedules/schedules.boundaries.spec.ts
Normal file
137
apps/server/src/schedules/schedules.boundaries.spec.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user