* takt: implement-exceeded-requeue * takt: implement-exceeded-requeue * takt: implement-exceeded-requeue * ci: trigger CI * fix: 未使用インポート削除と --create-worktree e2e テスト修正 InteractiveModeAction の不要な import を削除して lint エラーを解消する。 --create-worktree オプション削除に合わせ e2e の期待メッセージを更新する。 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
180 lines
6.6 KiB
TypeScript
180 lines
6.6 KiB
TypeScript
/**
|
|
* Unit tests for `exceeded` status schema validation
|
|
*
|
|
* Covers:
|
|
* - TaskRecordSchema cross-field validation for `exceeded` status
|
|
* - TaskExecutionConfigSchema new fields: exceeded_max_movements, exceeded_current_iteration
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
TaskRecordSchema,
|
|
TaskExecutionConfigSchema,
|
|
TaskStatusSchema,
|
|
} from '../infra/task/schema.js';
|
|
|
|
function makeExceededRecord(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
return {
|
|
name: 'test-task',
|
|
status: 'exceeded',
|
|
content: 'task content',
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
started_at: '2025-01-01T01:00:00.000Z',
|
|
completed_at: '2025-01-01T02:00:00.000Z',
|
|
start_movement: 'plan',
|
|
exceeded_max_movements: 60,
|
|
exceeded_current_iteration: 30,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('TaskStatusSchema', () => {
|
|
it('should accept exceeded as a valid status', () => {
|
|
expect(() => TaskStatusSchema.parse('exceeded')).not.toThrow();
|
|
});
|
|
|
|
it('should still accept all existing statuses', () => {
|
|
expect(() => TaskStatusSchema.parse('pending')).not.toThrow();
|
|
expect(() => TaskStatusSchema.parse('running')).not.toThrow();
|
|
expect(() => TaskStatusSchema.parse('completed')).not.toThrow();
|
|
expect(() => TaskStatusSchema.parse('failed')).not.toThrow();
|
|
});
|
|
|
|
it('should reject unknown status', () => {
|
|
expect(() => TaskStatusSchema.parse('unknown')).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('TaskExecutionConfigSchema - exceeded fields', () => {
|
|
it('should accept exceeded_max_movements as a positive integer', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({ exceeded_max_movements: 60 })).not.toThrow();
|
|
});
|
|
|
|
it('should accept exceeded_current_iteration as a non-negative integer', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({ exceeded_current_iteration: 30 })).not.toThrow();
|
|
});
|
|
|
|
it('should accept exceeded_current_iteration as zero', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({ exceeded_current_iteration: 0 })).not.toThrow();
|
|
});
|
|
|
|
it('should accept both fields together', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({
|
|
exceeded_max_movements: 60,
|
|
exceeded_current_iteration: 30,
|
|
})).not.toThrow();
|
|
});
|
|
|
|
it('should accept config without exceeded fields (optional)', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({})).not.toThrow();
|
|
});
|
|
|
|
it('should reject exceeded_max_movements as zero', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({ exceeded_max_movements: 0 })).toThrow();
|
|
});
|
|
|
|
it('should reject exceeded_max_movements as negative', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({ exceeded_max_movements: -1 })).toThrow();
|
|
});
|
|
|
|
it('should reject exceeded_max_movements as non-integer', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({ exceeded_max_movements: 1.5 })).toThrow();
|
|
});
|
|
|
|
it('should reject exceeded_current_iteration as negative', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({ exceeded_current_iteration: -1 })).toThrow();
|
|
});
|
|
|
|
it('should reject exceeded_current_iteration as non-integer', () => {
|
|
expect(() => TaskExecutionConfigSchema.parse({ exceeded_current_iteration: 0.5 })).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('TaskRecordSchema - exceeded status', () => {
|
|
describe('valid exceeded record', () => {
|
|
it('should accept a valid exceeded record with all required fields', () => {
|
|
expect(() => TaskRecordSchema.parse(makeExceededRecord())).not.toThrow();
|
|
});
|
|
|
|
it('should accept exceeded record without start_movement (optional)', () => {
|
|
const record = makeExceededRecord({ start_movement: undefined });
|
|
expect(() => TaskRecordSchema.parse(record)).not.toThrow();
|
|
});
|
|
|
|
it('should reject exceeded record with only exceeded_current_iteration set (exceeded_max_movements missing)', () => {
|
|
const record = makeExceededRecord({ exceeded_max_movements: undefined });
|
|
expect(() => TaskRecordSchema.parse(record)).toThrow();
|
|
});
|
|
|
|
it('should reject exceeded record with only exceeded_max_movements set (exceeded_current_iteration missing)', () => {
|
|
const record = makeExceededRecord({ exceeded_current_iteration: undefined });
|
|
expect(() => TaskRecordSchema.parse(record)).toThrow();
|
|
});
|
|
|
|
it('should accept exceeded record when both exceeded fields are absent (neither field set)', () => {
|
|
const record = makeExceededRecord({ exceeded_max_movements: undefined, exceeded_current_iteration: undefined });
|
|
expect(() => TaskRecordSchema.parse(record)).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('started_at requirement', () => {
|
|
it('should reject exceeded record without started_at (null)', () => {
|
|
const record = makeExceededRecord({ started_at: null });
|
|
expect(() => TaskRecordSchema.parse(record)).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('completed_at requirement', () => {
|
|
it('should reject exceeded record without completed_at (null)', () => {
|
|
const record = makeExceededRecord({ completed_at: null });
|
|
expect(() => TaskRecordSchema.parse(record)).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('failure prohibition', () => {
|
|
it('should reject exceeded record with failure field', () => {
|
|
const record = makeExceededRecord({ failure: { error: 'something' } });
|
|
expect(() => TaskRecordSchema.parse(record)).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('owner_pid prohibition', () => {
|
|
it('should reject exceeded record with owner_pid set to a process ID', () => {
|
|
const record = makeExceededRecord({ owner_pid: 12345 });
|
|
expect(() => TaskRecordSchema.parse(record)).toThrow();
|
|
});
|
|
|
|
it('should accept exceeded record with owner_pid explicitly null', () => {
|
|
const record = makeExceededRecord({ owner_pid: null });
|
|
expect(() => TaskRecordSchema.parse(record)).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('independence from other statuses', () => {
|
|
it('should not affect pending status validation', () => {
|
|
// pending: started_at must be null
|
|
expect(() => TaskRecordSchema.parse({
|
|
name: 'test-task',
|
|
status: 'pending',
|
|
content: 'task content',
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
started_at: null,
|
|
completed_at: null,
|
|
})).not.toThrow();
|
|
});
|
|
|
|
it('should not affect failed status validation', () => {
|
|
// failed: requires failure field
|
|
expect(() => TaskRecordSchema.parse({
|
|
name: 'test-task',
|
|
status: 'failed',
|
|
content: 'task content',
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
started_at: '2025-01-01T01:00:00.000Z',
|
|
completed_at: '2025-01-01T02:00:00.000Z',
|
|
failure: { error: 'something went wrong' },
|
|
})).not.toThrow();
|
|
});
|
|
});
|
|
});
|