2c91689692
单次提醒 worker 投递、local 时间写入校验、未来 7 天提醒列表与忽略/批量删除;行事历去掉事项重复展示。 Co-authored-by: Cursor <cursoragent@cursor.com>
267 lines
6.8 KiB
JavaScript
267 lines
6.8 KiB
JavaScript
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, []);
|
|
});
|