Files
daywork/intern-log/repro/unit-tests.ts
T
root 9a91c4c85d
CI / 测试套件(协议/用户管理/单测/双设备/实时链路/构建) (push) Failing after 30s
CI / 依赖漏洞扫描(软性检查) (push) Failing after 30s
ci: 统一测试入口 scripts/ci.mjs(协议/用户管理/单测/双设备/实时链路/构建/依赖审计)+ Gitea Actions 工作流
2026-10-16 21:03:54 +08:00

127 lines
6.1 KiB
TypeScript

// 纯函数单元测试:日期库 / 考勤计算 / 加解密(node:test 运行器,esbuild 打包后执行)
import test from 'node:test'
import assert from 'node:assert/strict'
import { addDays, addMonths, hhmm, isWeekend, nowLocalISO, parseDateStr, toDateStr, tsMinutes, weekStart, formatDateCN } from '../src/lib/date'
import { computeDay } from '../src/domain/attendance'
import { aesGcmDecrypt, aesGcmEncrypt, deriveKeyFromPassword, pbkdf2Hash, randomSaltB64 } from '../src/lib/crypto'
const RULE = { work_start: '09:00', work_end: '18:00', grace_minutes: 10, lunch_minutes: 60, remind_weekly: 'friday' }
function rec(kind: string, inAt: string | null, outAt: string | null) {
return { date: inAt ? inAt.slice(0, 10) : '2025-01-02', kind, clock_in_at: inAt, clock_out_at: outAt, note: null, location: null, created_at: '2025-01-02T00:00:00' } as never
}
test('日期库:addDays 跨月与闰年', () => {
assert.equal(addDays('2025-01-31', 1), '2025-02-01')
assert.equal(addDays('2024-02-28', 1), '2024-02-29') // 闰年
assert.equal(addDays('2023-02-28', 1), '2023-03-01') // 平年
assert.equal(addDays('2025-03-01', -1), '2025-02-28')
assert.equal(addDays('2024-12-31', 1), '2025-01-01')
})
test('日期库:addMonths 日期截断(1-31 加一个月)', () => {
assert.equal(addMonths('2025-01-31', 1), '2025-02-28')
assert.equal(addMonths('2024-01-31', 1), '2024-02-29')
assert.equal(addMonths('2025-01-15', 1), '2025-02-15')
assert.equal(addMonths('2025-11-30', 2), '2026-01-30')
})
test('日期库:weekStart 以周一为一周开始', () => {
assert.equal(weekStart('2025-01-05'), '2024-12-30') // 周日
assert.equal(weekStart('2025-01-06'), '2025-01-06') // 周一
assert.equal(weekStart('2025-01-12'), '2025-01-06') // 周日
assert.equal(weekStart('2025-01-08'), '2025-01-06') // 周中
})
test('日期库:isWeekend 与格式化', () => {
assert.equal(isWeekend('2025-01-04'), true)
assert.equal(isWeekend('2025-01-05'), true)
assert.equal(isWeekend('2025-01-06'), false)
assert.equal(hhmm('2025-01-02T09:05:00'), '09:05')
assert.equal(hhmm(null), '--:--')
assert.match(nowLocalISO(), /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)
assert.equal(formatDateCN('2025-01-06'), '2025 年 1 月 6 日 周一')
assert.equal(toDateStr(parseDateStr('2025-02-27')), '2025-02-27')
assert.equal(tsMinutes('2025-01-02T09:00:00', '2025-01-02T18:00:00'), 540)
})
test('考勤:正常/迟到/早退/宽限边界', () => {
const day = '2025-01-02' // 周四
const normal = computeDay(rec('normal', day + 'T09:00:00', day + 'T18:00:00'), day, RULE, false)
assert.equal(normal.status, 'normal')
assert.equal(normal.label, '正常')
assert.equal(normal.workedMinutes, 480) // 9 小时 - 1 小时午休
// 宽限边界:09:10 恰好等于上班时间+10 分钟 → 不算迟到
const edge = computeDay(rec('normal', day + 'T09:10:00', day + 'T18:00:00'), day, RULE, false)
assert.equal(edge.status, 'normal')
// 迟到:09:11
const late = computeDay(rec('normal', day + 'T09:11:00', day + 'T18:00:00'), day, RULE, false)
assert.equal(late.status, 'late')
// 早退:17:29 下班
const early = computeDay(rec('normal', day + 'T09:00:00', day + 'T17:29:00'), day, RULE, false)
assert.equal(early.status, 'early')
// 补卡不判迟到早退
const makeup = computeDay(rec('makeup', day + 'T10:30:00', day + 'T17:00:00'), day, RULE, false)
assert.equal(makeup.status, 'normal')
assert.equal(makeup.label, '补卡')
assert.equal(makeup.makeup, true)
})
test('考勤:进行中/未完成/缺勤/未打卡', () => {
const day = '2025-01-02'
const running = computeDay(rec('normal', day + 'T09:00:00', null), day, RULE, true)
assert.equal(running.status, 'incomplete')
assert.equal(running.label, '进行中')
const unfinished = computeDay(rec('normal', day + 'T09:00:00', null), day, RULE, false)
assert.equal(unfinished.status, 'incomplete')
assert.equal(unfinished.label, '未完成')
const absent = computeDay(undefined, day, RULE, false)
assert.equal(absent.status, 'absent')
const none = computeDay(undefined, day, RULE, true)
assert.equal(none.status, 'none')
})
test('考勤:休假与休息日', () => {
const saturday = '2025-01-04'
const leave = computeDay(rec('leave', '2025-01-02T09:00:00', '2025-01-02T18:00:00'), '2025-01-02', RULE, false)
assert.equal(leave.status, 'leave')
assert.equal(leave.label, '请假')
// 休息日:即使有打卡记录也显示休息日(当前产品行为,锁定语义)
const rest = computeDay(rec('normal', saturday + 'T10:00:00', saturday + 'T18:00:00'), saturday, RULE, false)
assert.equal(rest.status, 'rest')
assert.equal(rest.label, '休息日')
// 休息日无记录
const restEmpty = computeDay(undefined, saturday, RULE, false)
assert.equal(restEmpty.status, 'rest')
})
test('考勤:异常时长不出现负数', () => {
const bad = computeDay(rec('normal', '2025-01-02T18:00:00', '2025-01-02T09:00:00'), '2025-01-02', RULE, false)
assert.equal(bad.workedMinutes, 0)
})
test('加密:哈希确定性、加盐、AES-GCM 往返', async () => {
// 盐必须是合法 base64(与 randomSaltB64 的产出格式一致)
const saltA = Buffer.from('salt-a').toString('base64')
const saltB = Buffer.from('salt-b').toString('base64')
const h1 = await pbkdf2Hash('密码abc123', saltA)
const h2 = await pbkdf2Hash('密码abc123', saltA)
const h3 = await pbkdf2Hash('密码abc123', saltB)
const h4 = await pbkdf2Hash('密码abc124', saltA)
assert.equal(h1, h2)
assert.notEqual(h1, h3)
assert.notEqual(h1, h4)
assert.equal(Buffer.from(h1, 'base64').length, 32)
const salt = randomSaltB64()
assert.equal(Buffer.from(salt, 'base64').length, 16)
const key = await deriveKeyFromPassword('主密码!@#', salt)
const enc = await aesGcmEncrypt(key, '机密内容:服务器管理员密码')
assert.notEqual(enc, '机密内容:服务器管理员密码')
assert.equal(await aesGcmDecrypt(key, enc), '机密内容:服务器管理员密码')
const otherKey = await deriveKeyFromPassword('错误主密码', salt)
await assert.rejects(() => aesGcmDecrypt(otherKey, enc))
})