Files
ai-code-app/etc/servers/work-server/src/services/plan-schedule-service.test.ts

248 lines
6.4 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
buildScheduledBoardPostTitle,
buildScheduledPlanWorkIdBase,
isPlanScheduledTaskDue,
mapPlanScheduledTaskRow,
shouldCreatePlanForScheduleExecution,
updatePlanScheduledTaskSchema,
} from './plan-schedule-service.js';
test('buildScheduledBoardPostTitle prefers the first memo line when present', () => {
assert.equal(
buildScheduledBoardPostTitle({
work_id: '반복작업',
note: ' 첫 줄 제목 \n둘째 줄 설명',
}),
'첫 줄 제목',
);
});
test('buildScheduledBoardPostTitle falls back to normalized work id when memo is empty', () => {
assert.equal(
buildScheduledBoardPostTitle({
work_id: ' 작업 ID ',
note: ' \n ',
}),
'반복작업',
);
});
test('buildScheduledPlanWorkIdBase uses the schedule work id for automation registration', () => {
assert.equal(
buildScheduledPlanWorkIdBase({
work_id: ' 반복-정리 ',
}),
'반복-정리',
);
});
test('buildScheduledPlanWorkIdBase keeps managed-service runs on the schedule work id with schedule pk prefix', () => {
assert.equal(
buildScheduledPlanWorkIdBase({
id: 10,
work_id: ' 반복-정리 ',
execution_mode: 'managed-service',
}),
'schedule-10-반복-정리',
);
assert.equal(
buildScheduledPlanWorkIdBase({
id: 10,
work_id: '반복-정리-service',
execution_mode: 'managed-service',
}),
'schedule-10-반복-정리-service',
);
});
test('buildScheduledPlanWorkIdBase falls back when managed-service schedule id is missing', () => {
assert.equal(
buildScheduledPlanWorkIdBase({
work_id: '반복-정리-service',
execution_mode: 'managed-service',
}),
'반복-정리-service',
);
});
test('shouldCreatePlanForScheduleExecution only returns true for managed-service schedules', () => {
assert.equal(
shouldCreatePlanForScheduleExecution({
execution_mode: 'codex',
}),
false,
);
assert.equal(
shouldCreatePlanForScheduleExecution({
execution_mode: 'managed-service',
}),
true,
);
});
test('updatePlanScheduledTaskSchema keeps partial updates partial', () => {
assert.deepEqual(updatePlanScheduledTaskSchema.parse({ recreateManagedServiceOnNextSave: true }), {
recreateManagedServiceOnNextSave: true,
});
});
test('updatePlanScheduledTaskSchema accepts second-based interval updates', () => {
assert.deepEqual(
updatePlanScheduledTaskSchema.parse({
repeatIntervalValue: 10,
repeatIntervalUnit: 'second',
repeatIntervalSeconds: 10,
}),
{
repeatIntervalValue: 10,
repeatIntervalUnit: 'second',
repeatIntervalSeconds: 10,
},
);
});
test('interval schedule with start time waits until start time when immediate run is enabled', () => {
assert.equal(
isPlanScheduledTaskDue(
{
schedule_mode: 'interval',
immediate_run_enabled: true,
repeat_interval_minutes: 60,
repeat_window_start_time: '09:00',
created_at: '2026-04-30T07:00:00+09:00',
},
new Date('2026-04-30T08:59:00+09:00'),
),
false,
);
assert.equal(
isPlanScheduledTaskDue(
{
schedule_mode: 'interval',
immediate_run_enabled: true,
repeat_interval_minutes: 60,
repeat_window_start_time: '09:00',
created_at: '2026-04-30T07:00:00+09:00',
},
new Date('2026-04-30T09:00:00+09:00'),
),
true,
);
});
test('interval schedule with start time waits one interval after the start when immediate run is disabled', () => {
assert.equal(
isPlanScheduledTaskDue(
{
schedule_mode: 'interval',
immediate_run_enabled: false,
repeat_interval_minutes: 60,
repeat_window_start_time: '09:00',
created_at: '2026-04-30T07:00:00+09:00',
},
new Date('2026-04-30T09:59:00+09:00'),
),
false,
);
assert.equal(
isPlanScheduledTaskDue(
{
schedule_mode: 'interval',
immediate_run_enabled: false,
repeat_interval_minutes: 60,
repeat_window_start_time: '09:00',
created_at: '2026-04-30T07:00:00+09:00',
},
new Date('2026-04-30T10:00:00+09:00'),
),
true,
);
});
test('interval schedule supports second-based due calculation', () => {
assert.equal(
isPlanScheduledTaskDue(
{
schedule_mode: 'interval',
immediate_run_enabled: false,
repeat_interval_unit: 'second',
repeat_interval_value: 10,
repeat_interval_seconds: 10,
created_at: '2026-04-30T09:00:00+09:00',
},
new Date('2026-04-30T09:00:09+09:00'),
),
false,
);
assert.equal(
isPlanScheduledTaskDue(
{
schedule_mode: 'interval',
immediate_run_enabled: false,
repeat_interval_unit: 'second',
repeat_interval_value: 10,
repeat_interval_seconds: 10,
created_at: '2026-04-30T09:00:00+09:00',
},
new Date('2026-04-30T09:00:10+09:00'),
),
true,
);
});
test('interval schedule uses repeat interval value and unit when stored seconds are stale', () => {
assert.equal(
isPlanScheduledTaskDue(
{
schedule_mode: 'interval',
immediate_run_enabled: false,
repeat_interval_unit: 'minute',
repeat_interval_value: 10,
repeat_interval_seconds: 3600,
created_at: '2026-04-30T09:50:00+09:00',
},
new Date('2026-04-30T09:59:59+09:00'),
),
false,
);
assert.equal(
isPlanScheduledTaskDue(
{
schedule_mode: 'interval',
immediate_run_enabled: false,
repeat_interval_unit: 'minute',
repeat_interval_value: 10,
repeat_interval_seconds: 3600,
created_at: '2026-04-30T09:50:00+09:00',
},
new Date('2026-04-30T10:00:00+09:00'),
),
true,
);
});
test('mapPlanScheduledTaskRow normalizes stale stored repeat interval seconds', () => {
const mapped = mapPlanScheduledTaskRow({
id: 2,
work_id: 'stock-alert',
schedule_mode: 'interval',
repeat_interval_unit: 'minute',
repeat_interval_value: 10,
repeat_interval_seconds: 3600,
repeat_interval_minutes: 60,
});
assert.equal(mapped.repeatIntervalValue, 10);
assert.equal(mapped.repeatIntervalUnit, 'minute');
assert.equal(mapped.repeatIntervalSeconds, 600);
assert.equal(mapped.repeatIntervalMinutes, 10);
});