const test = require('node:test') const assert = require('node:assert') const { useTempDb } = require('./helper') useTempDb() const db = require('../src/db') const wx = require('../src/wx') const quota = require('../src/quota') const reminder = require('../src/reminder') // 用「今天」为基准造数据,保证 daysUntil 落在期望值上 const TODAY = new Date() const inDays = (n) => new Date(TODAY.getTime() + n * 86400000) function makeAnniv(id, openid, personName, importance, offsetDays, remindDays) { const d = inDays(offsetDays) return { id, openid, personId: 'p', personName, type: 'birthday', isLunar: 0, solarYear: d.getFullYear(), solarMonth: d.getMonth() + 1, solarDay: d.getDate(), importance, remindEnabled: 1, remindDays } } function resetLogs() { db.prepare('DELETE FROM remind_logs').run() } test('额度够时全部发出', async () => { resetLogs() quota.grant('uA', 5) const sent = [] const restore = wx.sendSubscribeMessage wx.sendSubscribeMessage = async (p) => { sent.push(p.data.name1.value); return { errcode: 0 } } const items = [ makeAnniv('A1', 'uA', '甲', 'low', 0, 3), makeAnniv('A2', 'uA', '乙', 'high', 0, 3) ] const r = await reminder.runForUser('uA', items, TODAY) wx.sendSubscribeMessage = restore assert.strictEqual(r.ok, 2) assert.strictEqual(r.skipped, 0) assert.strictEqual(quota.getBalance('uA'), 3) }) test('额度不足时当天优先、同级按重要程度', async () => { resetLogs() quota.grant('uB', 1) const sent = [] const restore = wx.sendSubscribeMessage wx.sendSubscribeMessage = async (p) => { sent.push(p.data.name1.value); return { errcode: 0 } } const items = [ makeAnniv('B1', 'uB', '提前的', 'high', 3, 3), // 提前事件(daysUntil=3=remindDays) makeAnniv('B2', 'uB', '当天低', 'low', 0, 3), // 当天事件 makeAnniv('B3', 'uB', '当天高', 'high', 0, 3) // 当天事件,重要程度更高 ] const r = await reminder.runForUser('uB', items, TODAY) wx.sendSubscribeMessage = restore assert.strictEqual(r.ok, 1) assert.strictEqual(r.skipped, 2) assert.deepStrictEqual(sent, ['当天高'], '应当只发出当天且最重要的那条') }) test('余额为 0 时不发任何请求', async () => { resetLogs() let called = 0 const restore = wx.sendSubscribeMessage wx.sendSubscribeMessage = async () => { called++; return { errcode: 0 } } const items = [makeAnniv('C1', 'uC', '丙', 'high', 0, 3)] const r = await reminder.runForUser('uC', items, TODAY) wx.sendSubscribeMessage = restore assert.strictEqual(called, 0, '没额度就不该打请求') assert.strictEqual(r.skipped, 1) }) test('遇到 43101 立即归零并中止本用户剩余发送', async () => { resetLogs() quota.grant('uD', 9) let called = 0 const restore = wx.sendSubscribeMessage wx.sendSubscribeMessage = async () => { called++ const e = new Error('发送订阅消息失败') e.errcode = 43101 throw e } const items = [ makeAnniv('D1', 'uD', '甲', 'high', 0, 3), makeAnniv('D2', 'uD', '乙', 'high', 0, 3), makeAnniv('D3', 'uD', '丙', 'high', 0, 3) ] const r = await reminder.runForUser('uD', items, TODAY) wx.sendSubscribeMessage = restore assert.strictEqual(called, 1, '失败一次就该停,不该继续打请求') assert.strictEqual(quota.getBalance('uD'), 0, '余额应被归零校正') assert.strictEqual(r.skipped, 3, '含失败那条在内全部计为 skipped') }) test('非额度类错误只记 failed,不动余额', async () => { resetLogs() quota.grant('uE', 4) const restore = wx.sendSubscribeMessage wx.sendSubscribeMessage = async () => { const e = new Error('网络炸了') e.errcode = 40003 throw e } const items = [makeAnniv('E1', 'uE', '甲', 'high', 0, 3)] const r = await reminder.runForUser('uE', items, TODAY) wx.sendSubscribeMessage = restore assert.strictEqual(r.fail, 1) assert.strictEqual(quota.getBalance('uE'), 4, '配置/网络错误不该动余额') }) test('skipped 会写入 remind_logs 便于排查', async () => { resetLogs() const items = [makeAnniv('F1', 'uF', '甲', 'high', 0, 3)] await reminder.runForUser('uF', items, TODAY) const row = db.prepare("SELECT * FROM remind_logs WHERE status = 'skipped'").get() assert.ok(row, '应写入 skipped 日志') assert.strictEqual(row.error, 'quota_exhausted') })