fix: audit remediation — SSE user scoping, FK transactional safety, UI error handling

- 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>
This commit is contained in:
2026-07-12 22:59:03 +08:00
parent b6fca99390
commit cc4f4dae4e
69 changed files with 6262 additions and 1980 deletions

View File

@@ -39,3 +39,67 @@ describe('SchedulesService — teacher class scope', () => {
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(),
);
});
});