191 lines
6.3 KiB
JavaScript
191 lines
6.3 KiB
JavaScript
import assert from 'node:assert';
|
|
|
|
console.log('=== Testing Attendance Sign-in Entry & Roster Semantics ===');
|
|
|
|
// 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. 验证未打卡学员渲染为“未签到”,绝不等于“缺勤”
|
|
{
|
|
const tagNull = formatAttendanceTag(null);
|
|
assert.strictEqual(tagNull.text, '未签到');
|
|
assert.strictEqual(tagNull.type, 'info');
|
|
|
|
const tagUndefined = formatAttendanceTag(undefined);
|
|
assert.strictEqual(tagUndefined.text, '未签到');
|
|
|
|
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)');
|
|
}
|
|
|
|
// 2. 按钮可见性与防重复规则
|
|
function getRowActionButtons(row) {
|
|
return {
|
|
signIn: !!row.canSignIn,
|
|
signOut: !!row.canSignOut,
|
|
adjust: !!row.canAdjust
|
|
};
|
|
}
|
|
|
|
// A. 无 checkin 记录学员:签到按钮可见,签退不可见
|
|
{
|
|
const uncheckRow = {
|
|
enrollmentId: 101,
|
|
checkinId: null,
|
|
attendanceStatus: null,
|
|
signInTime: null,
|
|
signOutTime: null,
|
|
canSignIn: true,
|
|
canSignOut: false,
|
|
canAdjust: 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');
|
|
}
|
|
|
|
// B. 已有签到记录:签到按钮不可重复执行
|
|
{
|
|
const signedInRow = {
|
|
enrollmentId: 101,
|
|
checkinId: 8001,
|
|
attendanceStatus: 1,
|
|
signInTime: '2026-09-21 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');
|
|
}
|
|
|
|
// 3. 模拟 signIn 闭环与名册状态刷新
|
|
{
|
|
// 初始名册
|
|
let roster = [
|
|
{
|
|
enrollmentId: 201,
|
|
studentName: '张三',
|
|
checkinId: null,
|
|
attendanceStatus: null,
|
|
signInTime: null,
|
|
signOutTime: null,
|
|
canSignIn: true,
|
|
canSignOut: false,
|
|
canAdjust: false
|
|
}
|
|
];
|
|
|
|
assert.strictEqual(roster[0].canSignIn, true);
|
|
assert.strictEqual(roster[0].attendanceStatus, null);
|
|
|
|
// 模拟执行 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
|
|
});
|
|
}
|
|
|
|
// 模拟刷新 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');
|
|
});
|
|
}
|
|
|
|
// 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!');
|