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
70 lines
2.9 KiB
JavaScript
70 lines
2.9 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { isValidCronSchedule } = require('../../back/shared/cronSchedule');
|
|
const { createCronJob } = require('../../back/shared/cronScheduler');
|
|
const legacy = require('../fixtures/legacy-cron.json');
|
|
const logger = { warn() {}, error() {} };
|
|
const flush = () => new Promise(setImmediate);
|
|
|
|
test('validation preserves legacy syntax except the explicitly added macros', () => {
|
|
const addedMacros = new Set(['@annually', '@midnight', '@minutely']);
|
|
assert.ok(legacy.validation.length > 250);
|
|
for (const { schedule, accepted } of legacy.validation) {
|
|
assert.equal(isValidCronSchedule(schedule), accepted || addedMacros.has(schedule), schedule);
|
|
}
|
|
});
|
|
|
|
for (const sample of legacy.times) {
|
|
test(`calendar compatibility: ${sample.schedule} from ${sample.currentDate}`, async (t) => {
|
|
const previousTZ = process.env.TZ;
|
|
process.env.TZ = 'UTC';
|
|
t.after(() => { if (previousTZ === undefined) delete process.env.TZ; else process.env.TZ = previousTZ; });
|
|
t.mock.timers.enable({ apis: ['Date', 'setTimeout', 'setInterval'], now: new Date(sample.currentDate) });
|
|
const actual = [];
|
|
const job = createCronJob(sample.schedule, (date) => actual.push(date.toISOString()), { name: 'calendar', logger });
|
|
t.after(() => job.cancel());
|
|
for (const expected of sample.next) {
|
|
t.mock.timers.tick(new Date(expected).getTime() - Date.now());
|
|
await flush();
|
|
}
|
|
assert.deepEqual(actual, sample.next);
|
|
});
|
|
}
|
|
|
|
test('late callbacks catch up exactly once and cancellation destroys native tasks', async (t) => {
|
|
t.mock.timers.enable({ apis: ['Date', 'setTimeout', 'setInterval'], now: new Date('2026-09-27T00:00:00Z') });
|
|
const native = require('node-cron');
|
|
const before = native.getTasks().size;
|
|
const dates = [], warnings = [];
|
|
const job = createCronJob('* * * * * *', (date) => dates.push(date.getTime()), {
|
|
name: 'late', logger: { warn: (...args) => warnings.push(args), error() {} },
|
|
});
|
|
t.after(() => job.cancel());
|
|
t.mock.timers.tick(8000);
|
|
await flush();
|
|
assert.equal(dates.length, 8);
|
|
assert.equal(new Set(dates).size, 8);
|
|
assert.ok(warnings.length > 0);
|
|
job.cancel();
|
|
t.mock.timers.tick(5000);
|
|
await flush();
|
|
assert.equal(dates.length, 8);
|
|
assert.equal(native.getTasks().size, before);
|
|
});
|
|
|
|
test('callback rejection is logged and future executions continue', async (t) => {
|
|
t.mock.timers.enable({ apis: ['Date', 'setTimeout', 'setInterval'], now: new Date('2026-09-27T00:00:00Z') });
|
|
const errors = [];
|
|
let count = 0;
|
|
const job = createCronJob('* * * * * *', async () => { count++; throw Error('callback failed'); }, {
|
|
name: 'failure', logger: { warn() {}, error: (...args) => errors.push(args) },
|
|
});
|
|
t.after(() => job.cancel());
|
|
t.mock.timers.tick(1000);
|
|
await flush();
|
|
t.mock.timers.tick(1000);
|
|
await flush();
|
|
assert.equal(count, 2);
|
|
assert.equal(errors.length, 2);
|
|
});
|