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
52 lines
2.4 KiB
JavaScript
52 lines
2.4 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { createCronJob } = require('../../back/shared/cronScheduler');
|
|
const { isValidCronSchedule } = require('../../back/shared/cronSchedule');
|
|
|
|
// node-cron caches timezone formatters; model each deployment in a fresh process.
|
|
if (!process.env.QL_MACRO_TEST_TZ) {
|
|
for (const tz of ['UTC', 'Asia/Shanghai']) {
|
|
test(`macro execution in ${tz}`, () => {
|
|
const result = require('node:child_process').spawnSync(process.execPath,
|
|
['-r', 'ts-node/register/transpile-only', '--test', __filename],
|
|
{ env: { ...process.env, TZ: tz, QL_MACRO_TEST_TZ: tz }, encoding: 'utf8' });
|
|
assert.equal(result.status, 0, result.stdout + result.stderr);
|
|
});
|
|
}
|
|
}
|
|
for (const tz of process.env.QL_MACRO_TEST_TZ ? [process.env.QL_MACRO_TEST_TZ] : []) {
|
|
for (const [macro, start, next] of [
|
|
['@annually', [2026, 11, 31, 23, 59, 59], [[2027, 0, 1, 0, 0, 0], [2028, 0, 1, 0, 0, 0]]],
|
|
['@midnight', [2026, 8, 27, 23, 59, 59], [[2026, 8, 28, 0, 0, 0], [2026, 8, 29, 0, 0, 0]]],
|
|
['@minutely', [2026, 8, 27, 12, 34, 59], [[2026, 8, 27, 12, 35, 0], [2026, 8, 27, 12, 36, 0]]],
|
|
]) {
|
|
test(`${macro} fires at the expected local times in ${tz}`, async (t) => {
|
|
const previousTZ = process.env.TZ;
|
|
process.env.TZ = tz;
|
|
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(...start) });
|
|
const expected = next.map((parts) => new Date(...parts).getTime());
|
|
const actual = [];
|
|
assert.equal(isValidCronSchedule(` ${macro} `), true);
|
|
const job = createCronJob(macro, (date) => actual.push(date.getTime()), {
|
|
name: macro, logger: { warn() {}, error() {} },
|
|
});
|
|
t.after(() => job.cancel());
|
|
for (const time of expected) {
|
|
t.mock.timers.tick(time - Date.now() - 1);
|
|
await new Promise(setImmediate);
|
|
assert.equal(actual.includes(time), false, 'must not fire early');
|
|
t.mock.timers.tick(1);
|
|
await new Promise(setImmediate);
|
|
}
|
|
assert.deepEqual(actual, expected);
|
|
});
|
|
}
|
|
}
|
|
|
|
test('new aliases do not enable unknown macros or raw parser extensions', () => {
|
|
for (const schedule of ['@annuallyx', '@midnightx', '@minutelyx', '@secondly', '@reboot', 'H * * * *', '0 /5 * * * *']) {
|
|
assert.equal(isValidCronSchedule(schedule), false, schedule);
|
|
}
|
|
});
|