import assert from 'node:assert/strict'; import test from 'node:test'; import { startScheduledTaskWorker } from './scheduled-task-worker.mjs'; test('scheduled task worker executes due task and completes one-shot task', async () => { const calls = []; const notifications = []; const wechat = []; const task = { id: 'task-1', userId: 'user-1', title: '每日新闻页', taskSpec: '做新闻页', recurrence: 'once', notifyChannel: 'both', attempts: 1, }; const worker = startScheduledTaskWorker({ intervalMs: 60_000, userAuth: { id: 'user-auth' }, tkmindProxy: { id: 'proxy' }, scheduledTaskService: { async listDueTasks() { calls.push('list'); return [task]; }, async lockTask(id) { calls.push(`lock:${id}`); return task; }, async markTaskRunning(input) { calls.push(`running:${input.id}`); return { ...input, status: 'running' }; }, async markTaskSucceeded(input, payload) { calls.push(`success:${input.id}:${payload.deliveryText}`); return { ...input, status: 'completed' }; }, async markTaskFailed() { calls.push('failed'); }, }, scheduleService: { async createUserNotification(input) { notifications.push(input.notificationType); }, }, notificationDispatcher: { async sendScheduleNotification({ userId, text }) { wechat.push({ userId, text }); }, }, executeTask: async () => ({ sessionId: 'session-1', requestId: 'req-1', deliveryText: '页面:https://example.com/news.html', }), logger: { warn() {}, info() {} }, runOnStart: false, }); await worker.runOnce(); worker.stop(); assert.deepEqual(calls, [ 'list', 'lock:task-1', 'running:task-1', 'success:task-1:页面:https://example.com/news.html', ]); assert.deepEqual(notifications, ['scheduled_task_result']); assert.equal(wechat.length, 1); assert.match(wechat[0].text, /定时任务完成:每日新闻页/); }); test('scheduled task worker marks failure after execution error', async () => { const calls = []; const task = { id: 'task-2', userId: 'user-2', title: '失败任务', recurrence: 'daily', notifyChannel: 'web', attempts: 3, }; const worker = startScheduledTaskWorker({ intervalMs: 60_000, userAuth: { id: 'user-auth' }, tkmindProxy: { id: 'proxy' }, scheduledTaskService: { async listDueTasks() { return [task]; }, async lockTask() { return task; }, async markTaskRunning(input) { return input; }, async markTaskSucceeded() { calls.push('success'); }, async markTaskFailed(input, err, { maxAttempts }) { calls.push(`failed:${maxAttempts}:${err.message}`); return { ...input, status: 'failed', lastError: err.message }; }, }, scheduleService: { async createUserNotification(input) { calls.push(`notify:${input.notificationType}`); }, }, executeTask: async () => { throw new Error('agent timeout'); }, maxAttempts: 3, logger: { warn() {} }, runOnStart: false, }); await worker.runOnce(); worker.stop(); assert.deepEqual(calls, ['failed:3:agent timeout', 'notify:scheduled_task_failed']); }); test('scheduled task worker marks failure when agent only asks for clarification', async () => { const calls = []; const task = { id: 'task-3', userId: 'user-3', title: '每日诗意清晨', recurrence: 'daily', notifyChannel: 'web', attempts: 1, }; const worker = startScheduledTaskWorker({ intervalMs: 60_000, userAuth: { id: 'user-auth' }, tkmindProxy: { id: 'proxy' }, scheduledTaskService: { async listDueTasks() { return [task]; }, async lockTask() { return task; }, async markTaskRunning(input) { return input; }, async markTaskSucceeded() { calls.push('success'); }, async markTaskFailed(input, err) { calls.push(`failed:${err.code}`); return { ...input, status: 'failed', lastError: err.message }; }, }, scheduleService: { async createUserNotification(input) { calls.push(`notify:${input.notificationType}`); }, }, executeTask: async () => ({ sessionId: 'session-3', requestId: 'req-3', deliveryText: '请问每天早上几点执行?', }), logger: { warn() {} }, runOnStart: false, }); await worker.runOnce(); worker.stop(); assert.deepEqual(calls, ['failed:SCHEDULED_TASK_NON_DELIVERY', 'notify:scheduled_task_failed']); }); test('scheduled task worker keeps success when wechat notification fails', async () => { const calls = []; const task = { id: 'task-4', userId: 'user-4', title: '每日新闻页', recurrence: 'daily', notifyChannel: 'both', attempts: 1, }; const worker = startScheduledTaskWorker({ intervalMs: 60_000, userAuth: { id: 'user-auth' }, tkmindProxy: { id: 'proxy' }, scheduledTaskService: { async listDueTasks() { return [task]; }, async lockTask() { return task; }, async markTaskRunning(input) { return input; }, async markTaskSucceeded(input) { calls.push(`success:${input.id}`); return { ...input, status: 'completed' }; }, async markTaskFailed() { calls.push('failed'); }, }, scheduleService: { async createUserNotification() { calls.push('notify:web'); }, }, notificationDispatcher: { async sendScheduleNotification() { const err = new Error('out of response count limit'); err.errcode = 45047; throw err; }, }, executeTask: async () => ({ sessionId: 'session-4', requestId: 'req-4', deliveryText: '页面已生成:https://example.com/news.html', }), logger: { warn() {} }, runOnStart: false, }); await worker.runOnce(); worker.stop(); assert.deepEqual(calls, ['notify:web', 'success:task-4']); });