2026-04-20 15:41:15 +03:00
|
|
|
import { describe, expect, it } from 'vitest';
|
2026-04-16 15:55:08 +03:00
|
|
|
import { computeNextRunAt, formatScheduledSessionTitle, parseScheduledCommandPrompt } from './runtime.js';
|
|
|
|
|
|
|
|
|
|
describe('scheduled-tasks runtime helpers', () => {
|
|
|
|
|
it('computes next daily run in timezone', () => {
|
|
|
|
|
const nowUtc = Date.UTC(2025, 0, 1, 8, 0, 0);
|
|
|
|
|
const next = computeNextRunAt({
|
|
|
|
|
enabled: true,
|
|
|
|
|
schedule: {
|
|
|
|
|
kind: 'daily',
|
|
|
|
|
times: ['09:30'],
|
|
|
|
|
timezone: 'UTC',
|
|
|
|
|
},
|
|
|
|
|
}, nowUtc);
|
|
|
|
|
|
|
|
|
|
expect(next).toBe(Date.UTC(2025, 0, 1, 9, 30, 0));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('computes weekly next run using weekdays', () => {
|
|
|
|
|
// Monday 2025-01-06 10:00:00 UTC
|
|
|
|
|
const nowUtc = Date.UTC(2025, 0, 6, 10, 0, 0);
|
|
|
|
|
const next = computeNextRunAt({
|
|
|
|
|
enabled: true,
|
|
|
|
|
schedule: {
|
|
|
|
|
kind: 'weekly',
|
|
|
|
|
times: ['09:00'],
|
|
|
|
|
weekdays: [1, 3],
|
|
|
|
|
timezone: 'UTC',
|
|
|
|
|
},
|
|
|
|
|
}, nowUtc);
|
|
|
|
|
|
|
|
|
|
// Wednesday 2025-01-08 09:00:00 UTC
|
|
|
|
|
expect(next).toBe(Date.UTC(2025, 0, 8, 9, 0, 0));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('picks nearest time from multiple daily times', () => {
|
|
|
|
|
const nowUtc = Date.UTC(2025, 0, 1, 9, 20, 0);
|
|
|
|
|
const next = computeNextRunAt({
|
|
|
|
|
enabled: true,
|
|
|
|
|
schedule: {
|
|
|
|
|
kind: 'daily',
|
|
|
|
|
times: ['09:15', '09:45', '18:00'],
|
|
|
|
|
timezone: 'UTC',
|
|
|
|
|
},
|
|
|
|
|
}, nowUtc);
|
|
|
|
|
|
|
|
|
|
expect(next).toBe(Date.UTC(2025, 0, 1, 9, 45, 0));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('computes one-time next run for future date', () => {
|
|
|
|
|
const nowUtc = Date.UTC(2026, 3, 15, 10, 0, 0);
|
|
|
|
|
const next = computeNextRunAt({
|
|
|
|
|
enabled: true,
|
|
|
|
|
schedule: {
|
|
|
|
|
kind: 'once',
|
|
|
|
|
date: '2026-04-16',
|
|
|
|
|
time: '13:30',
|
|
|
|
|
timezone: 'UTC',
|
|
|
|
|
},
|
|
|
|
|
}, nowUtc);
|
|
|
|
|
|
|
|
|
|
expect(next).toBe(Date.UTC(2026, 3, 16, 13, 30, 0));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns null for past one-time schedule', () => {
|
|
|
|
|
const nowUtc = Date.UTC(2026, 3, 16, 14, 0, 0);
|
|
|
|
|
const next = computeNextRunAt({
|
|
|
|
|
enabled: true,
|
|
|
|
|
schedule: {
|
|
|
|
|
kind: 'once',
|
|
|
|
|
date: '2026-04-16',
|
|
|
|
|
time: '13:30',
|
|
|
|
|
timezone: 'UTC',
|
|
|
|
|
},
|
|
|
|
|
}, nowUtc);
|
|
|
|
|
|
|
|
|
|
expect(next).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('formats session title with timestamp suffix', () => {
|
|
|
|
|
const title = formatScheduledSessionTitle({
|
|
|
|
|
name: 'Morning Sync',
|
|
|
|
|
schedule: { timezone: 'UTC' },
|
|
|
|
|
}, Date.UTC(2025, 2, 10, 7, 5, 0));
|
|
|
|
|
|
|
|
|
|
expect(title).toBe('Morning Sync 2025-03-10 07:05');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('parses slash command prompt for scheduled command mode', () => {
|
|
|
|
|
expect(parseScheduledCommandPrompt('/review src/components')).toEqual({
|
|
|
|
|
command: 'review',
|
|
|
|
|
arguments: 'src/components',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns null when prompt is not a slash command', () => {
|
|
|
|
|
expect(parseScheduledCommandPrompt('Summarize open issues')).toBeNull();
|
|
|
|
|
expect(parseScheduledCommandPrompt('/')).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
});
|