Files
Toolbox/dev_test_scripts/integration/test_schedule_reminder_integration.js
T
yangxiangyuan a3e8911deb feat: 新增作息报时器功能并替换短链接前缀为/to/
- 新增作息报时器独立模块,包含完整的日程管理、语音播报和待机功能
- 短链接正式前缀由/l/改为/to/,解决手机小屏下与I、i、1字形混淆问题
- 保留旧前缀/l/、/L/、/I/、/i/兼容已分发的旧短链
- 新增短码大小写兜底匹配,仅唯一匹配时生效避免歧义
- 新增防爆紧急替换功能,可轮换首页鉴权并替换短链接
- 新增多个调试和集成测试脚本,保障数据安全和功能回归
2026-10-09 14:05:36 +08:00

177 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 作息报时器(schedule_reminder)后端接口集成测试
* 独立起一个临时 express 实例(8978),只挂载 schedule_reminder 的路由,
* 不依赖 8976 主服务,也不影响运行中的服务。
*
* 数据库隔离策略:脚本开始时若 data/app_order_box_schedule_reminder.db 不存在,
* 结束时把测试期间产生的 db / wal / shm 一并删除,保证不留测试脏数据。
*
* 运行:node dev_test_scripts/integration/test_schedule_reminder_integration.js
*/
const fs = require('fs')
const path = require('path')
const express = require('express')
const DB_FILE = path.join(process.cwd(), 'data', 'app_order_box_schedule_reminder.db')
const DB_EXISTED_BEFORE = fs.existsSync(DB_FILE)
const { bindRoutes } = require('../../src/server/app_order_box/kits/schedule_reminder')
const PORT = 8978
const BASE = `http://127.0.0.1:${PORT}`
let passed = 0
let failed = 0
const check = (name, ok, extra) => {
if (ok) { passed += 1; console.log(` ✔ ${name}`) }
else { failed += 1; console.log(` ✘ ${name}${extra ? ' → ' + extra : ''}`) }
}
const request = async (method, url, body) => {
const res = await fetch(BASE + url, {
method,
headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
body: body === undefined ? undefined : JSON.stringify(body),
})
const text = await res.text()
let json = null
try { json = JSON.parse(text) } catch (_) { /* 非 JSON */ }
return { status: res.status, json, text }
}
const cleanupDb = () => {
if (DB_EXISTED_BEFORE) return
;['', '-wal', '-shm'].forEach(suffix => {
const p = DB_FILE + suffix
try { if (fs.existsSync(p)) fs.unlinkSync(p) } catch (_) { /* 静默 */ }
})
console.log('\n(测试库为本次新建,已清理:' + path.basename(DB_FILE) + ')')
}
const run = async () => {
console.log('=== 作息报时器 · 后端接口集成测试 ===\n')
// ---------- 1. 校时接口 ----------
console.log('[1] GET /time 校时')
const timeRes = await request('GET', '/api/app_order_box/kits/schedule_reminder/time')
check('HTTP 200', timeRes.status === 200, timeRes.status)
check('返回 ok=true', !!timeRes.json && timeRes.json.ok === true)
const epochMs = timeRes.json && timeRes.json.data ? Number(timeRes.json.data.epochMs) : NaN
check('epochMs 是有效时间戳', Number.isFinite(epochMs) && Math.abs(Date.now() - epochMs) < 60 * 1000, epochMs)
// ---------- 2. 日程校验 ----------
console.log('\n[2] POST /schedules 参数校验')
const noTitle = await request('POST', '/api/app_order_box/kits/schedule_reminder/schedules', { items: [{ time: '07:30', name: '起床' }] })
check('缺 title → 400', noTitle.status === 400, noTitle.status)
const noItem = await request('POST', '/api/app_order_box/kits/schedule_reminder/schedules', { title: '周末', weekdays: [6, 7] })
check('缺 items → 400', noItem.status === 400, noItem.status)
const noWeekday = await request('POST', '/api/app_order_box/kits/schedule_reminder/schedules', { title: '周末', items: [{ time: '07:30', name: '起床' }] })
check('缺 weekdays → 400', noWeekday.status === 400, noWeekday.status)
// ---------- 3. 新建日程 ----------
console.log('\n[3] POST /schedules 新建(含清洗规则)')
const createRes = await request('POST', '/api/app_order_box/kits/schedule_reminder/schedules', {
title: ' 周末在家 ',
weekdays: [7, 6, 6, 99, 0], // 期望清洗为 [6,7]
items: [
{ time: '11:00', name: '做午饭', repeatTimes: 5, intervalSec: 30 },
{ time: '07:30', name: '起床', repeatTimes: 99, intervalSec: 1 }, // 期望被夹到 20 / 5
{ time: '25:99', name: '非法时刻' }, // 期望被过滤
],
})
check('HTTP 200', createRes.status === 200, createRes.status + ' ' + createRes.text.slice(0, 120))
const created = createRes.json && createRes.json.data
check('title 已 trim', created && created.title === '周末在家', created && created.title)
check('weekdays 清洗为 [6,7]', created && JSON.stringify(created.weekdays) === '[6,7]', created && JSON.stringify(created.weekdays))
check('非法时刻被过滤(剩 2 条)', created && created.items.length === 2, created && created.items.length)
check('items 按时刻升序', created && created.items[0].time === '07:30' && created.items[1].time === '11:00',
created && created.items.map(i => i.time).join(','))
const first = created && created.items[0]
check('repeatTimes 夹到 20', first && first.repeatTimes === 20, first && first.repeatTimes)
check('intervalSec 夹到 5', first && first.intervalSec === 5, first && first.intervalSec)
check('默认 repeatTimes=5 / intervalSec=30', created && created.items[1].repeatTimes === 5 && created.items[1].intervalSec === 30,
created && (created.items[1].repeatTimes + '/' + created.items[1].intervalSec))
const scheduleId = created && created.id
// ---------- 4. 列表 ----------
console.log('\n[4] GET /schedules 列表')
const listRes = await request('GET', '/api/app_order_box/kits/schedule_reminder/schedules')
check('HTTP 200', listRes.status === 200, listRes.status)
check('列表含新建日程', Array.isArray(listRes.json.data) && listRes.json.data.some(s => s.id === scheduleId))
// ---------- 5. 更新 ----------
console.log('\n[5] PUT /schedules 更新')
const updateRes = await request('PUT', '/api/app_order_box/kits/schedule_reminder/schedules', {
id: scheduleId,
title: '周末在家 v2',
weekdays: [6, 7],
items: [{ time: '07:30', name: '起床', repeatTimes: 5, intervalSec: 30 }],
})
check('HTTP 200', updateRes.status === 200, updateRes.status + ' ' + updateRes.text.slice(0, 120))
check('title 已更新', updateRes.json && updateRes.json.data.title === '周末在家 v2')
check('items 已更新为 1 条', updateRes.json && updateRes.json.data.items.length === 1)
const missingUpdate = await request('PUT', '/api/app_order_box/kits/schedule_reminder/schedules', {
id: 'not-exist-id', title: 'x', weekdays: [1], items: [{ time: '08:00', name: 'a' }],
})
check('更新不存在的日程 → 404', missingUpdate.status === 404, missingUpdate.status)
// ---------- 6. 播报日志 ----------
console.log('\n[6] 播报日志 logs / logs/ack')
const logRes = await request('POST', '/api/app_order_box/kits/schedule_reminder/logs', {
scheduleId: scheduleId,
scheduleTitle: '周末在家 v2',
itemId: 'item-1',
itemName: '起床',
plannedAt: '2026-09-30T07:30:00+08:00',
status: 'fired',
})
check('写日志 HTTP 200', logRes.status === 200, logRes.status + ' ' + logRes.text.slice(0, 120))
const logId = logRes.json && logRes.json.data && logRes.json.data.id
check('返回日志 id', Number.isFinite(Number(logId)) && Number(logId) > 0, logId)
check('状态为 fired', logRes.json && logRes.json.data.status === 'fired')
const missingLog = await request('POST', '/api/app_order_box/kits/schedule_reminder/logs', { itemName: 'x' })
check('缺 itemId → 400', missingLog.status === 400, missingLog.status)
const ackRes = await request('POST', '/api/app_order_box/kits/schedule_reminder/logs/ack', { id: logId })
check('确认 HTTP 200', ackRes.status === 200, ackRes.status)
check('状态变为 acked', ackRes.json && ackRes.json.data && ackRes.json.data.status === 'acked',
ackRes.json && ackRes.json.data && ackRes.json.data.status)
const logsRes = await request('GET', '/api/app_order_box/kits/schedule_reminder/logs')
check('日志列表 HTTP 200', logsRes.status === 200, logsRes.status)
check('列表含刚写入的记录', Array.isArray(logsRes.json.data) && logsRes.json.data.some(l => Number(l.id) === Number(logId)))
// ---------- 7. 语音预合成参数校验 ----------
console.log('\n[7] POST /tts/prepare 参数校验(不做真实合成)')
const ttsBad = await request('POST', '/api/app_order_box/kits/schedule_reminder/tts/prepare', { schedule: {} })
check('空 schedule → 400', ttsBad.status === 400, ttsBad.status)
// ---------- 8. 删除 ----------
console.log('\n[8] DELETE /schedules 删除')
const delRes = await request('DELETE', `/api/app_order_box/kits/schedule_reminder/schedules?id=${encodeURIComponent(scheduleId)}`)
check('HTTP 200', delRes.status === 200, delRes.status)
const afterDel = await request('GET', '/api/app_order_box/kits/schedule_reminder/schedules')
check('删除后列表不再包含', Array.isArray(afterDel.json.data) && !afterDel.json.data.some(s => s.id === scheduleId))
console.log(`\n=== 结果:通过 ${passed} 项,失败 ${failed} 项 ===`)
return failed === 0
}
const app = express()
app.use(express.json())
bindRoutes(app)
const server = app.listen(PORT, async () => {
let ok = false
try {
ok = await run()
} catch (e) {
console.log('\n✘ 测试执行异常:' + String((e && e.stack) || e))
failed += 1
}
server.close(() => {
cleanupDb()
process.exit(ok ? 0 : 1)
})
})