import assert from 'node:assert/strict'; import test from 'node:test'; import { startScheduleReminderWorker } from './schedule-reminder-worker.mjs'; test('schedule reminder worker sends due daily todo digest', async () => { const calls = []; const sent = []; const subscription = { id: 'sub-1', userId: 'user-1', hour: 7, minute: 0, timezone: 'Asia/Shanghai', channel: 'wechat', attempts: 1, }; const worker = startScheduleReminderWorker({ intervalMs: 60_000, scheduleService: { async listDueReminders() { return []; }, async lockReminder() { return null; }, async listDueDigestSubscriptions() { calls.push('list'); return [subscription]; }, async lockDigestSubscription(id) { calls.push(`lock:${id}`); return subscription; }, async buildTodoDigestText({ userId }) { calls.push(`digest:${userId}`); return '6月18日 待办记录\n\n1. 开会'; }, async logDelivery(input) { calls.push(`log:${input.status}`); }, async markDigestSent(input) { calls.push(`sent:${input.id}`); }, async markDigestFailed() { calls.push('failed'); }, }, async sendWechatTextToUser(userId, text) { sent.push({ userId, text }); }, logger: { warn() {} }, runOnStart: false, }); await worker.runOnce(); worker.stop(); assert.deepEqual(sent, [{ userId: 'user-1', text: '6月18日 待办记录\n\n1. 开会' }]); assert.deepEqual(calls, ['list', 'lock:sub-1', 'digest:user-1', 'log:success', 'sent:sub-1']); }); test('schedule reminder worker sends due balance alert for same user only', async () => { const sent = []; const alerts = []; const worker = startScheduleReminderWorker({ intervalMs: 60_000, scheduleService: { async listDueReminders() { return []; }, async lockReminder() { return null; }, async listDueDigestSubscriptions() { return []; }, async lockDigestSubscription() { return null; }, async listDueBalanceAlerts() { return [ { id: 'alert-1', userId: 'user-1', thresholdCents: 2000, channel: 'wechat', attempts: 0, }, ]; }, async lockBalanceAlert(id) { return { id, userId: 'user-1', thresholdCents: 2000, channel: 'wechat', attempts: 0 }; }, async getUserWalletSnapshot(userId) { return userId === 'user-1' ? { balanceCents: 1500 } : { balanceCents: 9999 }; }, async logDelivery(input) { alerts.push(`log:${input.status}:${input.userId}`); }, async markBalanceAlertSent(input) { alerts.push(`sent:${input.id}`); }, async markBalanceAlertFailed() { alerts.push('failed'); }, async buildTodoDigestText() { return ''; }, async markDigestSent() {}, async markDigestFailed() {}, }, async sendWechatTextToUser(userId, text) { sent.push({ userId, text }); }, logger: { warn() {} }, runOnStart: false, }); await worker.runOnce(); worker.stop(); assert.deepEqual(sent, [ { userId: 'user-1', text: '余额不足提醒\n\n你的账户余额已低于 20.00 元,请及时充值。' }, ]); assert.deepEqual(alerts, ['log:success:user-1', 'sent:alert-1']); }); test('schedule reminder worker sends due item reminders via wechat', async () => { const sent = []; const calls = []; const reminder = { id: 'rem-1', userId: 'user-1', itemId: 'item-1', remindAt: Date.now() - 1000, channel: 'wechat', attempts: 1, }; const worker = startScheduleReminderWorker({ intervalMs: 60_000, scheduleService: { async listDueReminders() { calls.push('list-reminders'); return [reminder]; }, async lockReminder(id) { calls.push(`lock-reminder:${id}`); return reminder; }, async buildReminderText(input) { calls.push(`text:${input.id}`); return '【待办提醒】带材料\n提醒时间:09:00'; }, async createUserNotification(input) { calls.push(`notify:${input.notificationType}`); }, async logDelivery(input) { calls.push(`log:${input.status}:${input.reminderId}`); }, async markReminderSent(input) { calls.push(`sent:${input.id}`); }, async markReminderCancelled() { calls.push('cancelled'); }, async markReminderFailed() { calls.push('failed'); }, async listDueDigestSubscriptions() { return []; }, async lockDigestSubscription() { return null; }, async listDueBalanceAlerts() { return []; }, async lockBalanceAlert() { return null; }, async buildTodoDigestText() { return ''; }, async markDigestSent() {}, async markDigestFailed() {}, }, async sendWechatTextToUser(userId, text) { sent.push({ userId, text }); }, logger: { warn() {} }, runOnStart: false, }); await worker.runOnce(); worker.stop(); assert.deepEqual(sent, [ { userId: 'user-1', text: '【待办提醒】带材料\n提醒时间:09:00' }, ]); assert.deepEqual(calls, [ 'list-reminders', 'lock-reminder:rem-1', 'text:rem-1', 'notify:schedule_reminder', 'log:success:rem-1', 'sent:rem-1', ]); }); test('schedule reminder worker skips wechat for in_app reminders', async () => { const sent = []; const reminder = { id: 'rem-2', userId: 'user-2', itemId: 'item-2', remindAt: Date.now() - 1000, channel: 'in_app', attempts: 0, }; const worker = startScheduleReminderWorker({ intervalMs: 60_000, scheduleService: { async listDueReminders() { return [reminder]; }, async lockReminder() { return reminder; }, async buildReminderText() { return '【待办提醒】开会'; }, async createUserNotification() {}, async logDelivery() {}, async markReminderSent() {}, async markReminderCancelled() {}, async markReminderFailed() {}, async listDueDigestSubscriptions() { return []; }, async lockDigestSubscription() { return null; }, async listDueBalanceAlerts() { return []; }, async lockBalanceAlert() { return null; }, async buildTodoDigestText() { return ''; }, async markDigestSent() {}, async markDigestFailed() {}, }, async sendWechatTextToUser(userId, text) { sent.push({ userId, text }); }, logger: { warn() {} }, runOnStart: false, }); await worker.runOnce(); worker.stop(); assert.deepEqual(sent, []); });