测试考勤:覆盖参训状态签到守卫与真实前端策略
This commit is contained in:
1 parent
e2c15e6216
commit
08b8abe778
2 files changed
+344
-151
No files matched your search
@@ -1,29 +1,15 @@
|
||||
import assert from 'node:assert';
|
||||
import {
|
||||
formatAttendanceTag,
|
||||
formatEnrollmentStatusTag,
|
||||
isEnrollmentEligibleForSignIn,
|
||||
computeRosterActionEligibility,
|
||||
resolveSignInFeedback
|
||||
} from '../../src/views/train/checkin/attendancePolicy.js';
|
||||
|
||||
console.log('=== Testing Attendance Sign-in Entry & Roster Semantics ===');
|
||||
console.log('=== Testing Real Production Attendance Policy Helper ===');
|
||||
|
||||
// 1. 测试考勤状态语义映射:未签到 (NO_CHECKIN) 必须严格与缺勤 (0) 区分
|
||||
function formatAttendanceTag(status) {
|
||||
if (status === null || status === undefined) {
|
||||
return { text: '未签到', type: 'info' };
|
||||
}
|
||||
switch (status) {
|
||||
case 0:
|
||||
return { text: '缺勤', type: 'danger' };
|
||||
case 1:
|
||||
return { text: '正常', type: 'success' };
|
||||
case 2:
|
||||
return { text: '迟到', type: 'warning' };
|
||||
case 3:
|
||||
return { text: '早退', type: 'warning' };
|
||||
case 4:
|
||||
return { text: '请假', type: 'primary' };
|
||||
default:
|
||||
return { text: '-', type: '' };
|
||||
}
|
||||
}
|
||||
|
||||
// A. 验证未打卡学员渲染为“未签到”,绝不等于“缺勤”
|
||||
// 1. 考勤状态语义映射:未签到 (NO_CHECKIN) 必须严格与缺勤 (0) 区分
|
||||
{
|
||||
const tagNull = formatAttendanceTag(null);
|
||||
assert.strictEqual(tagNull.text, '未签到');
|
||||
@@ -31,160 +17,142 @@ function formatAttendanceTag(status) {
|
||||
|
||||
const tagUndefined = formatAttendanceTag(undefined);
|
||||
assert.strictEqual(tagUndefined.text, '未签到');
|
||||
assert.strictEqual(tagUndefined.type, 'info');
|
||||
|
||||
const tagAbsent = formatAttendanceTag(0);
|
||||
assert.strictEqual(tagAbsent.text, '缺勤');
|
||||
assert.strictEqual(tagAbsent.type, 'danger');
|
||||
|
||||
// 红线:未签到和缺勤完全不同
|
||||
// 严格区分未签到与缺勤事实
|
||||
assert.notStrictEqual(tagNull.text, tagAbsent.text);
|
||||
assert.notStrictEqual(tagNull.type, tagAbsent.type);
|
||||
console.log('✔ PASS: NO_CHECKIN is strictly distinct from ABSENT (0)');
|
||||
console.log('✔ PASS: Production formatAttendanceTag distinguishes NO_CHECKIN from ABSENT (0)');
|
||||
}
|
||||
|
||||
// 2. 按钮可见性与防重复规则
|
||||
function getRowActionButtons(row) {
|
||||
return {
|
||||
signIn: !!row.canSignIn,
|
||||
signOut: !!row.canSignOut,
|
||||
adjust: !!row.canAdjust
|
||||
};
|
||||
}
|
||||
|
||||
// A. 无 checkin 记录学员:签到按钮可见,签退不可见
|
||||
// 2. 学员参训状态展示标签
|
||||
{
|
||||
const uncheckRow = {
|
||||
enrollmentId: 101,
|
||||
assert.strictEqual(formatEnrollmentStatusTag(0).text, '待报到');
|
||||
assert.strictEqual(formatEnrollmentStatusTag(1).text, '已报到');
|
||||
assert.strictEqual(formatEnrollmentStatusTag(2).text, '请假');
|
||||
assert.strictEqual(formatEnrollmentStatusTag(3).text, '放弃');
|
||||
assert.strictEqual(formatEnrollmentStatusTag(4).text, '已完成');
|
||||
console.log('✔ PASS: Production formatEnrollmentStatusTag maps all lifecycle statuses correctly');
|
||||
}
|
||||
|
||||
// 3. 参训生命周期首次签到资格规则断言
|
||||
{
|
||||
assert.strictEqual(isEnrollmentEligibleForSignIn(0), false, 'status=0 待报到严禁签到');
|
||||
assert.strictEqual(isEnrollmentEligibleForSignIn(1), true, 'status=1 已报到在训允许签到');
|
||||
assert.strictEqual(isEnrollmentEligibleForSignIn(2), true, 'status=2 请假学员当前 Preview Default 允许签到');
|
||||
assert.strictEqual(isEnrollmentEligibleForSignIn(3), false, 'status=3 放弃学员严禁签到');
|
||||
assert.strictEqual(isEnrollmentEligibleForSignIn(4), false, 'status=4 已完成学员严禁签到');
|
||||
assert.strictEqual(isEnrollmentEligibleForSignIn(null), false, 'null 状态 fail closed');
|
||||
assert.strictEqual(isEnrollmentEligibleForSignIn(undefined), false, 'undefined 状态 fail closed');
|
||||
assert.strictEqual(isEnrollmentEligibleForSignIn(99), false, '未知状态 fail closed');
|
||||
console.log('✔ PASS: Production isEnrollmentEligibleForSignIn strictly enforces lifecycle guard');
|
||||
}
|
||||
|
||||
// 4. 生产环境操作按钮可用性 (computeRosterActionEligibility) 覆盖
|
||||
{
|
||||
// A. status=0 待报到 + 无打卡记录 -> 签到按钮不存在
|
||||
const row0 = {
|
||||
enrollmentId: 100,
|
||||
enrollmentStatus: 0,
|
||||
checkinId: null,
|
||||
attendanceStatus: null,
|
||||
signInTime: null,
|
||||
signOutTime: null,
|
||||
canSignIn: true,
|
||||
canSignOut: false,
|
||||
canAdjust: false
|
||||
canSignIn: false
|
||||
};
|
||||
const actions = getRowActionButtons(uncheckRow);
|
||||
assert.strictEqual(actions.signIn, true, '无签到记录学员签到按钮必须可见');
|
||||
assert.strictEqual(actions.signOut, false, '未签到学员不能显示签退');
|
||||
assert.strictEqual(actions.adjust, false, '无考勤记录学员不能显示调整');
|
||||
console.log('✔ PASS: Unchecked student has visible signIn button, no duplicate path');
|
||||
}
|
||||
const act0 = computeRosterActionEligibility(row0);
|
||||
assert.strictEqual(act0.canSignIn, false, 'status=0 待报到学员签到按钮必须不存在');
|
||||
assert.strictEqual(act0.canSignOut, false);
|
||||
assert.strictEqual(act0.canAdjust, false);
|
||||
|
||||
// B. 已有签到记录:签到按钮不可重复执行
|
||||
{
|
||||
const signedInRow = {
|
||||
// B. status=1 已报到 + NO_CHECKIN -> 签到按钮存在
|
||||
const row1 = {
|
||||
enrollmentId: 101,
|
||||
checkinId: 8001,
|
||||
enrollmentStatus: 1,
|
||||
checkinId: null,
|
||||
attendanceStatus: null,
|
||||
signInTime: null,
|
||||
signOutTime: null,
|
||||
canSignIn: true
|
||||
};
|
||||
const act1 = computeRosterActionEligibility(row1);
|
||||
assert.strictEqual(act1.canSignIn, true, 'status=1 已报到学员未签到时签到按钮必须存在');
|
||||
assert.strictEqual(act1.canSignOut, false);
|
||||
assert.strictEqual(act1.canAdjust, false);
|
||||
|
||||
// C. status=2 请假 + NO_CHECKIN -> 签到按钮存在 (Preview Default)
|
||||
const row2 = {
|
||||
enrollmentId: 102,
|
||||
enrollmentStatus: 2,
|
||||
checkinId: null,
|
||||
attendanceStatus: null,
|
||||
signInTime: null,
|
||||
signOutTime: null,
|
||||
canSignIn: true
|
||||
};
|
||||
const act2 = computeRosterActionEligibility(row2);
|
||||
assert.strictEqual(act2.canSignIn, true, 'status=2 请假学员未签到时签到按钮必须存在 (Preview Default)');
|
||||
assert.strictEqual(act2.canSignOut, false);
|
||||
assert.strictEqual(act2.canAdjust, false);
|
||||
|
||||
// D. status=4 已完成 + NO_CHECKIN -> 签到按钮不存在
|
||||
const row4 = {
|
||||
enrollmentId: 104,
|
||||
enrollmentStatus: 4,
|
||||
checkinId: null,
|
||||
attendanceStatus: null,
|
||||
signInTime: null,
|
||||
signOutTime: null,
|
||||
canSignIn: false
|
||||
};
|
||||
const act4 = computeRosterActionEligibility(row4);
|
||||
assert.strictEqual(act4.canSignIn, false, 'status=4 已完成学员签到按钮必须不存在');
|
||||
assert.strictEqual(act4.canSignOut, false);
|
||||
assert.strictEqual(act4.canAdjust, false);
|
||||
|
||||
// E. 已有合法签到记录 (无论 status 为何) -> 签到按钮禁止重复存在
|
||||
const rowSignedIn = {
|
||||
enrollmentId: 101,
|
||||
enrollmentStatus: 1,
|
||||
checkinId: 8888,
|
||||
attendanceStatus: 1,
|
||||
signInTime: '2026-09-21 08:30:00',
|
||||
signInTime: '2026-09-22 08:30:00',
|
||||
signOutTime: null,
|
||||
canSignIn: false,
|
||||
canSignOut: true,
|
||||
canAdjust: true
|
||||
};
|
||||
const actions = getRowActionButtons(signedInRow);
|
||||
assert.strictEqual(actions.signIn, false, '已有合法签到记录学员不得显示签到按钮');
|
||||
assert.strictEqual(actions.signOut, true, '已签到学员允许签退');
|
||||
assert.strictEqual(actions.adjust, true, '已有打卡记录允许人工调整');
|
||||
console.log('✔ PASS: Signed-in student cannot duplicate sign-in');
|
||||
const actSigned = computeRosterActionEligibility(rowSignedIn);
|
||||
assert.strictEqual(actSigned.canSignIn, false, '已签到学员绝对不能再次显示签到按钮');
|
||||
assert.strictEqual(actSigned.canSignOut, true, '已签到未签退学员激活签退');
|
||||
assert.strictEqual(actSigned.canAdjust, true, '已有打卡记录激活调整');
|
||||
console.log('✔ PASS: Production computeRosterActionEligibility covers all status button visibility');
|
||||
}
|
||||
|
||||
// 3. 模拟 signIn 闭环与名册状态刷新
|
||||
// 5. 后端生命周期报错反馈解析真实性 (resolveSignInFeedback)
|
||||
{
|
||||
// 初始名册
|
||||
let roster = [
|
||||
{
|
||||
enrollmentId: 201,
|
||||
studentName: '张三',
|
||||
checkinId: null,
|
||||
attendanceStatus: null,
|
||||
signInTime: null,
|
||||
signOutTime: null,
|
||||
canSignIn: true,
|
||||
canSignOut: false,
|
||||
canAdjust: false
|
||||
}
|
||||
];
|
||||
const errWaiting = { code: 40085, msg: '学员尚未完成开班报到,禁止录入课次考勤' };
|
||||
const fbWaiting = resolveSignInFeedback(errWaiting, '张三');
|
||||
assert.strictEqual(fbWaiting.isSuccess, false);
|
||||
assert.strictEqual(fbWaiting.message.includes('学员尚未完成开班报到'), true);
|
||||
assert.strictEqual(fbWaiting.message.includes('成功'), false);
|
||||
|
||||
assert.strictEqual(roster[0].canSignIn, true);
|
||||
assert.strictEqual(roster[0].attendanceStatus, null);
|
||||
const errCompleted = { code: 40087, msg: '学员参训档案已结训完成,禁止录入课次考勤' };
|
||||
const fbCompleted = resolveSignInFeedback(errCompleted, '李四');
|
||||
assert.strictEqual(fbCompleted.isSuccess, false);
|
||||
assert.strictEqual(fbCompleted.message.includes('学员参训档案已结训完成'), true);
|
||||
assert.strictEqual(fbCompleted.message.includes('成功'), false);
|
||||
|
||||
// 模拟执行 signIn 成功
|
||||
function mockSignIn(enrollmentId, lessonId) {
|
||||
return Promise.resolve({
|
||||
checkinId: 5001,
|
||||
enrollmentId: enrollmentId,
|
||||
lessonId: lessonId,
|
||||
attendanceStatus: 1,
|
||||
signInTime: '2026-09-21 09:00:00',
|
||||
signOutTime: null
|
||||
});
|
||||
}
|
||||
const errAbandoned = { code: 40086, msg: '学员参训状态已放弃,禁止录入课次考勤' };
|
||||
const fbAbandoned = resolveSignInFeedback(errAbandoned, '王五');
|
||||
assert.strictEqual(fbAbandoned.isSuccess, false);
|
||||
assert.strictEqual(fbAbandoned.message.includes('学员参训状态已放弃'), true);
|
||||
|
||||
// 模拟刷新 roster
|
||||
function mockRefreshRoster(updatedRecord) {
|
||||
roster = roster.map(item => {
|
||||
if (item.enrollmentId === updatedRecord.enrollmentId) {
|
||||
return {
|
||||
...item,
|
||||
checkinId: updatedRecord.checkinId,
|
||||
attendanceStatus: updatedRecord.attendanceStatus,
|
||||
signInTime: updatedRecord.signInTime,
|
||||
signOutTime: updatedRecord.signOutTime,
|
||||
canSignIn: false, // 已签到禁止再签到
|
||||
canSignOut: true,
|
||||
canAdjust: true
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
mockSignIn(201, 1001).then(res => {
|
||||
mockRefreshRoster(res);
|
||||
assert.strictEqual(roster[0].checkinId, 5001);
|
||||
assert.strictEqual(roster[0].attendanceStatus, 1);
|
||||
assert.strictEqual(roster[0].canSignIn, false, '刷新后签到按钮必须消失');
|
||||
assert.strictEqual(roster[0].canSignOut, true, '刷新后签退按钮必须激活');
|
||||
assert.strictEqual(roster[0].canAdjust, true, '刷新后调整按钮必须激活');
|
||||
console.log('✔ PASS: signIn success refreshes roster to current attendance fact');
|
||||
});
|
||||
console.log('✔ PASS: Production resolveSignInFeedback faithfully reports errors without false success');
|
||||
}
|
||||
|
||||
// 4. 后端错误真实展示,不得出现假成功
|
||||
{
|
||||
function handleSignInError(err) {
|
||||
// 页面逻辑:
|
||||
let displayedMsg = '';
|
||||
let isSuccess = false;
|
||||
if (err && (err.msg || err.message)) {
|
||||
displayedMsg = err.msg || err.message;
|
||||
} else {
|
||||
displayedMsg = '签到操作失败';
|
||||
}
|
||||
return { displayedMsg, isSuccess };
|
||||
}
|
||||
|
||||
const backendError = { code: 400, msg: '课次未关联有效的培训课程计划' };
|
||||
const feedback = handleSignInError(backendError);
|
||||
|
||||
assert.strictEqual(feedback.isSuccess, false);
|
||||
assert.strictEqual(feedback.displayedMsg, '课次未关联有效的培训课程计划');
|
||||
assert.strictEqual(feedback.displayedMsg.includes('成功'), false);
|
||||
console.log('✔ PASS: Backend error is truthfully reported without false success');
|
||||
}
|
||||
|
||||
// 5. 权限可达性检查:train_admin 具备 checkin 权限与 list 权限
|
||||
{
|
||||
const trainAdminPerms = ['train:attendance:list', 'train:attendance:checkin', 'train:attendance:edit', 'train:attendance:export'];
|
||||
const canAccessList = trainAdminPerms.includes('train:attendance:list');
|
||||
const canPerformSignIn = trainAdminPerms.includes('train:attendance:checkin');
|
||||
const canPerformAdjust = trainAdminPerms.includes('train:attendance:edit');
|
||||
|
||||
assert.strictEqual(canAccessList, true, 'train_admin 必须有查看考勤名册权限');
|
||||
assert.strictEqual(canPerformSignIn, true, 'train_admin 必须有课次签到权限');
|
||||
assert.strictEqual(canPerformAdjust, true, 'train_admin 必须有课次调整权限');
|
||||
console.log('✔ PASS: train_admin has full permission reachability');
|
||||
}
|
||||
|
||||
console.log('ALL FRONTEND ATTENDANCE SIGN-IN ENTRY TESTS PASSED!');
|
||||
console.log('ALL PRODUCTION ATTENDANCE POLICY TESTS PASSED!');
|
||||
Reference in new issue
Block a user