120 lines
4.2 KiB
TypeScript
120 lines
4.2 KiB
TypeScript
/**
|
|
* Tests for piece transitions module (movement-based)
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { determineNextMovementByRules } from '../core/piece/index.js';
|
|
import { extractBlockedPrompt } from '../core/piece/engine/transitions.js';
|
|
import type { PieceMovement } from '../core/models/index.js';
|
|
|
|
function createMovementWithRules(rules: { condition: string; next: string }[]): PieceMovement {
|
|
return {
|
|
name: 'test-step',
|
|
persona: 'test-agent',
|
|
personaDisplayName: 'Test Agent',
|
|
instructionTemplate: '{task}',
|
|
passPreviousResponse: false,
|
|
rules: rules.map((r) => ({
|
|
condition: r.condition,
|
|
next: r.next,
|
|
})),
|
|
};
|
|
}
|
|
|
|
describe('determineNextMovementByRules', () => {
|
|
it('should return next movement for valid rule index', () => {
|
|
const step = createMovementWithRules([
|
|
{ condition: 'Clear', next: 'implement' },
|
|
{ condition: 'Blocked', next: 'ABORT' },
|
|
]);
|
|
|
|
expect(determineNextMovementByRules(step, 0)).toBe('implement');
|
|
expect(determineNextMovementByRules(step, 1)).toBe('ABORT');
|
|
});
|
|
|
|
it('should return null for out-of-bounds index', () => {
|
|
const step = createMovementWithRules([
|
|
{ condition: 'Clear', next: 'implement' },
|
|
]);
|
|
|
|
expect(determineNextMovementByRules(step, 1)).toBeNull();
|
|
expect(determineNextMovementByRules(step, -1)).toBeNull();
|
|
expect(determineNextMovementByRules(step, 100)).toBeNull();
|
|
});
|
|
|
|
it('should return null when movement has no rules', () => {
|
|
const step: PieceMovement = {
|
|
name: 'test-step',
|
|
persona: 'test-agent',
|
|
personaDisplayName: 'Test Agent',
|
|
instructionTemplate: '{task}',
|
|
passPreviousResponse: false,
|
|
};
|
|
|
|
expect(determineNextMovementByRules(step, 0)).toBeNull();
|
|
});
|
|
|
|
it('should handle COMPLETE as next movement', () => {
|
|
const step = createMovementWithRules([
|
|
{ condition: 'All passed', next: 'COMPLETE' },
|
|
]);
|
|
|
|
expect(determineNextMovementByRules(step, 0)).toBe('COMPLETE');
|
|
});
|
|
|
|
it('should return null when rule exists but next is undefined', () => {
|
|
// Parallel sub-movement rules may omit `next` (optional field)
|
|
const step: PieceMovement = {
|
|
name: 'sub-step',
|
|
persona: 'test-agent',
|
|
personaDisplayName: 'Test Agent',
|
|
instructionTemplate: '{task}',
|
|
passPreviousResponse: false,
|
|
rules: [
|
|
{ condition: 'approved' },
|
|
{ condition: 'needs_fix' },
|
|
],
|
|
};
|
|
|
|
expect(determineNextMovementByRules(step, 0)).toBeNull();
|
|
expect(determineNextMovementByRules(step, 1)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('extractBlockedPrompt', () => {
|
|
it('should extract prompt after "必要な情報:" pattern', () => {
|
|
const content = '処理がブロックされました。\n必要な情報: デプロイ先の環境を教えてください';
|
|
expect(extractBlockedPrompt(content)).toBe('デプロイ先の環境を教えてください');
|
|
});
|
|
|
|
it('should extract prompt after "質問:" pattern', () => {
|
|
const content = '質問: どのブランチにマージしますか?';
|
|
expect(extractBlockedPrompt(content)).toBe('どのブランチにマージしますか?');
|
|
});
|
|
|
|
it('should extract prompt after "理由:" pattern', () => {
|
|
const content = '理由: 権限が不足しています';
|
|
expect(extractBlockedPrompt(content)).toBe('権限が不足しています');
|
|
});
|
|
|
|
it('should extract prompt after "確認:" pattern', () => {
|
|
const content = '確認: この変更を続けてもよいですか?';
|
|
expect(extractBlockedPrompt(content)).toBe('この変更を続けてもよいですか?');
|
|
});
|
|
|
|
it('should support full-width colon', () => {
|
|
const content = '必要な情報:ファイルパスを指定してください';
|
|
expect(extractBlockedPrompt(content)).toBe('ファイルパスを指定してください');
|
|
});
|
|
|
|
it('should return full content when no pattern matches', () => {
|
|
const content = 'Something went wrong and I need help';
|
|
expect(extractBlockedPrompt(content)).toBe('Something went wrong and I need help');
|
|
});
|
|
|
|
it('should return first matching pattern when multiple exist', () => {
|
|
const content = '質問: 最初の質問\n確認: 二番目の質問';
|
|
expect(extractBlockedPrompt(content)).toBe('最初の質問');
|
|
});
|
|
});
|