takt/src/__tests__/rule-utils.test.ts
nrslib 2460dbdf61 refactor(output-contracts): unify OutputContractEntry to item format with use_judge and move runtime dir under .takt
- Remove OutputContractLabelPath (label:path format), unify to OutputContractItem only
- Add required format field and use_judge flag to output contracts
- Add getJudgmentReportFiles() to filter reports eligible for Phase 3 status judgment
- Add supervisor-validation output contract, remove review-summary
- Enhance output contracts with finding_id tracking (new/persists/resolved sections)
- Move runtime environment directory from .runtime to .takt/.runtime
- Update all builtin pieces, e2e fixtures, and tests
2026-02-15 11:17:55 +09:00

156 lines
5.0 KiB
TypeScript

/**
* Unit tests for rule-utils
*
* Tests tag-based rule detection, single-branch auto-selection,
* and report file extraction from output contracts.
*/
import { describe, it, expect } from 'vitest';
import {
hasTagBasedRules,
hasOnlyOneBranch,
getAutoSelectedTag,
getReportFiles,
} from '../core/piece/evaluation/rule-utils.js';
import type { OutputContractEntry } from '../core/models/types.js';
import { makeMovement } from './test-helpers.js';
describe('hasTagBasedRules', () => {
it('should return false when movement has no rules', () => {
const step = makeMovement({ rules: undefined });
expect(hasTagBasedRules(step)).toBe(false);
});
it('should return false when rules array is empty', () => {
const step = makeMovement({ rules: [] });
expect(hasTagBasedRules(step)).toBe(false);
});
it('should return true when rules contain tag-based conditions', () => {
const step = makeMovement({
rules: [
{ condition: 'approved' },
{ condition: 'rejected' },
],
});
expect(hasTagBasedRules(step)).toBe(true);
});
it('should return false when all rules are ai() conditions', () => {
const step = makeMovement({
rules: [
{ condition: 'approved', isAiCondition: true, aiConditionText: 'is it approved?' },
{ condition: 'rejected', isAiCondition: true, aiConditionText: 'is it rejected?' },
],
});
expect(hasTagBasedRules(step)).toBe(false);
});
it('should return false when all rules are aggregate conditions', () => {
const step = makeMovement({
rules: [
{ condition: 'all approved', isAggregateCondition: true, aggregateType: 'all', aggregateConditionText: 'approved' },
],
});
expect(hasTagBasedRules(step)).toBe(false);
});
it('should return true when mixed rules include tag-based ones', () => {
const step = makeMovement({
rules: [
{ condition: 'approved', isAiCondition: true, aiConditionText: 'approved?' },
{ condition: 'manual check' },
],
});
expect(hasTagBasedRules(step)).toBe(true);
});
});
describe('hasOnlyOneBranch', () => {
it('should return false when rules is undefined', () => {
const step = makeMovement({ rules: undefined });
expect(hasOnlyOneBranch(step)).toBe(false);
});
it('should return false when rules array is empty', () => {
const step = makeMovement({ rules: [] });
expect(hasOnlyOneBranch(step)).toBe(false);
});
it('should return true when exactly one rule exists', () => {
const step = makeMovement({
rules: [{ condition: 'done', next: 'COMPLETE' }],
});
expect(hasOnlyOneBranch(step)).toBe(true);
});
it('should return false when multiple rules exist', () => {
const step = makeMovement({
rules: [
{ condition: 'approved', next: 'implement' },
{ condition: 'rejected', next: 'review' },
],
});
expect(hasOnlyOneBranch(step)).toBe(false);
});
});
describe('getAutoSelectedTag', () => {
it('should return uppercase tag for single-branch movement', () => {
const step = makeMovement({
name: 'ai-review',
rules: [{ condition: 'done', next: 'COMPLETE' }],
});
expect(getAutoSelectedTag(step)).toBe('[AI-REVIEW:1]');
});
it('should throw when multiple branches exist', () => {
const step = makeMovement({
rules: [
{ condition: 'approved', next: 'implement' },
{ condition: 'rejected', next: 'review' },
],
});
expect(() => getAutoSelectedTag(step)).toThrow('Cannot auto-select tag when multiple branches exist');
});
it('should throw when no rules exist', () => {
const step = makeMovement({ rules: undefined });
expect(() => getAutoSelectedTag(step)).toThrow('Cannot auto-select tag when multiple branches exist');
});
});
describe('getReportFiles', () => {
it('should return empty array when outputContracts is undefined', () => {
expect(getReportFiles(undefined)).toEqual([]);
});
it('should return empty array when outputContracts is empty', () => {
expect(getReportFiles([])).toEqual([]);
});
it('should extract name from OutputContractItem entries', () => {
const contracts: OutputContractEntry[] = [
{ name: '00-plan.md', format: '00-plan', useJudge: true },
{ name: '01-review.md', format: '01-review', useJudge: true },
];
expect(getReportFiles(contracts)).toEqual(['00-plan.md', '01-review.md']);
});
it('should extract path from OutputContractLabelPath entries', () => {
const contracts: OutputContractEntry[] = [
{ name: 'scope.md', format: 'scope', useJudge: true },
{ name: 'decisions.md', format: 'decisions', useJudge: true },
];
expect(getReportFiles(contracts)).toEqual(['scope.md', 'decisions.md']);
});
it('should handle mixed entry types', () => {
const contracts: OutputContractEntry[] = [
{ name: '00-plan.md', format: '00-plan', useJudge: true },
{ name: 'review.md', format: 'review', useJudge: true },
];
expect(getReportFiles(contracts)).toEqual(['00-plan.md', 'review.md']);
});
});