This commit is contained in:
2026-07-20 20:18:33 +08:00 Unverified
parent 5a33a6bcc4
commit 9a21c7519c
2 changed files with 30 additions and 3 deletions
+7 -2
View File
@@ -1,4 +1,9 @@
export function createSessionManager({ sessions, readDb, sendError }) { export function createSessionManager({ sessions, readDb, sendError }) {
function normalizeUser(user) {
if (user?.role === 'admin' && !user.adminLevel) return { ...user, adminLevel: 'super' };
return user;
}
function parseCookies(request) { function parseCookies(request) {
return Object.fromEntries(String(request.headers.cookie || '').split(';').map(part => part.trim()).filter(Boolean).map(part => { return Object.fromEntries(String(request.headers.cookie || '').split(';').map(part => part.trim()).filter(Boolean).map(part => {
const index = part.indexOf('='); const index = part.indexOf('=');
@@ -15,7 +20,7 @@ export function createSessionManager({ sessions, readDb, sendError }) {
} }
const db = await readDb(); const db = await readDb();
const user = db.users.find(item => item.id === session.userId) || null; const user = db.users.find(item => item.id === session.userId) || null;
return user?.active === false || user?.archivedAt ? null : user; return user?.active === false || user?.archivedAt ? null : normalizeUser(user);
} }
function safeUser(user) { function safeUser(user) {
@@ -23,7 +28,7 @@ export function createSessionManager({ sessions, readDb, sendError }) {
id: user.id, id: user.id,
username: user.username, username: user.username,
role: user.role, role: user.role,
adminLevel: user.adminLevel || null, adminLevel: user.role === 'admin' ? user.adminLevel || 'super' : null,
schoolId: user.schoolId || null, schoolId: user.schoolId || null,
classId: user.classId || null, classId: user.classId || null,
displayName: user.displayName, displayName: user.displayName,
+23 -1
View File
@@ -72,7 +72,18 @@ process.env.DATABASE_CLIENT = 'sqlite';
process.env.SQLITE_PATH = testDb; process.env.SQLITE_PATH = testDb;
const seededTestDatabase = await createDatabase({ const seededTestDatabase = await createDatabase({
root, root,
seed: () => createSeedDatabase({ nowIso: () => new Date().toISOString(), hashPassword }) seed: () => {
const state = createSeedDatabase({ nowIso: () => new Date().toISOString(), hashPassword });
const currentSuperAdmin = state.users.find(user => user.role === 'admin' && user.adminLevel === 'super');
state.users.push({
...currentSuperAdmin,
id: 'admin_legacy_super',
username: 'legacy_super',
adminLevel: null,
displayName: '旧版超级管理员'
});
return state;
}
}); });
await seededTestDatabase.close(); await seededTestDatabase.close();
@@ -116,6 +127,7 @@ function createClient() {
} }
const admin = createClient(); const admin = createClient();
const legacyAdmin = createClient();
const schoolAdmin = createClient(); const schoolAdmin = createClient();
const schoolAdmin2 = createClient(); const schoolAdmin2 = createClient();
const classAdmin = createClient(); const classAdmin = createClient();
@@ -210,6 +222,8 @@ try {
const loginAdmin = await admin.request('/api/auth/login', { method: 'POST', body: { username: 'admin', password: '12345678' } }); const loginAdmin = await admin.request('/api/auth/login', { method: 'POST', body: { username: 'admin', password: '12345678' } });
assert.equal(loginAdmin.data.user.role, 'admin'); assert.equal(loginAdmin.data.user.role, 'admin');
assert.equal(loginAdmin.data.user.adminLevel, 'super', '默认管理员应为超级管理员'); assert.equal(loginAdmin.data.user.adminLevel, 'super', '默认管理员应为超级管理员');
const loginLegacyAdmin = await legacyAdmin.request('/api/auth/login', { method: 'POST', body: { username: 'legacy_super', password: '12345678' } });
assert.equal(loginLegacyAdmin.data.user.adminLevel, 'super', '未保存层级的旧版管理员登录后应规范化为超级管理员');
assert.equal((await schoolAdmin.request('/api/auth/login', { method: 'POST', body: { username: 'school_admin', password: '12345678' } })).data.user.adminLevel, 'school'); assert.equal((await schoolAdmin.request('/api/auth/login', { method: 'POST', body: { username: 'school_admin', password: '12345678' } })).data.user.adminLevel, 'school');
assert.equal((await schoolAdmin2.request('/api/auth/login', { method: 'POST', body: { username: 'school_admin_2', password: '12345678' } })).data.user.adminLevel, 'school'); assert.equal((await schoolAdmin2.request('/api/auth/login', { method: 'POST', body: { username: 'school_admin_2', password: '12345678' } })).data.user.adminLevel, 'school');
assert.equal((await classAdmin.request('/api/auth/login', { method: 'POST', body: { username: 'class_admin', password: '12345678' } })).data.user.adminLevel, 'class'); assert.equal((await classAdmin.request('/api/auth/login', { method: 'POST', body: { username: 'class_admin', password: '12345678' } })).data.user.adminLevel, 'class');
@@ -258,6 +272,9 @@ try {
assert.ok(adminDirectory.data.admins.filter(item => item.adminLevel === 'school' && item.schoolId === 'school_hz1').length >= 2, '同一学校应支持多个同级管理员'); assert.ok(adminDirectory.data.admins.filter(item => item.adminLevel === 'school' && item.schoolId === 'school_hz1').length >= 2, '同一学校应支持多个同级管理员');
const schoolCenters = await schoolAdmin.request('/api/admin/centers'); const schoolCenters = await schoolAdmin.request('/api/admin/centers');
assert.ok(schoolCenters.data.centers.every(item => item.schoolId === 'school_hz1'), '校级管理员只能读取本校考点'); assert.ok(schoolCenters.data.centers.every(item => item.schoolId === 'school_hz1'), '校级管理员只能读取本校考点');
const legacySuperCenters = await legacyAdmin.request('/api/admin/centers');
assert.ok(legacySuperCenters.data.centers.some(item => item.schoolId === 'school_hz3'), '旧版超级管理员应能跨校读取学校维护的正式考点');
assert.ok(legacySuperCenters.data.centers.flatMap(item => item.rooms).some(item => item.centerId === 'center_hz3'), '旧版超级管理员应能读取其他学校维护的结构化考场');
const newCenter = await schoolAdmin.request('/api/admin/centers', { method: 'POST', body: { const newCenter = await schoolAdmin.request('/api/admin/centers', { method: 'POST', body: {
code: 'HZ01-EAST', name: '海州市第一中学东区考点', provinceCode: '320000', cityCode: '320700', districtCode: '320706', address: '测试路 8 号', contact: '0518-12345678', code: 'HZ01-EAST', name: '海州市第一中学东区考点', provinceCode: '320000', cityCode: '320700', districtCode: '320706', address: '测试路 8 号', contact: '0518-12345678',
managerName: '测试负责人', managerPhone: '13800001234', emergencyPhone: '0518-120', gateOpenTime: '07:00', transport: '东门入场', status: 'active', notes: '自动化测试档案', managerName: '测试负责人', managerPhone: '13800001234', emergencyPhone: '0518-120', gateOpenTime: '07:00', transport: '东门入场', status: 'active', notes: '自动化测试档案',
@@ -265,6 +282,9 @@ try {
} }); } });
assert.equal(newCenter.response.status, 202, '新增考点应创建审批申请而不是直接落库'); assert.equal(newCenter.response.status, 202, '新增考点应创建审批申请而不是直接落库');
assert.equal(newCenter.data.changeRequest.schoolId, 'school_hz1', '校级管理员新增考点必须自动归属本校'); assert.equal(newCenter.data.changeRequest.schoolId, 'school_hz1', '校级管理员新增考点必须自动归属本校');
const legacyPendingCenters = await legacyAdmin.request('/api/admin/centers');
const legacyPendingCenter = legacyPendingCenters.data.changeRequests.find(item => item.id === newCenter.data.changeRequest.id);
assert.equal(legacyPendingCenter?.rooms[0]?.code, 'E001', '旧版超级管理员应能读取学校提交的待审批考点与考场快照');
const beforeCenterApproval = await schoolAdmin.request('/api/admin/centers'); const beforeCenterApproval = await schoolAdmin.request('/api/admin/centers');
assert.ok(!beforeCenterApproval.data.centers.some(item => item.code === 'HZ01-EAST'), '考点审批通过前不得进入正式档案'); assert.ok(!beforeCenterApproval.data.centers.some(item => item.code === 'HZ01-EAST'), '考点审批通过前不得进入正式档案');
const approveCenter = await admin.request(`/api/admin/center-change-requests/${newCenter.data.changeRequest.id}`, { method: 'PATCH', body: { status: 'approved', reviewNote: '考务条件核验通过' } }); const approveCenter = await admin.request(`/api/admin/center-change-requests/${newCenter.data.changeRequest.id}`, { method: 'PATCH', body: { status: 'approved', reviewNote: '考务条件核验通过' } });
@@ -273,6 +293,8 @@ try {
const createdCenter = afterCenterApproval.data.centers.find(item => item.code === 'HZ01-EAST'); const createdCenter = afterCenterApproval.data.centers.find(item => item.code === 'HZ01-EAST');
assert.equal(createdCenter.rooms.length, 1, '审批通过后应同时写入结构化考场'); assert.equal(createdCenter.rooms.length, 1, '审批通过后应同时写入结构化考场');
assert.equal(createdCenter.totalCapacity, 30, '考场容量应汇总到考点档案'); assert.equal(createdCenter.totalCapacity, 30, '考场容量应汇总到考点档案');
const legacyApprovedCenter = (await legacyAdmin.request('/api/admin/centers')).data.centers.find(item => item.code === 'HZ01-EAST');
assert.equal(legacyApprovedCenter?.rooms[0]?.code, 'E001', '旧版超级管理员应能读取学校维护并审批生效的考点与考场');
const centerUpdate = await schoolAdmin.request(`/api/admin/centers/${createdCenter.id}`, { method: 'PATCH', body: { const centerUpdate = await schoolAdmin.request(`/api/admin/centers/${createdCenter.id}`, { method: 'PATCH', body: {
...createdCenter, managerName: '变更后负责人', rooms: [ ...createdCenter, managerName: '变更后负责人', rooms: [
...createdCenter.rooms, ...createdCenter.rooms,