takt/src/__tests__/judgment-strategies.test.ts
2026-02-10 07:07:40 +09:00

205 lines
6.0 KiB
TypeScript

/**
* Unit tests for FallbackStrategy judgment strategies
*
* Tests AutoSelectStrategy and canApply logic for all strategies.
* Strategies requiring external agent calls (ReportBased, ResponseBased,
* AgentConsult) are tested for canApply and input validation only.
*/
import { describe, it, expect } from 'vitest';
import {
AutoSelectStrategy,
ReportBasedStrategy,
ResponseBasedStrategy,
AgentConsultStrategy,
JudgmentStrategyFactory,
type JudgmentContext,
} from '../core/piece/judgment/FallbackStrategy.js';
import type { PieceMovement } from '../core/models/types.js';
function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
return {
name: 'test-movement',
personaDisplayName: 'tester',
instructionTemplate: '',
passPreviousResponse: false,
...overrides,
};
}
function makeContext(overrides: Partial<JudgmentContext> = {}): JudgmentContext {
return {
step: makeMovement(),
cwd: '/tmp/test',
...overrides,
};
}
describe('AutoSelectStrategy', () => {
const strategy = new AutoSelectStrategy();
it('should have name "AutoSelect"', () => {
expect(strategy.name).toBe('AutoSelect');
});
describe('canApply', () => {
it('should return true when movement has exactly one rule', () => {
const ctx = makeContext({
step: makeMovement({
rules: [{ condition: 'done', next: 'COMPLETE' }],
}),
});
expect(strategy.canApply(ctx)).toBe(true);
});
it('should return false when movement has multiple rules', () => {
const ctx = makeContext({
step: makeMovement({
rules: [
{ condition: 'approved', next: 'implement' },
{ condition: 'rejected', next: 'review' },
],
}),
});
expect(strategy.canApply(ctx)).toBe(false);
});
it('should return false when movement has no rules', () => {
const ctx = makeContext({
step: makeMovement({ rules: undefined }),
});
expect(strategy.canApply(ctx)).toBe(false);
});
});
describe('execute', () => {
it('should return auto-selected tag for single-branch movement', async () => {
const ctx = makeContext({
step: makeMovement({
name: 'review',
rules: [{ condition: 'done', next: 'COMPLETE' }],
}),
});
const result = await strategy.execute(ctx);
expect(result.success).toBe(true);
expect(result.tag).toBe('[REVIEW:1]');
});
});
});
describe('ReportBasedStrategy', () => {
const strategy = new ReportBasedStrategy();
it('should have name "ReportBased"', () => {
expect(strategy.name).toBe('ReportBased');
});
describe('canApply', () => {
it('should return true when reportDir and outputContracts are present', () => {
const ctx = makeContext({
reportDir: '/tmp/reports',
step: makeMovement({
outputContracts: [{ name: 'report.md' }],
}),
});
expect(strategy.canApply(ctx)).toBe(true);
});
it('should return false when reportDir is missing', () => {
const ctx = makeContext({
step: makeMovement({
outputContracts: [{ name: 'report.md' }],
}),
});
expect(strategy.canApply(ctx)).toBe(false);
});
it('should return false when outputContracts is empty', () => {
const ctx = makeContext({
reportDir: '/tmp/reports',
step: makeMovement({ outputContracts: [] }),
});
expect(strategy.canApply(ctx)).toBe(false);
});
it('should return false when outputContracts is undefined', () => {
const ctx = makeContext({
reportDir: '/tmp/reports',
step: makeMovement(),
});
expect(strategy.canApply(ctx)).toBe(false);
});
});
});
describe('ResponseBasedStrategy', () => {
const strategy = new ResponseBasedStrategy();
it('should have name "ResponseBased"', () => {
expect(strategy.name).toBe('ResponseBased');
});
describe('canApply', () => {
it('should return true when lastResponse is non-empty', () => {
const ctx = makeContext({ lastResponse: 'some response' });
expect(strategy.canApply(ctx)).toBe(true);
});
it('should return false when lastResponse is undefined', () => {
const ctx = makeContext({ lastResponse: undefined });
expect(strategy.canApply(ctx)).toBe(false);
});
it('should return false when lastResponse is empty string', () => {
const ctx = makeContext({ lastResponse: '' });
expect(strategy.canApply(ctx)).toBe(false);
});
});
});
describe('AgentConsultStrategy', () => {
const strategy = new AgentConsultStrategy();
it('should have name "AgentConsult"', () => {
expect(strategy.name).toBe('AgentConsult');
});
describe('canApply', () => {
it('should return true when sessionId is non-empty', () => {
const ctx = makeContext({ sessionId: 'session-123' });
expect(strategy.canApply(ctx)).toBe(true);
});
it('should return false when sessionId is undefined', () => {
const ctx = makeContext({ sessionId: undefined });
expect(strategy.canApply(ctx)).toBe(false);
});
it('should return false when sessionId is empty string', () => {
const ctx = makeContext({ sessionId: '' });
expect(strategy.canApply(ctx)).toBe(false);
});
});
describe('execute', () => {
it('should return failure when sessionId is not provided', async () => {
const ctx = makeContext({ sessionId: undefined });
const result = await strategy.execute(ctx);
expect(result.success).toBe(false);
expect(result.reason).toBe('Session ID not provided');
});
});
});
describe('JudgmentStrategyFactory', () => {
it('should create strategies in correct priority order', () => {
const strategies = JudgmentStrategyFactory.createStrategies();
expect(strategies).toHaveLength(4);
expect(strategies[0]!.name).toBe('AutoSelect');
expect(strategies[1]!.name).toBe('ReportBased');
expect(strategies[2]!.name).toBe('ResponseBased');
expect(strategies[3]!.name).toBe('AgentConsult');
});
});