feat(schedule): 待办提醒推送、行事历仅展示提醒与管理 API
单次提醒 worker 投递、local 时间写入校验、未来 7 天提醒列表与忽略/批量删除;行事历去掉事项重复展示。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -17,6 +17,12 @@ test('schedule reminder worker sends due daily todo digest', async () => {
|
||||
const worker = startScheduleReminderWorker({
|
||||
intervalMs: 60_000,
|
||||
scheduleService: {
|
||||
async listDueReminders() {
|
||||
return [];
|
||||
},
|
||||
async lockReminder() {
|
||||
return null;
|
||||
},
|
||||
async listDueDigestSubscriptions() {
|
||||
calls.push('list');
|
||||
return [subscription];
|
||||
@@ -59,6 +65,12 @@ test('schedule reminder worker sends due balance alert for same user only', asyn
|
||||
const worker = startScheduleReminderWorker({
|
||||
intervalMs: 60_000,
|
||||
scheduleService: {
|
||||
async listDueReminders() {
|
||||
return [];
|
||||
},
|
||||
async lockReminder() {
|
||||
return null;
|
||||
},
|
||||
async listDueDigestSubscriptions() {
|
||||
return [];
|
||||
},
|
||||
@@ -112,3 +124,143 @@ test('schedule reminder worker sends due balance alert for same user only', asyn
|
||||
]);
|
||||
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, []);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user