Files

131 lines
3.1 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
InvalidLocalScheduleError,
assertLocalSchedulePageSize,
resolveLocalScheduleDecision,
} = require('../dist/scheduler/localScheduler');
const MINUTE = 60_000;
function nextMinute(schedule, afterMs) {
if (schedule.expression !== '* * * * *' || schedule.timezone !== 'UTC') {
throw new Error('unsupported test schedule');
}
return Math.floor(afterMs / MINUTE + 1) * MINUTE;
}
function candidate(overrides = {}) {
return {
projectId: 'default',
triggerId: 'trigger-1',
triggerRevision: 1,
triggerContentDigest: 'a'.repeat(64),
triggerUpdatedAtMs: 1,
taskId: 'task-1',
taskRevision: 1,
taskContentDigest: 'b'.repeat(64),
expression: '* * * * *',
timezone: 'UTC',
misfirePolicy: 'skip',
stateVersion: 0,
nextFireAtMs: MINUTE,
...overrides,
};
}
test('admits one on-time occurrence and advances beyond the observation', () => {
assert.deepEqual(
resolveLocalScheduleDecision(
candidate(),
MINUTE + 1_000,
5_000,
nextMinute,
),
{
candidate: candidate(),
observedAtMs: MINUTE + 1_000,
scheduledForMs: MINUTE,
nextFireAtMs: 2 * MINUTE,
disposition: 'admit',
},
);
});
test('applies skip and fire-once misfire without replaying a backlog', () => {
const observedAtMs = 10 * MINUTE + 30_000;
const skipped = resolveLocalScheduleDecision(
candidate(),
observedAtMs,
5_000,
nextMinute,
);
assert.equal(skipped.disposition, 'skip');
assert.equal(skipped.nextFireAtMs, 11 * MINUTE);
const admitted = resolveLocalScheduleDecision(
candidate({ misfirePolicy: 'fire_once' }),
observedAtMs,
5_000,
nextMinute,
);
assert.equal(admitted.disposition, 'admit');
assert.equal(admitted.scheduledForMs, MINUTE);
assert.equal(admitted.nextFireAtMs, 11 * MINUTE);
});
test('initializes migrated state without inventing a due occurrence', () => {
const decision = resolveLocalScheduleDecision(
candidate({ triggerUpdatedAtMs: 30_000, nextFireAtMs: null }),
40_000,
5_000,
nextMinute,
);
assert.deepEqual(
{
disposition: decision.disposition,
nextFireAtMs: decision.nextFireAtMs,
scheduledForMs: decision.scheduledForMs,
},
{
disposition: 'initialize',
nextFireAtMs: MINUTE,
scheduledForMs: undefined,
},
);
});
test('rejects widened candidates, invalid cron and unbounded pages', () => {
assert.throws(
() =>
resolveLocalScheduleDecision(
{ ...candidate(), extra: true },
MINUTE,
0,
nextMinute,
),
InvalidLocalScheduleError,
);
assert.throws(
() =>
resolveLocalScheduleDecision(
candidate({ expression: 'invalid cron' }),
MINUTE,
0,
nextMinute,
),
InvalidLocalScheduleError,
);
assert.throws(
() =>
resolveLocalScheduleDecision(
candidate(),
MINUTE,
0,
(_schedule, afterMs) => afterMs,
),
InvalidLocalScheduleError,
);
assert.throws(() => assertLocalSchedulePageSize(257), RangeError);
});