'use strict'; const test = require('node:test'); const assert = require('node:assert'); const { ScheduledMessenger, normalizeTasks, clampInterval, MIN_INTERVAL_SECONDS, MAX_TASKS } = require('../src/bot/scheduledMessenger'); /** 造一个可控的假 BotManager */ function fakeManager(options) { const opts = options || {}; const sent = []; return { connection: { username: 'BotA' }, canSendChat: () => opts.canSend !== false, sendChat: (content) => { sent.push(content); return true; }, sent }; } test('normalizeTasks 丢弃空内容、补齐默认值、按 / 前缀判定类型', () => { const tasks = normalizeTasks([ { id: 'a', content: '/u restore confirm', intervalSeconds: 300, label: '恢复民心' }, { content: '大家好', intervalSeconds: 60 }, { content: ' ' }, null, { intervalSeconds: 10 } ]); assert.strictEqual(tasks.length, 2); assert.strictEqual(tasks[0].kind, 'command', '以 / 开头应识别为指令'); assert.strictEqual(tasks[0].enabled, true, '默认启用'); assert.strictEqual(tasks[1].kind, 'chat'); assert.ok(tasks[1].id, '缺 id 时应自动生成'); }); test('normalizeTasks 钳制间隔到合理范围并限制条数', () => { assert.strictEqual(clampInterval(1), MIN_INTERVAL_SECONDS, '间隔不能小于下限,否则等同刷屏'); assert.strictEqual(clampInterval(999999), 86400); assert.strictEqual(clampInterval('abc'), 300, '非法值回退到 5 分钟'); const many = []; for (let i = 0; i < MAX_TASKS + 10; i++) many.push({ content: 'm' + i, intervalSeconds: 60 }); assert.strictEqual(normalizeTasks(many).length, MAX_TASKS); }); test('id 重复时自动改名,保证每个任务独立计时', () => { const tasks = normalizeTasks([ { id: 'x', content: 'a', intervalSeconds: 60 }, { id: 'x', content: 'b', intervalSeconds: 60 } ]); assert.strictEqual(tasks.length, 2); assert.notStrictEqual(tasks[0].id, tasks[1].id); }); test('到点才发送,且按间隔重复', () => { const manager = fakeManager(); const messenger = new ScheduledMessenger(manager, null); const t0 = 1_000_000; messenger.setTasks([{ id: 'restore', content: '/u restore confirm', intervalSeconds: 300 }], t0); assert.strictEqual(messenger.tick(t0), 0, '刚设置完不应立即发送'); assert.strictEqual(messenger.tick(t0 + 299_000), 0, '未到间隔不发'); assert.strictEqual(messenger.tick(t0 + 300_000), 1, '到点应发送'); assert.deepStrictEqual(manager.sent, ['/u restore confirm']); assert.strictEqual(messenger.tick(t0 + 599_000), 0); assert.strictEqual(messenger.tick(t0 + 600_000), 1, '第二个周期应再发一次'); assert.strictEqual(manager.sent.length, 2); }); test('未连接 / 未通过登录验证时跳过本次并顺延,不补发', () => { const manager = fakeManager({ canSend: false }); const messenger = new ScheduledMessenger(manager, null); const t0 = 1_000_000; messenger.setTasks([{ id: 'a', content: 'hi', intervalSeconds: 60 }], t0); assert.strictEqual(messenger.tick(t0 + 60_000), 0, '未就绪时不发送'); assert.deepStrictEqual(manager.sent, []); // 顺延一个周期:即使中间恢复也不会把漏掉的补发出来 manager.canSendChat = () => true; assert.strictEqual(messenger.tick(t0 + 61_000), 0, '不能补发上一周期的内容'); assert.strictEqual(messenger.tick(t0 + 120_000), 1); assert.deepStrictEqual(manager.sent, ['hi']); }); test('禁用的任务不发送', () => { const manager = fakeManager(); const messenger = new ScheduledMessenger(manager, null); messenger.setTasks([{ id: 'a', content: 'hi', intervalSeconds: 60, enabled: false }], 1_000_000); assert.strictEqual(messenger.tick(1_000_000 + 120_000), 0); assert.deepStrictEqual(manager.sent, []); }); test('多个任务各自独立计时', () => { const manager = fakeManager(); const messenger = new ScheduledMessenger(manager, null); const t0 = 1_000_000; messenger.setTasks([ { id: 'fast', content: 'A', intervalSeconds: 30 }, { id: 'slow', content: 'B', intervalSeconds: 300 } ], t0); assert.strictEqual(messenger.tick(t0 + 30_000), 1); assert.strictEqual(messenger.tick(t0 + 60_000), 1); assert.strictEqual(messenger.tick(t0 + 300_000), 2, '两个任务在同一时刻都到点'); assert.deepStrictEqual(manager.sent, ['A', 'A', 'A', 'B']); }); test('snapshot 暴露剩余秒数供控制台展示', () => { const manager = fakeManager(); const messenger = new ScheduledMessenger(manager, null); messenger.setTasks([{ id: 'a', label: '恢复', content: '/u restore confirm', intervalSeconds: 300 }], Date.now()); const snap = messenger.snapshot(); assert.strictEqual(snap.length, 1); assert.strictEqual(snap[0].label, '恢复'); assert.strictEqual(snap[0].kind, 'command'); assert.ok(snap[0].nextDueAt > 0); assert.ok(snap[0].nextInSeconds <= 300 && snap[0].nextInSeconds >= 0); }); test('setTasks 整体替换并重置计时', () => { const manager = fakeManager(); const messenger = new ScheduledMessenger(manager, null); const t0 = 1_000_000; messenger.setTasks([{ id: 'a', content: 'A', intervalSeconds: 60 }], t0); messenger.tick(t0 + 60_000); assert.strictEqual(manager.sent.length, 1); messenger.setTasks([{ id: 'b', content: 'B', intervalSeconds: 60 }], t0 + 60_000); assert.strictEqual(messenger.snapshot().length, 1); assert.strictEqual(messenger.tick(t0 + 61_000), 0, '替换后重新计时,不应立刻发送'); assert.strictEqual(messenger.tick(t0 + 200_000), 1); assert.deepStrictEqual(manager.sent, ['A', 'B']); }); test('start / stop 控制心跳定时器', () => { const messenger = new ScheduledMessenger(fakeManager(), null); messenger.start(); assert.ok(messenger._timer, 'start 后应有定时器'); const timer = messenger._timer; messenger.start(); assert.strictEqual(messenger._timer, timer, '重复 start 不应叠加定时器'); messenger.stop(); assert.strictEqual(messenger._timer, null); });