forked from wangziqi/gongxue-base
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:
@@ -1,7 +1,7 @@
|
||||
import { RbacService } from './rbac.service';
|
||||
|
||||
describe('RbacService seedData', () => {
|
||||
it('adds preset permissions to system roles without removing manually granted permissions', async () => {
|
||||
it('migrates the legacy teacher role and replaces broad permissions with the teaching matrix', async () => {
|
||||
const permissions = [
|
||||
{ id: 1, code: 'profile:view', name: '查看个人资料', group: 'profile' },
|
||||
{ id: 2, code: 'notification:view', name: '查看通知', group: 'notification' },
|
||||
@@ -10,27 +10,32 @@ describe('RbacService seedData', () => {
|
||||
{ id: 5, code: 'schedule:view', name: '查看排课', group: 'schedule' },
|
||||
{ id: 6, code: 'attendance:view', name: '查看考勤', group: 'attendance' },
|
||||
{ id: 7, code: 'attendance:create', name: '新增考勤', group: 'attendance' },
|
||||
{ id: 8, code: 'attendance:export', name: '导出考勤', group: 'attendance' },
|
||||
{ id: 8, code: 'teacher-workspace:view', name: '教师工作台', group: 'teacher-workspace' },
|
||||
{ id: 9, code: 'room:view', name: '查看宿舍', group: 'room' },
|
||||
{ id: 10, code: 'schedule:create', name: '新增排课', group: 'schedule' },
|
||||
];
|
||||
const teacherRole = {
|
||||
id: 1,
|
||||
name: '老师',
|
||||
description: '查看和管理本班学生',
|
||||
code: 'teacher',
|
||||
description: '旧角色',
|
||||
isSystem: true,
|
||||
permissions: [permissions[8]],
|
||||
status: 1,
|
||||
permissions: [permissions[2], permissions[3], permissions[8]],
|
||||
};
|
||||
|
||||
const permRepo = {
|
||||
findOne: jest.fn(
|
||||
async ({ where }: any) => permissions.find((p) => p.code === where.code) ?? null,
|
||||
async ({ where }: any) => permissions.find((permission) => permission.code === where.code) ?? null,
|
||||
),
|
||||
create: jest.fn((value) => value),
|
||||
save: jest.fn(async (value) => value),
|
||||
find: jest.fn(async () => permissions),
|
||||
};
|
||||
const roleRepo = {
|
||||
findOne: jest.fn(async ({ where }: any) => (where.name === '老师' ? teacherRole : null)),
|
||||
findOne: jest.fn(async ({ where }: any) =>
|
||||
where.code === 'teacher' || where.name === '老师' ? teacherRole : null,
|
||||
),
|
||||
create: jest.fn((value) => ({ ...value, permissions: [] })),
|
||||
save: jest.fn(async (value) => value),
|
||||
find: jest.fn(async () => [teacherRole]),
|
||||
@@ -50,8 +55,86 @@ describe('RbacService seedData', () => {
|
||||
|
||||
await service.seedData();
|
||||
|
||||
expect(teacherRole.name).toBe('任课老师');
|
||||
expect(teacherRole.permissions.map((permission) => permission.code)).toEqual(
|
||||
expect.arrayContaining(['room:view', 'profile:view', 'student:view', 'attendance:create']),
|
||||
expect.arrayContaining([
|
||||
'profile:view',
|
||||
'teacher-workspace:view',
|
||||
'schedule:view',
|
||||
'attendance:create',
|
||||
]),
|
||||
);
|
||||
expect(teacherRole.permissions.map((permission) => permission.code)).not.toContain(
|
||||
'schedule:create',
|
||||
);
|
||||
expect(teacherRole.permissions.map((permission) => permission.code)).not.toContain('student:view');
|
||||
expect(teacherRole.permissions.map((permission) => permission.code)).not.toContain('class:view');
|
||||
expect(teacherRole.permissions.map((permission) => permission.code)).not.toContain('room:view');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RbacService legacy role consolidation', () => {
|
||||
it('moves users from duplicate accommodation roles before deleting the duplicates', async () => {
|
||||
const permissions = [
|
||||
{ id: 1, code: 'profile:view', name: '查看个人资料', group: 'profile' },
|
||||
{ id: 2, code: 'room:view', name: '查看宿舍', group: 'room' },
|
||||
{ id: 3, code: 'expense:view', name: '查看费用', group: 'expense' },
|
||||
{ id: 4, code: 'student:basic-view', name: '学生基础信息', group: 'student-scope' },
|
||||
];
|
||||
const targetRole: any = {
|
||||
id: 10,
|
||||
name: '住宿运营管理员',
|
||||
code: 'accommodation_operations',
|
||||
description: '',
|
||||
isSystem: true,
|
||||
status: 1,
|
||||
permissions: [],
|
||||
users: [],
|
||||
};
|
||||
const legacyRole: any = {
|
||||
id: 11,
|
||||
name: '财务',
|
||||
code: 'finance',
|
||||
description: '',
|
||||
isSystem: true,
|
||||
status: 1,
|
||||
permissions: [],
|
||||
users: [{ id: 21 }],
|
||||
};
|
||||
const user: any = { id: 21, roles: [legacyRole] };
|
||||
const permRepo = {
|
||||
findOne: jest.fn(async ({ where }: any) => permissions.find((item) => item.code === where.code) ?? null),
|
||||
create: jest.fn((value) => value),
|
||||
save: jest.fn(async (value) => value),
|
||||
find: jest.fn(async () => permissions),
|
||||
};
|
||||
const roleRepo = {
|
||||
findOne: jest.fn(async () => targetRole),
|
||||
create: jest.fn((value) => ({ ...value, permissions: [] })),
|
||||
save: jest.fn(async (value) => value),
|
||||
find: jest.fn(async () => [targetRole, legacyRole]),
|
||||
remove: jest.fn(async (value) => value),
|
||||
};
|
||||
const userRepo = {
|
||||
count: jest.fn(async () => 1),
|
||||
findOne: jest.fn(async () => user),
|
||||
save: jest.fn(async (value) => value),
|
||||
};
|
||||
const service = new RbacService(
|
||||
permRepo as never,
|
||||
roleRepo as never,
|
||||
userRepo as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
);
|
||||
|
||||
await service.seedData();
|
||||
|
||||
expect(user.roles).toEqual([targetRole]);
|
||||
expect(userRepo.save).toHaveBeenCalledWith(user);
|
||||
expect(roleRepo.remove).toHaveBeenCalledWith(legacyRole);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user