mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-28 09:02:12 +08:00
* refactor: migrate cron scheduling to node-cron * feat: support annually midnight and minutely cron macros * fix: harden cron recovery and system scheduler compatibility
77 lines
2.9 KiB
JavaScript
77 lines
2.9 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { format } = require('node:util');
|
|
const load = require('../helpers/load-security-module.cjs');
|
|
const { SchedulerReadiness } = require('../../back/shared/schedulerReadiness');
|
|
|
|
const { getTasks } = require('node-cron');
|
|
|
|
const legacySchedules = [
|
|
'* * * * *',
|
|
'*/5 * * * *',
|
|
'0 */10 * * * ?',
|
|
'0 */2 * * * ?',
|
|
'0 */5 * * * ?',
|
|
'0 0/30 * * * ?',
|
|
'0 0/5 * * * ?',
|
|
'0 0 12 ? * MON',
|
|
];
|
|
|
|
test('API accepts legacy question-mark schedules but rejects malformed cron', () => {
|
|
const { scheduleSchema } = load('back/validation/schedule.ts', {
|
|
'../config': { logPath: '/ql/data/log' },
|
|
});
|
|
for (const schedule of [...legacySchedules, '@once', '@boot']) {
|
|
assert.equal(scheduleSchema.validate(schedule).error, undefined, schedule);
|
|
}
|
|
for (const schedule of ['@bogus', '0 /5 * * * ?', '0 70 * * * ?', 'not a cron']) {
|
|
assert.ok(scheduleSchema.validate(schedule).error, schedule);
|
|
}
|
|
});
|
|
|
|
test('legacy main and extra schedules restore real jobs and healthy readiness', async (t) => {
|
|
const stacks = new Map();
|
|
t.after(() => {
|
|
for (const jobs of stacks.values()) for (const job of jobs) job.cancel();
|
|
});
|
|
const { addCron } = load('back/schedule/addCron.ts', {
|
|
'./data': { scheduleStacks: stacks },
|
|
'../shared/runCron': { runCron() {} },
|
|
'../loaders/logger': { info() {}, warn() {} },
|
|
'../shared/i18n': { tf: format },
|
|
});
|
|
const crons = legacySchedules.map((schedule, index) => ({
|
|
id: `legacy-${index}`, name: 'legacy', schedule, command: 'true',
|
|
extra_schedules: [{ schedule: '0 0/30 * * * ?' }],
|
|
}));
|
|
const register = (rows) => new Promise((resolve, reject) => {
|
|
addCron({ request: { replace: true, crons: rows } }, (error) =>
|
|
error ? reject(error) : resolve());
|
|
});
|
|
const readiness = new SchedulerReadiness(async () => {}, 60000);
|
|
t.after(() => clearTimeout(readiness.retry));
|
|
readiness.configure(() => register(crons));
|
|
assert.equal(await readiness.recover(), true);
|
|
assert.equal(await readiness.check(), true);
|
|
assert.equal(stacks.size, crons.length);
|
|
for (const jobs of stacks.values()) {
|
|
assert.equal(jobs.length, 2);
|
|
assert.ok(jobs.every((job) => typeof job.cancel === 'function'));
|
|
}
|
|
const nativeJobs = [...getTasks().values()];
|
|
assert.equal(nativeJobs.length, crons.length * 2);
|
|
assert.ok(nativeJobs.every((job) => job.getStatus() !== 'stopped'));
|
|
const previousJobs = [...stacks.values()].flat();
|
|
for (const schedule of ['@bogus', '0 /5 * * * ?', '0 70 * * * ?']) {
|
|
for (const invalid of [
|
|
{ ...crons[0], schedule },
|
|
{ ...crons[0], extra_schedules: [{ schedule }] },
|
|
]) {
|
|
await assert.rejects(register([invalid]), (error) => error.code === 3);
|
|
assert.deepEqual([...stacks.values()].flat(), previousJobs);
|
|
assert.deepEqual([...getTasks().values()], nativeJobs);
|
|
assert.ok(nativeJobs.every((job) => job.getStatus() !== 'stopped'));
|
|
}
|
|
}
|
|
});
|