diff --git a/src/task-scheduler.test.ts b/src/task-scheduler.test.ts new file mode 100644 index 0000000..62129e8 --- /dev/null +++ b/src/task-scheduler.test.ts @@ -0,0 +1,53 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { _initTestDatabase, createTask, getTaskById } from './db.js'; +import { + _resetSchedulerLoopForTests, + startSchedulerLoop, +} from './task-scheduler.js'; + +describe('task scheduler', () => { + beforeEach(() => { + _initTestDatabase(); + _resetSchedulerLoopForTests(); + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('pauses due tasks with invalid group folders to prevent retry churn', async () => { + createTask({ + id: 'task-invalid-folder', + group_folder: '../../outside', + chat_jid: 'bad@g.us', + prompt: 'run', + schedule_type: 'once', + schedule_value: '2026-02-22T00:00:00.000Z', + context_mode: 'isolated', + next_run: new Date(Date.now() - 60_000).toISOString(), + status: 'active', + created_at: '2026-02-22T00:00:00.000Z', + }); + + const enqueueTask = vi.fn( + (_groupJid: string, _taskId: string, fn: () => Promise) => { + void fn(); + }, + ); + + startSchedulerLoop({ + registeredGroups: () => ({}), + getSessions: () => ({}), + queue: { enqueueTask } as any, + onProcess: () => {}, + sendMessage: async () => {}, + }); + + await vi.advanceTimersByTimeAsync(10); + + const task = getTaskById('task-invalid-folder'); + expect(task?.status).toBe('paused'); + }); +}); diff --git a/src/task-scheduler.ts b/src/task-scheduler.ts index 13e1161..d8da795 100644 --- a/src/task-scheduler.ts +++ b/src/task-scheduler.ts @@ -14,6 +14,7 @@ import { getDueTasks, getTaskById, logTaskRun, + updateTask, updateTaskAfterRun, } from './db.js'; import { GroupQueue } from './group-queue.js'; @@ -39,6 +40,8 @@ async function runTask( groupDir = resolveGroupFolderPath(task.group_folder); } catch (err) { const error = err instanceof Error ? err.message : String(err); + // Stop retry churn for malformed legacy rows. + updateTask(task.id, { status: 'paused' }); logger.error( { taskId: task.id, groupFolder: task.group_folder, error }, 'Task has invalid group folder', @@ -237,3 +240,8 @@ export function startSchedulerLoop(deps: SchedulerDependencies): void { loop(); } + +/** @internal - for tests only. */ +export function _resetSchedulerLoopForTests(): void { + schedulerRunning = false; +}