import { describe, it, expect, beforeEach, afterEach } from 'vitest'; const path = require('path'); const fs = require('fs'); const { PromptBuilder } = require('../../core/prompt-builder'); describe('PromptBuilder', () => { const baseDir = path.join(__dirname, '..', 'fixtures'); const configPath = path.join(baseDir, 'config.json'); let config; let builder; beforeEach(() => { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); builder = new PromptBuilder(config, baseDir); }); describe('getProtocol', () => { it('should load executor protocol', () => { const protocol = builder.getProtocol('executor'); expect(protocol).toContain('[RESULT: DONE]'); expect(protocol).toContain('[RESULT: ERROR]'); }); it('should load validator protocol', () => { const protocol = builder.getProtocol('validator'); expect(protocol).toContain('[RESULT: PASS]'); expect(protocol).toContain('[RESULT: FAIL]'); }); it('should load master protocol', () => { const protocol = builder.getProtocol('master'); expect(protocol).toContain('[CALL:'); expect(protocol).toContain('{{AVAILABLE_AGENTS}}'); }); it('should return empty string for unknown type', () => { const protocol = builder.getProtocol('unknown'); expect(protocol).toBe(''); }); }); describe('getAgentPrompt', () => { it('should load coder prompt', () => { const prompt = builder.getAgentPrompt('coder'); expect(prompt).toContain('Coder'); expect(prompt).toContain('代码'); }); it('should load verifier prompt', () => { const prompt = builder.getAgentPrompt('verifier'); expect(prompt).toContain('Verifier'); }); it('should throw for unknown agent', () => { expect(() => builder.getAgentPrompt('unknown')).toThrow('Agent not found'); }); }); describe('build', () => { it('should combine protocol and agent prompt for executor', () => { const result = builder.build('coder'); // 应该包含 executor protocol expect(result).toContain('[RESULT: DONE]'); expect(result).toContain('[RESULT: ERROR]'); // 应该包含 coder prompt expect(result).toContain('Coder'); }); it('should combine protocol and agent prompt for validator', () => { const result = builder.build('verifier'); // 应该包含 validator protocol expect(result).toContain('[RESULT: PASS]'); expect(result).toContain('[RESULT: FAIL]'); // 应该包含 verifier prompt expect(result).toContain('Verifier'); }); it('should replace {{AVAILABLE_AGENTS}} in master protocol', () => { const result = builder.build('master'); // 不应该包含占位符 expect(result).not.toContain('{{AVAILABLE_AGENTS}}'); // 应该包含实际的 agent 列表 expect(result).toContain('coder'); expect(result).toContain('verifier'); expect(result).toContain('frontend-tester'); }); it('should append user task when provided', () => { const result = builder.build('coder', '实现一个计算器'); expect(result).toContain('当前任务'); expect(result).toContain('实现一个计算器'); }); it('should not include task section when no task provided', () => { const result = builder.build('coder'); expect(result).not.toContain('当前任务'); }); }); describe('build - companyContextPath (P1 virtual reference)', () => { const os = require('os'); let tmpFile; afterEach(() => { if (tmpFile && fs.existsSync(tmpFile)) { fs.unlinkSync(tmpFile); } tmpFile = undefined; }); it('appends company-context.md content when companyContextPath exists', () => { tmpFile = path.join(os.tmpdir(), `company-context-${Date.now()}.md`); fs.writeFileSync(tmpFile, '## 所属公司\n- **公司ID**: test-co', 'utf-8'); config.agents.coder.companyContextPath = tmpFile; const result = builder.build('coder'); expect(result).toContain('## 所属公司'); expect(result).toContain('test-co'); }); // in getSystemStateXML() is wall-clock and can legitimately // differ by a millisecond between two build() calls, so strip it before // comparing outputs for structural equality. const stripTimestamp = (s) => s.replace(/.*?<\/timestamp>/, ''); it('does not change output when companyContextPath is null (legacy mode)', () => { const baseline = stripTimestamp(builder.build('coder')); config.agents.coder.companyContextPath = null; const withNull = stripTimestamp(builder.build('coder')); expect(withNull).toBe(baseline); }); it('does not change output when companyContextPath points to a nonexistent file', () => { const baseline = stripTimestamp(builder.build('coder')); config.agents.coder.companyContextPath = path.join(os.tmpdir(), 'does-not-exist-xyz.md'); const withMissing = stripTimestamp(builder.build('coder')); expect(withMissing).toBe(baseline); }); }); describe('caching', () => { it('should cache loaded files', () => { // 第一次加载 builder.build('coder'); // 第二次加载应该使用缓存 const cacheSize = builder.promptCache.size; builder.build('coder'); expect(builder.promptCache.size).toBe(cacheSize); }); }); });