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:
105
apps/admin/src/auth/menu-policy.integration.test.ts
Normal file
105
apps/admin/src/auth/menu-policy.integration.test.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildMenu, collectMenuPaths, findRoleAwareLandingPath } from './menu-policy';
|
||||
|
||||
const teacherPermissions = [
|
||||
'teacher-workspace:view',
|
||||
'schedule:view',
|
||||
'attendance:view',
|
||||
'notification:view',
|
||||
];
|
||||
const academicPermissions = [
|
||||
'dashboard:view',
|
||||
'student:view',
|
||||
'class:view',
|
||||
'teacher:view',
|
||||
'schedule:view',
|
||||
'attendance:view',
|
||||
'classroom:view',
|
||||
'notification:view',
|
||||
];
|
||||
const accommodationPermissions = [
|
||||
'dashboard:view',
|
||||
'room:view',
|
||||
'occupancy:view',
|
||||
'expense:view',
|
||||
'bill:view',
|
||||
'deposit:view',
|
||||
'notification:view',
|
||||
];
|
||||
const systemPermissions = [
|
||||
'user:view',
|
||||
'role:view',
|
||||
'log:view',
|
||||
'integration:read',
|
||||
'ai:config:read',
|
||||
'notification:view',
|
||||
];
|
||||
|
||||
describe('role-aware menu policy', () => {
|
||||
it('builds a teacher flow without global student or class management', () => {
|
||||
const menu = buildMenu(['任课老师'], teacherPermissions);
|
||||
expect(menu.map((item) => item.label)).toEqual(['教学工作', '通知中心']);
|
||||
expect(collectMenuPaths(menu)).toEqual([
|
||||
'/teacher-workspace',
|
||||
'/schedules',
|
||||
'/attendance',
|
||||
'/notifications',
|
||||
]);
|
||||
expect(findRoleAwareLandingPath(['任课老师'], teacherPermissions)).toBe(
|
||||
'/teacher-workspace',
|
||||
);
|
||||
});
|
||||
|
||||
it('places schedules and attendance only once in academic management', () => {
|
||||
const menu = buildMenu(['教务管理员'], academicPermissions);
|
||||
const paths = collectMenuPaths(menu);
|
||||
expect(menu.map((item) => item.label)).toEqual(['数据面板', '教务管理', '通知中心']);
|
||||
expect(paths.filter((path) => path === '/schedules')).toHaveLength(1);
|
||||
expect(paths.filter((path) => path === '/attendance')).toHaveLength(1);
|
||||
expect(paths).not.toContain('/teacher-workspace');
|
||||
});
|
||||
|
||||
it('keeps accommodation billing in one business workspace', () => {
|
||||
const menu = buildMenu(['住宿运营管理员'], accommodationPermissions);
|
||||
expect(menu.map((item) => item.label)).toEqual(['数据面板', '住宿运营', '通知中心']);
|
||||
expect(collectMenuPaths(menu)).toEqual([
|
||||
'/dashboard',
|
||||
'/room-visual',
|
||||
'/rooms',
|
||||
'/occupancies',
|
||||
'/expenses',
|
||||
'/bills',
|
||||
'/deposits',
|
||||
'/notifications',
|
||||
]);
|
||||
});
|
||||
|
||||
it('lands system administrators on account management rather than notifications', () => {
|
||||
expect(findRoleAwareLandingPath(['系统管理员'], systemPermissions)).toBe('/users');
|
||||
});
|
||||
|
||||
it('builds a super-admin menu with unique routes and no teacher workspace', () => {
|
||||
const allPermissions = [
|
||||
...academicPermissions,
|
||||
...accommodationPermissions,
|
||||
...systemPermissions,
|
||||
'rental:view',
|
||||
'organization:view',
|
||||
];
|
||||
const menu = buildMenu(['超级管理员'], [...new Set(allPermissions)]);
|
||||
const paths = collectMenuPaths(menu);
|
||||
expect(new Set(paths).size).toBe(paths.length);
|
||||
expect(paths).not.toContain('/teacher-workspace');
|
||||
expect(paths.filter((path) => path === '/attendance')).toHaveLength(1);
|
||||
expect(paths.filter((path) => path === '/schedules')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('deduplicates routes when an account has multiple business roles', () => {
|
||||
const menu = buildMenu(
|
||||
['教务管理员', '住宿运营管理员'],
|
||||
[...academicPermissions, ...accommodationPermissions],
|
||||
);
|
||||
const paths = collectMenuPaths(menu);
|
||||
expect(new Set(paths).size).toBe(paths.length);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user