diff --git a/planning-contract/contract-validator.test.ts b/planning-contract/contract-validator.test.ts new file mode 100644 index 0000000..ed57a15 --- /dev/null +++ b/planning-contract/contract-validator.test.ts @@ -0,0 +1,312 @@ +/** + * 계획 계약 검증기 테스트 + */ + +import { + validateGoalClarity, + validateStepCompleteness, + validateExecutability, + validateRiskAssessment, + validateVerifiability, + validateTemporalConstraints, + validateRollbackCapability, + validateContract, + createEpitaph +} from './contract-validator'; +import { Plan } from './types'; + +describe('계획 계약 검증기', () => { + describe('조항 1: 목표 명확성', () => { + it('명확한 목표는 통과해야 함', () => { + const plan: Plan = { + id: 'test-1', + goal: { + description: '테스트 파일 10개 생성', + criteria: ['파일 10개 생성됨', '각 파일 크기 > 0'], + produces: ['test-files'] + }, + steps: [] + }; + + const result = validateGoalClarity(plan); + expect(result.passed).toBe(true); + expect(result.issues).toHaveLength(0); + }); + + it('모호한 표현이 있으면 실패해야 함', () => { + const plan: Plan = { + id: 'test-2', + goal: { + description: '테스트 파일을 잘 생성', + criteria: [] + }, + steps: [] + }; + + const result = validateGoalClarity(plan); + expect(result.passed).toBe(false); + expect(result.issues.length).toBeGreaterThan(0); + }); + + it('판단 기준이 없으면 실패해야 함', () => { + const plan: Plan = { + id: 'test-3', + goal: { + description: '파일 생성', + criteria: [] + }, + steps: [] + }; + + const result = validateGoalClarity(plan); + expect(result.passed).toBe(false); + expect(result.issues).toContain('목표 달성 판단 기준이 없음'); + }); + }); + + describe('조항 2: 단계 완전성', () => { + it('올바른 의존성 순서는 통과해야 함', () => { + const plan: Plan = { + id: 'test-4', + goal: { + description: '빌드 완료', + criteria: ['빌드 성공'], + produces: ['build-output'] + }, + steps: [ + { id: 'step1', description: '의존성 설치', produces: ['deps'] }, + { id: 'step2', description: '컴파일', dependencies: ['step1'], produces: ['build-output'] } + ] + }; + + const result = validateStepCompleteness(plan); + expect(result.passed).toBe(true); + }); + + it('순서 위반이 있으면 실패해야 함', () => { + const plan: Plan = { + id: 'test-5', + goal: { + description: '빌드 완료', + criteria: ['빌드 성공'] + }, + steps: [ + { id: 'step1', description: '컴파일', dependencies: ['step2'] }, + { id: 'step2', description: '의존성 설치' } + ] + }; + + const result = validateStepCompleteness(plan); + expect(result.passed).toBe(false); + }); + }); + + describe('조항 3: 실행 가능성', () => { + it('사용 가능한 명령어는 통과해야 함', () => { + const plan: Plan = { + id: 'test-6', + goal: { description: '테스트', criteria: ['완료'] }, + steps: [ + { id: 'step1', command: 'npm', timeout: 30000 } + ] + }; + + const result = validateExecutability(plan, ['npm', 'node']); + expect(result.passed).toBe(true); + }); + + it('타임아웃 없으면 실패해야 함', () => { + const plan: Plan = { + id: 'test-7', + goal: { description: '테스트', criteria: ['완료'] }, + steps: [ + { id: 'step1', command: 'npm' } + ] + }; + + const result = validateExecutability(plan, ['npm']); + expect(result.passed).toBe(false); + expect(result.issues).toContain('단계 step1: 타임아웃 미설정'); + }); + }); + + describe('조항 4: 리스크 평가', () => { + it('리스크와 완화 전략이 있으면 통과해야 함', () => { + const plan: Plan = { + id: 'test-8', + goal: { description: '배포', criteria: ['성공'] }, + steps: [], + risks: [ + { + id: 'risk1', + description: '네트워크 오류', + severity: 'medium', + mitigation: '재시도 로직 구현' + } + ] + }; + + const result = validateRiskAssessment(plan); + expect(result.passed).toBe(true); + }); + + it('고위험 리스크에 대안 경로 없으면 실패해야 함', () => { + const plan: Plan = { + id: 'test-9', + goal: { description: '배포', criteria: ['성공'] }, + steps: [], + risks: [ + { + id: 'risk1', + description: '데이터 손상', + severity: 'critical' + } + ] + }; + + const result = validateRiskAssessment(plan); + expect(result.passed).toBe(false); + }); + }); + + describe('조항 5: 검증 가능성', () => { + it('예상 결과와 어설션이 있으면 통과해야 함', () => { + const plan: Plan = { + id: 'test-10', + goal: { description: '파일 생성', criteria: ['완료'] }, + steps: [ + { + id: 'step1', + description: '파일 생성', + expectedOutcome: '파일이 생성됨', + assertions: ['file.exists()'] + } + ] + }; + + const result = validateVerifiability(plan); + expect(result.passed).toBe(true); + }); + + it('어설션 없으면 실패해야 함', () => { + const plan: Plan = { + id: 'test-11', + goal: { description: '파일 생성', criteria: ['완료'] }, + steps: [ + { + id: 'step1', + description: '파일 생성', + expectedOutcome: '파일이 생성됨' + } + ] + }; + + const result = validateVerifiability(plan); + expect(result.passed).toBe(false); + }); + }); + + describe('조항 6: 시간적 제약', () => { + it('시간 제약이 설정되면 통과해야 함', () => { + const plan: Plan = { + id: 'test-12', + goal: { description: '빌드', criteria: ['완료'] }, + steps: [ + { id: 'step1', timeout: 60000 } + ], + estimatedDuration: 120000, + timeoutStrategy: '실패 후 롤백' + }; + + const result = validateTemporalConstraints(plan); + expect(result.passed).toBe(true); + }); + }); + + describe('조항 7: 롤백 가능성', () => { + it('올바른 롤백 시퀀스가 있으면 통과해야 함', () => { + const plan: Plan = { + id: 'test-13', + goal: { description: '배포', criteria: ['완료'] }, + steps: [], + rollbackCommands: [ + { id: 'rb1', command: 'rollback-step2', order: 2, verification: 'check-state' }, + { id: 'rb2', command: 'rollback-step1', order: 1, verification: 'check-state' } + ] + }; + + const result = validateRollbackCapability(plan); + expect(result.passed).toBe(true); + }); + }); + + describe('전체 계약 검증', () => { + it('모든 조항 충족 시 A 등급 부여', () => { + const plan: Plan = { + id: 'test-full', + goal: { + description: '완전한 테스트 계획', + criteria: ['모든 단계 성공'], + produces: ['result'] + }, + steps: [ + { + id: 'step1', + description: '준비', + command: 'prepare', + timeout: 10000, + expectedOutcome: '준비 완료', + assertions: ['ready'], + produces: ['prepared'] + }, + { + id: 'step2', + description: '실행', + command: 'execute', + dependencies: ['step1'], + timeout: 30000, + expectedOutcome: '실행 완료', + assertions: ['completed'], + produces: ['result'] + } + ], + risks: [ + { + id: 'risk1', + description: '실패 가능성', + severity: 'low', + mitigation: '재시도' + } + ], + estimatedDuration: 60000, + timeoutStrategy: '중단', + rollbackCommands: [ + { id: 'rb1', command: 'cleanup', order: 1, verification: 'check-clean' } + ] + }; + + const result = validateContract(plan, ['prepare', 'execute']); + expect(result.grade).toBe('A'); + }); + }); + + describe('에피타이드 생성', () => { + it('올바른 에피타이드를 생성해야 함', () => { + const plan: Plan = { + id: 'test-epitaph', + goal: { description: '테스트', criteria: ['완료'] }, + steps: [] + }; + + const validation = validateContract(plan); + const epitaph = createEpitaph(plan, validation, { + status: 'success', + duration: 5000 + }); + + expect(epitaph.planId).toBe('test-epitaph'); + expect(epitaph.timestamp).toBeDefined(); + expect(epitaph.executionResult.status).toBe('success'); + }); + }); +});