Implement fixed candidate account onboarding
This commit is contained in:
+43
-23
@@ -55,6 +55,7 @@ const schoolAdmin = createClient();
|
||||
const schoolAdmin2 = createClient();
|
||||
const classAdmin = createClient();
|
||||
const candidate = createClient();
|
||||
const selfCandidate = createClient();
|
||||
const anonymous = createClient();
|
||||
|
||||
try {
|
||||
@@ -82,27 +83,13 @@ try {
|
||||
assert.equal(publicHome.response.status, 200);
|
||||
assert.ok(publicHome.data.notices.length >= 3, '公开首页应返回通知');
|
||||
assert.ok(publicHome.data.exams.some(exam => exam.subjects.length > 1), '公开考试应包含多个科目');
|
||||
assert.equal(publicHome.data.selfRegistrationEnabled, false, '自主注册默认应关闭');
|
||||
|
||||
const register = await candidate.request('/api/auth/register', {
|
||||
const closedRegister = await candidate.request('/api/auth/register', {
|
||||
method: 'POST',
|
||||
body: { username: 'test_candidate', password: 'Test12345!', name: '测试考生', gender: '男', idNumber: '320101200801019999', phone: '13900009999', schoolId: 'school_hz1', classId: 'class_hz1_302' }
|
||||
body: { password: 'Test12345!', name: '测试考生', gender: '男', schoolId: 'school_hz1', classId: 'class_hz1_302' }
|
||||
});
|
||||
assert.equal(register.response.status, 201, '考生应可自主注册');
|
||||
|
||||
const loginCandidate = await candidate.request('/api/auth/login', { method: 'POST', body: { username: 'test_candidate', password: 'Test12345!' } });
|
||||
assert.equal(loginCandidate.data.user.role, 'candidate');
|
||||
|
||||
const updateProfile = await candidate.request('/api/candidate/profile', {
|
||||
method: 'PUT',
|
||||
body: { name: '测试考生新名', gender: '男', idNumber: '320101200801019999', phone: '13900009999', email: 'test@example.com', schoolId: 'school_hz1', classId: 'class_hz1_302', address: '海州市测试区 1 号', emergencyContact: '测试家长', emergencyPhone: '13800008888' }
|
||||
});
|
||||
assert.equal(updateProfile.response.status, 200, '考生应可自主维护完整资料');
|
||||
assert.equal(updateProfile.data.profile.status, 'pending', '资料修改后应重新进入审核');
|
||||
const refreshedSession = await candidate.request('/api/auth/me');
|
||||
assert.equal(refreshedSession.data.user.displayName, '测试考生新名', '考生姓名修改后账号显示名应同步');
|
||||
|
||||
const candidateCannotAdmin = await candidate.request('/api/admin/dashboard');
|
||||
assert.equal(candidateCannotAdmin.response.status, 403, '考生不得访问管理接口');
|
||||
assert.equal(closedRegister.response.status, 403, '自主注册关闭时公开注册必须拒绝');
|
||||
|
||||
const loginAdmin = await admin.request('/api/auth/login', { method: 'POST', body: { username: 'admin', password: 'Admin123!' } });
|
||||
assert.equal(loginAdmin.data.user.role, 'admin');
|
||||
@@ -111,6 +98,38 @@ try {
|
||||
assert.equal((await schoolAdmin2.request('/api/auth/login', { method: 'POST', body: { username: 'school_admin_2', password: 'School123!' } })).data.user.adminLevel, 'school');
|
||||
assert.equal((await classAdmin.request('/api/auth/login', { method: 'POST', body: { username: 'class_admin', password: 'Class123!' } })).data.user.adminLevel, 'class');
|
||||
|
||||
const issuedAccount = await schoolAdmin.request('/api/admin/candidate-accounts', { method: 'POST', body: {
|
||||
name: '测试考生', gender: '男', classId: 'class_hz1_302', initialPassword: 'Initial123!'
|
||||
} });
|
||||
assert.equal(issuedAccount.response.status, 201, '校级管理员应能为本校考生创建固定报名号账户');
|
||||
const candidateNumber = issuedAccount.data.candidate.candidateNumber;
|
||||
assert.match(candidateNumber, /^2026-HZ01-M-\d{4}$/, '账户报名号应按当前规则生成');
|
||||
const loginCandidate = await candidate.request('/api/auth/login', { method: 'POST', body: { username: candidateNumber, password: 'Initial123!' } });
|
||||
assert.equal(loginCandidate.data.user.mustChangePassword, true, '学校下发账户首次登录必须修改初始密码');
|
||||
assert.equal((await candidate.request('/api/candidate/dashboard')).response.status, 428, '未修改初始密码前不得进入考生业务');
|
||||
const changedPassword = await candidate.request('/api/auth/change-password', { method: 'POST', body: { currentPassword: 'Initial123!', newPassword: 'Test12345!' } });
|
||||
assert.equal(changedPassword.data.user.mustChangePassword, false, '修改密码后应解除首次登录限制');
|
||||
assert.equal((await candidate.request('/api/candidate/dashboard')).response.status, 428, '未补全个人信息前仍不得进入考试业务');
|
||||
const updateProfile = await candidate.request('/api/candidate/profile', {
|
||||
method: 'PUT',
|
||||
body: { name: '测试考生新名', gender: '男', idNumber: '320101200801019999', nativePlace: '江苏海州', birthDate: '2008-01-01', ethnicity: '汉族', phone: '13900009999', email: 'test@example.com', schoolId: 'school_hz1', classId: 'class_hz1_302', address: '海州市测试区 1 号', postalCode: '222000', guardianName: '测试家长', guardianPhone: '13800008888', emergencyContact: '测试家长', emergencyPhone: '13800008888' }
|
||||
});
|
||||
assert.equal(updateProfile.response.status, 200, '考生应补全包含籍贯、住址、手机、邮箱和班级的完整资料');
|
||||
assert.equal(updateProfile.data.profile.profileCompleted, true, '完整资料提交后应标记完成');
|
||||
assert.equal(updateProfile.data.profile.status, 'pending', '完整资料应进入审核');
|
||||
const refreshedSession = await candidate.request('/api/auth/me');
|
||||
assert.equal(refreshedSession.data.user.displayName, '测试考生新名', '考生姓名修改后账号显示名应同步');
|
||||
const candidateCannotAdmin = await candidate.request('/api/admin/dashboard');
|
||||
assert.equal(candidateCannotAdmin.response.status, 403, '考生不得访问管理接口');
|
||||
|
||||
assert.equal((await admin.request('/api/admin/settings/self-registration', { method: 'PUT', body: { enabled: true } })).response.status, 200, '超级管理员应能开启自主注册');
|
||||
const openHome = await anonymous.request('/api/public/home');
|
||||
assert.equal(openHome.data.selfRegistrationEnabled, true, '公开端应同步自主注册开关');
|
||||
const selfRegister = await selfCandidate.request('/api/auth/register', { method: 'POST', body: { password: 'Self12345!', name: '自主注册考生', gender: '女', schoolId: 'school_hz3', classId: 'class_hz3_301' } });
|
||||
assert.equal(selfRegister.response.status, 201, '开关开启后考生应可自主申请固定报名号');
|
||||
assert.match(selfRegister.data.registrationNumber, /^2026-HZ03-F-\d{4}$/);
|
||||
await admin.request('/api/admin/settings/self-registration', { method: 'PUT', body: { enabled: false } });
|
||||
|
||||
const adminDirectory = await admin.request('/api/admin/admins');
|
||||
assert.ok(adminDirectory.data.admins.filter(item => item.adminLevel === 'school' && item.schoolId === 'school_hz1').length >= 2, '同一学校应支持多个同级管理员');
|
||||
const schoolCenters = await schoolAdmin.request('/api/admin/centers');
|
||||
@@ -203,7 +222,7 @@ try {
|
||||
assert.equal(beforeApproval.response.status, 403, '资料审核前不得报名考试');
|
||||
|
||||
const candidates = await admin.request('/api/admin/candidates');
|
||||
const profile = candidates.data.candidates.find(item => item.username === 'test_candidate');
|
||||
const profile = candidates.data.candidates.find(item => item.candidateNumber === candidateNumber);
|
||||
assert.ok(profile, '管理员应能看到新注册考生');
|
||||
assert.equal(profile.address, '海州市测试区 1 号', '管理员应能审核考生自主填写的完整资料');
|
||||
const schoolCandidates = await schoolAdmin.request('/api/admin/candidates');
|
||||
@@ -245,7 +264,7 @@ try {
|
||||
await schoolAdmin.request(`/api/admin/registrations/${registrationId}`, { method: 'PATCH', body: { status: 'approved', reviewNote: '学校再次复核通过' } });
|
||||
const approveRegistration = await admin.request(`/api/admin/registrations/${registrationId}`, { method: 'PATCH', body: { status: 'approved', reviewNote: '科目与资格终审通过' } });
|
||||
assert.equal(approveRegistration.data.registration.status, 'approved');
|
||||
assert.match(approveRegistration.data.registration.registrationNumber, /^2026-HZ01-M-\d{4}$/, '终审通过后应按年份、学校、性别和流水号生成报名号');
|
||||
assert.equal(approveRegistration.data.registration.registrationNumber, candidateNumber, '考试报名必须复用考生账户的固定报名号');
|
||||
|
||||
const generateAdmit = await admin.request(`/api/admin/registrations/${registrationId}/admit-card`, { method: 'POST', body: { testCenter: '海州市测试中学' } });
|
||||
assert.ok(generateAdmit.data.admitCard.number, '管理员应能生成准考证号');
|
||||
@@ -273,7 +292,8 @@ try {
|
||||
const futureRegistration = await candidate.request('/api/candidate/registrations', { method: 'POST', body: { examId: futureExam.id, subjectIds: [futureExam.subjects[0].id] } });
|
||||
const futureRegistrationId = futureRegistration.data.registration.id;
|
||||
await schoolAdmin.request(`/api/admin/registrations/${futureRegistrationId}`, { method: 'PATCH', body: { status: 'approved', reviewNote: '学校通过' } });
|
||||
await admin.request(`/api/admin/registrations/${futureRegistrationId}`, { method: 'PATCH', body: { status: 'approved', reviewNote: '通过' } });
|
||||
const approvedFutureRegistration = await admin.request(`/api/admin/registrations/${futureRegistrationId}`, { method: 'PATCH', body: { status: 'approved', reviewNote: '通过' } });
|
||||
assert.equal(approvedFutureRegistration.data.registration.registrationNumber, candidateNumber, '同一考生参加不同考试必须保持相同报名号');
|
||||
await admin.request(`/api/admin/registrations/${futureRegistrationId}/admit-card`, { method: 'POST', body: { testCenter: '海州市测试中学' } });
|
||||
const earlyDownload = await candidate.request(`/api/candidate/registrations/${futureRegistrationId}/admit-card`);
|
||||
assert.equal(earlyDownload.response.status, 403, '准考证下载窗口开放前必须拒绝下载');
|
||||
@@ -300,10 +320,10 @@ try {
|
||||
console.log('✓ 公开首页与通知读取');
|
||||
console.log(`✓ SQLite 关系型数据库初始化(${relationalTables.length} 张分表)`);
|
||||
console.log('✓ 超级、校级、班级管理员的数据范围与权限隔离');
|
||||
console.log('✓ 考生自主注册、完整资料维护与两级审批');
|
||||
console.log('✓ 固定报名号账户、首次强制改密、完整资料与注册开关');
|
||||
console.log('✓ 多科目考试创建与考生自主选科报名');
|
||||
console.log('✓ 审批流程设计、同级转交与超级管理员监督退回');
|
||||
console.log('✓ 自定义规则、筛选批量报名号、准考证与下载窗口限制');
|
||||
console.log('✓ 自定义账户号码、旧记录批量同步、准考证与下载窗口限制');
|
||||
console.log('✓ 结构化考点考场档案、变更审批与班级只读边界');
|
||||
console.log('✓ 成绩录入、发布与考生查询');
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user