This commit is contained in:
2026-06-25 20:57:09 -05:00
parent 552b86c323
commit e47d09d7d6
35 changed files with 4507 additions and 101 deletions

View File

@@ -0,0 +1,89 @@
import { db, load, makeUser } from './setup.mjs';
import test from 'node:test';
import assert from 'node:assert/strict';
const { createCredential } = await load('../models/credentialModel.js');
const { createHost } = await load('../models/hostModel.js');
const { updateSettings } = await load('../models/settingsModel.js');
const {
computeNextRun,
createRunPlanSchedule,
listDueRunPlanSchedules,
listRunPlanScheduleRuns,
previewScheduleOccurrences
} = await load('../models/runPlanScheduleModel.js');
const { dispatchDueRunPlanSchedules } = await load('../services/runPlanScheduleWorker.js');
const owner = makeUser({ email: 'schedule-owner@test.local' });
const ts = new Date().toISOString();
function createRunnableRunPlan() {
const scriptId = 'scr_schedule_test';
db.prepare(`INSERT INTO scripts (id, name, content, visibility, owner_user_id, version, created_at, updated_at)
VALUES (?, 'Scheduled Script.ps1', 'Write-Output scheduled', 'personal', ?, 1, ?, ?)`).run(scriptId, owner, ts, ts);
const credential = createCredential({
name: 'Schedule Credential',
kind: 'username_password',
username: 'TEST\\svc',
secret: 'secret',
visibility: 'personal'
}, owner);
const host = createHost({
name: 'Schedule Host',
address: 'schedule.local',
transport: 'local',
osFamily: 'linux',
credentialId: credential.id,
visibility: 'personal'
}, owner);
const runplanId = 'run_schedule_test';
db.prepare(`INSERT INTO runplans (id, name, script_id, visibility, owner_user_id, parallel, created_at, updated_at)
VALUES (?, 'Scheduled RunPlan', ?, 'personal', ?, 1, ?, ?)`).run(runplanId, scriptId, owner, ts, ts);
db.prepare('INSERT INTO runplan_hosts (runplan_id, host_id) VALUES (?, ?)').run(runplanId, host.id);
return runplanId;
}
test('recurrence preview covers weekly and interval schedules', () => {
const weekly = {
scheduleType: 'weekly',
startAt: '2026-06-01T14:00:00.000Z',
timeOfDay: '09:30',
daysOfWeek: [1, 3]
};
const preview = previewScheduleOccurrences(weekly, 3, new Date('2026-06-02T00:00:00.000Z'));
assert.equal(preview.length, 3);
assert.ok(preview.every((date) => [1, 3].includes(new Date(date).getDay())));
const intervalNext = computeNextRun({
scheduleType: 'interval',
startAt: '2026-06-01T00:00:00.000Z',
intervalValue: 6,
intervalUnit: 'hours'
}, new Date('2026-06-01T07:00:00.000Z'));
assert.equal(intervalNext, '2026-06-01T12:00:00.000Z');
});
test('due RunPlan schedules dispatch jobs and record scheduler history', async () => {
updateSettings({ allow_script_execution: 'false' });
const runplanId = createRunnableRunPlan();
const schedule = createRunPlanSchedule({
name: 'Every minute schedule',
runplanId,
scheduleType: 'interval',
startAt: '2026-01-01T00:00:00.000Z',
intervalValue: 1,
intervalUnit: 'minutes',
visibility: 'personal'
}, owner);
db.prepare('UPDATE runplan_schedules SET next_run_at = ? WHERE id = ?').run('2026-01-01T00:02:00.000Z', schedule.id);
assert.ok(listDueRunPlanSchedules('2026-01-01T00:02:00.000Z').some((row) => row.id === schedule.id));
const summary = await dispatchDueRunPlanSchedules({ nowIso: '2026-01-01T00:02:00.000Z' });
assert.equal(summary.dispatched, 1);
const job = db.prepare('SELECT * FROM jobs WHERE runplan_id = ? ORDER BY created_at DESC').get(runplanId);
assert.ok(job);
const history = listRunPlanScheduleRuns(owner, false, 20);
assert.ok(history.some((event) => event.scheduleId === schedule.id && event.jobId === job.id));
updateSettings({ allow_script_execution: 'true' });
});