import assert from 'node:assert/strict'; import test from 'node:test'; import { createScheduledTaskService, computeScheduledTaskNextRunAt } from './scheduled-task-service.mjs'; import { nextDailyRunAt } from './schedule-time.mjs'; test('computeScheduledTaskNextRunAt supports daily schedule', () => { const now = Date.parse('2026-07-31T02:00:00+08:00'); const nextRunAt = computeScheduledTaskNextRunAt({ recurrence: 'daily', hour: 6, minute: 0, timezone: 'Asia/Shanghai', now, }); assert.equal(nextRunAt, nextDailyRunAt({ hour: 6, minute: 0, timezone: 'Asia/Shanghai', now })); }); test('createTask inserts scheduled automation row', async () => { const inserts = []; const service = createScheduledTaskService({ async query(sql, params) { if (sql.includes('INSERT INTO h5_scheduled_tasks')) { inserts.push(params); return [{ affectedRows: 1 }]; } if (sql.includes('SELECT * FROM h5_scheduled_tasks WHERE id = ?')) { return [[{ id: params[0], user_id: 'user-1', title: '每日新闻页', task_spec: '搜索今日新闻并生成 HTML 页面', recurrence: 'daily', hour: 6, minute: 0, weekday: null, timezone: 'Asia/Shanghai', next_run_at: 1780000000000, last_run_at: null, notify_channel: 'both', status: 'active', attempts: 0, last_error: null, last_result_json: null, source_channel: 'agent', source_session_id: null, source_message_id: null, source_text: null, created_at: 1780000000000, updated_at: 1780000000000, }]]; } return [[]]; }, }, { clock: { now: () => 1780000000000 }, }); const task = await service.createTask({ userId: 'user-1', title: '每日新闻页', taskSpec: '搜索今日新闻并生成 HTML 页面', recurrence: 'daily', hour: 6, minute: 0, }); assert.equal(task.title, '每日新闻页'); assert.equal(task.recurrence, 'daily'); assert.equal(inserts.length, 1); }); test('cancelTask updates status to cancelled', async () => { const service = createScheduledTaskService({ async query(sql, params) { if (sql.includes('UPDATE h5_scheduled_tasks') && sql.includes('cancelled')) { return [{ affectedRows: 1 }]; } if (sql.includes('SELECT * FROM h5_scheduled_tasks WHERE id = ?')) { return [[{ id: params[0], user_id: 'user-1', title: '每日新闻页', task_spec: '搜索今日新闻并生成 HTML 页面', recurrence: 'daily', hour: 6, minute: 0, weekday: null, timezone: 'Asia/Shanghai', next_run_at: 1780000000000, last_run_at: null, notify_channel: 'both', status: 'cancelled', attempts: 0, last_error: null, last_result_json: null, source_channel: 'agent', source_session_id: null, source_message_id: null, source_text: null, created_at: 1780000000000, updated_at: 1780000001000, }]]; } return [[]]; }, }); const task = await service.cancelTask({ userId: 'user-1', taskId: 'task-1' }); assert.equal(task.status, 'cancelled'); }); test('markTaskSucceeded completes one-shot task', async () => { const updates = []; const service = createScheduledTaskService({ async query(sql, params) { if (sql.includes('UPDATE h5_scheduled_tasks') && sql.includes("'completed'")) { updates.push(['completed', params]); } return [{ affectedRows: 1 }]; }, }, { clock: { now: () => 1780000001000 }, }); const result = await service.markTaskSucceeded({ id: 'task-1', recurrence: 'once', title: '一次性任务', nextRunAt: 1780000000000, }, { deliveryText: 'done', sessionId: 'session-1', requestId: 'req-1', }); assert.equal(result.status, 'completed'); assert.equal(updates.length, 1); }); test('markTaskSucceeded schedules next run for daily task', async () => { const updates = []; const service = createScheduledTaskService({ async query(sql, params) { if (sql.includes('UPDATE h5_scheduled_tasks') && sql.includes("'active'")) { updates.push(params); } return [{ affectedRows: 1 }]; }, }, { clock: { now: () => Date.parse('2026-07-31T02:00:00+08:00') }, }); const result = await service.markTaskSucceeded({ id: 'task-2', recurrence: 'daily', hour: 6, minute: 0, timezone: 'Asia/Shanghai', title: '每日任务', nextRunAt: Date.parse('2026-07-31T06:00:00+08:00'), }, { deliveryText: 'done', }); assert.equal(result.status, 'active'); assert.ok(result.nextRunAt > Date.parse('2026-07-31T02:00:00+08:00')); assert.equal(updates.length, 1); });