takt/src/__tests__/task-schema-exceeded.test.ts
nrs 4a92ba2012
[#366] implement-exceeded-requeue (#374)
* 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>
2026-03-02 23:30:53 +09:00

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();
});
});
});