- H4: scoped SSE import progress to exact userId match; non-HTTP events excluded from all subscribers - H2: moved PRAGMA foreign_key_check inside SQLite transaction before COMMIT; violations rollback preserving old tables - M1: removed dead axios-style error branch from extractErrorMessage (interceptor already unwraps) - M2: split handleSave try/catch — save errors vs reload errors shown distinctly - M3: added provider field validation before AI config test request - Added SSE scoping regression tests (import service + controller) - Added FK check failure rollback test (database-migrations.spec) - Updated controller spec expectations for userId parameter Co-authored-by: Code Review <branch-review>
106 lines
3.1 KiB
TypeScript
106 lines
3.1 KiB
TypeScript
import { SchedulesService } from './schedules.service';
|
|
|
|
const createQb = () => ({
|
|
andWhere: jest.fn().mockReturnThis(),
|
|
orderBy: jest.fn().mockReturnThis(),
|
|
addOrderBy: jest.fn().mockReturnThis(),
|
|
getMany: jest.fn().mockResolvedValue([]),
|
|
});
|
|
|
|
describe('SchedulesService — teacher class scope', () => {
|
|
it('filters schedule list to assigned classes when no class filter is selected', async () => {
|
|
const qb = createQb();
|
|
const scheduleRepo = { createQueryBuilder: jest.fn().mockReturnValue(qb) };
|
|
const service = new SchedulesService(
|
|
scheduleRepo as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never,
|
|
);
|
|
|
|
await service.findAll({}, [3, 5]);
|
|
|
|
expect(qb.andWhere).toHaveBeenCalledWith('cs.classId IN (:...accessibleClassIds)', {
|
|
accessibleClassIds: [3, 5],
|
|
});
|
|
});
|
|
|
|
it('returns no schedules when teacher has no assigned classes', async () => {
|
|
const qb = createQb();
|
|
const scheduleRepo = { createQueryBuilder: jest.fn().mockReturnValue(qb) };
|
|
const service = new SchedulesService(
|
|
scheduleRepo as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never,
|
|
);
|
|
|
|
await expect(service.findAll({}, [])).resolves.toEqual([]);
|
|
expect(qb.getMany).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('SchedulesService — shared classroom occupancy visibility', () => {
|
|
it('shows other classes as masked busy blocks while preserving assigned-class details', async () => {
|
|
const qb = createQb();
|
|
qb.getMany.mockResolvedValue([
|
|
{
|
|
id: 1,
|
|
classId: 3,
|
|
classroomId: 10,
|
|
weekDay: 1,
|
|
startTime: '09:00',
|
|
endTime: '10:00',
|
|
startDate: '2026-07-01',
|
|
endDate: '2026-07-31',
|
|
subject: '数学',
|
|
teacherId: 8,
|
|
scheduleType: 'INTERNAL',
|
|
status: 'active',
|
|
notes: '本班备注',
|
|
},
|
|
{
|
|
id: 2,
|
|
classId: 99,
|
|
classroomId: 10,
|
|
weekDay: 1,
|
|
startTime: '10:00',
|
|
endTime: '11:00',
|
|
startDate: '2026-07-01',
|
|
endDate: '2026-07-31',
|
|
subject: '其他班隐私科目',
|
|
teacherId: 9,
|
|
scheduleType: 'INTERNAL',
|
|
status: 'active',
|
|
notes: '其他班备注',
|
|
},
|
|
]);
|
|
const service = new SchedulesService(
|
|
{ createQueryBuilder: jest.fn().mockReturnValue(qb) } as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never,
|
|
);
|
|
|
|
const result = await service.getWeeklyView({}, [3]);
|
|
const blocks = result[10][1];
|
|
|
|
expect(blocks[0]).toEqual(expect.objectContaining({ subject: '数学', canViewDetails: true }));
|
|
expect(blocks[1]).toEqual(
|
|
expect.objectContaining({
|
|
subject: '已占用',
|
|
classId: null,
|
|
teacherId: null,
|
|
notes: null,
|
|
canViewDetails: false,
|
|
}),
|
|
);
|
|
expect(JSON.stringify(blocks[1])).not.toContain('其他班隐私科目');
|
|
expect(JSON.stringify(blocks[1])).not.toContain('其他班备注');
|
|
expect(qb.andWhere).not.toHaveBeenCalledWith(
|
|
'cs.classId IN (:...accessibleClassIds)',
|
|
expect.anything(),
|
|
);
|
|
});
|
|
});
|