#!/usr/bin/env node /** * Company Layer — E2E 测试脚本 * * 测试内容: * 1. Company CRUD (创建、查询、列表、更新、关停) * 2. 模板库查询 * 3. Agent 招聘 (模板命中 / 模板未命中) * 4. 公司内 Agent 列表 * 5. Agent 解雇 * 6. resolveAgentDir 统一寻址 * * 运行: node server/scripts/test-company-e2e.js */ const path = require('path'); const fs = require('fs'); // 直接引入 service 层 (不需要 HTTP) const { CompanyService } = require('../services/company-service'); const DATA_DIR = path.resolve(__dirname, '../../data'); const TEST_COMPANY_ID = 'test-media-co'; const TEST_COMPANY_DIR = path.join(DATA_DIR, TEST_COMPANY_ID); let passed = 0; let failed = 0; function assert(condition, label) { if (condition) { console.log(` ✅ ${label}`); passed++; } else { console.error(` ❌ ${label}`); failed++; } } function cleanup() { // 清理测试数据 if (fs.existsSync(TEST_COMPANY_DIR)) { fs.rmSync(TEST_COMPANY_DIR, { recursive: true, force: true }); } } async function main() { console.log('\n🏢 Company Layer E2E 测试\n'); cleanup(); const svc = new CompanyService(); // ─── 1. 模板库 ─── console.log('📦 Phase 1: 模板库'); const templates = svc.listTemplates(); assert(Array.isArray(templates), '模板列表返回数组'); assert(templates.length >= 2, `模板数量 ≥ 2 (实际: ${templates.length})`); const ceoTpl = templates.find(t => t.id === 'ceo'); assert(ceoTpl !== undefined, '存在 CEO 模板'); assert(ceoTpl && ceoTpl.type === 'cxo', 'CEO 模板类型为 cxo'); const qaTpl = templates.find(t => t.id === 'qa-judge'); assert(qaTpl !== undefined, '存在 qa-judge 模板'); // ─── 2. 创建公司 ─── console.log('\n🏗️ Phase 2: 创建公司'); const company = svc.create({ id: TEST_COMPANY_ID, name: '测试自媒体公司', description: '一家用于 E2E 测试的自媒体公司', }); assert(company.id === TEST_COMPANY_ID, `公司 ID = ${TEST_COMPANY_ID}`); assert(company.status === 'creating', '初始状态为 creating'); assert(fs.existsSync(path.join(TEST_COMPANY_DIR, 'company.json')), 'company.json 已创建'); assert(fs.existsSync(path.join(TEST_COMPANY_DIR, 'agents')), 'agents/ 目录已创建'); assert(fs.existsSync(path.join(TEST_COMPANY_DIR, 'goals')), 'goals/ 目录已创建'); assert(fs.existsSync(path.join(TEST_COMPANY_DIR, 'workspace')), 'workspace/ 目录已创建'); assert(fs.existsSync(path.join(TEST_COMPANY_DIR, 'pipelines')), 'pipelines/ 目录已创建'); // ─── 3. 查询公司 ─── console.log('\n🔍 Phase 3: 查询公司'); const fetched = svc.get(TEST_COMPANY_ID); assert(fetched !== null, 'get() 返回非空'); assert(fetched.name === '测试自媒体公司', '公司名称正确'); const list = svc.list(); assert(list.some(c => c.id === TEST_COMPANY_ID), 'list() 中包含新公司'); // ─── 4. 招聘 CEO (从模板) ─── console.log('\n👔 Phase 4: 招聘 CEO (模板命中)'); const hireCeo = svc.hire(TEST_COMPANY_ID, 'ceo'); assert(hireCeo.status === 'hired_from_template', '从模板招聘成功'); const ceoDir = path.join(TEST_COMPANY_DIR, 'agents', 'ceo'); assert(fs.existsSync(ceoDir), 'CEO Agent 目录已创建'); assert(fs.existsSync(path.join(ceoDir, 'agent.md')), 'agent.md 已复制'); assert(fs.existsSync(path.join(ceoDir, 'soul.md')), 'soul.md 已复制'); assert(fs.existsSync(path.join(ceoDir, 'config.json')), 'config.json 已复制'); assert(fs.existsSync(path.join(ceoDir, 'memory', 'memory.md')), 'memory/memory.md 已初始化'); // 检查上下文注入 const ceoAgentMd = fs.readFileSync(path.join(ceoDir, 'agent.md'), 'utf-8'); assert(ceoAgentMd.includes(TEST_COMPANY_ID), 'agent.md 注入了公司 ID'); assert(ceoAgentMd.includes('测试自媒体公司'), 'agent.md 注入了公司名称'); const ceoConfig = JSON.parse(fs.readFileSync(path.join(ceoDir, 'config.json'), 'utf-8')); assert(ceoConfig.companyId === TEST_COMPANY_ID, 'config.json 注入了 companyId'); assert(ceoConfig.status === 'active', 'config.json 状态为 active'); // 检查公司元数据更新 const companyAfterCeo = svc.get(TEST_COMPANY_ID); assert(companyAfterCeo.ceo === 'ceo', 'company.json 中 CEO 已标记'); assert(companyAfterCeo.agents.includes('ceo'), 'company.json agents 列表包含 ceo'); // ─── 5. 招聘 qa-judge (从模板) ─── console.log('\n🔍 Phase 5: 招聘 qa-judge (模板命中)'); const hireQa = svc.hire(TEST_COMPANY_ID, 'qa-judge'); assert(hireQa.status === 'hired_from_template', 'qa-judge 从模板招聘成功'); // ─── 6. 招聘不存在的角色 (需要培养) ─── console.log('\n🎓 Phase 6: 招聘不存在的角色 (需培养)'); const hireNew = svc.hire(TEST_COMPANY_ID, 'hot-topic-hunter'); assert(hireNew.status === 'needs_training', '返回 needs_training'); assert(hireNew.needsTraining === true, 'needsTraining 标志为 true'); // ─── 7. 重复招聘检测 ─── console.log('\n🔄 Phase 7: 重复招聘检测'); const hireDup = svc.hire(TEST_COMPANY_ID, 'ceo'); assert(hireDup.status === 'already_exists', '重复招聘返回 already_exists'); // ─── 8. 公司 Agent 列表 ─── console.log('\n📋 Phase 8: 公司 Agent 列表'); const agents = svc.listAgents(TEST_COMPANY_ID); assert(agents.length === 2, `Agent 数量 = 2 (实际: ${agents.length})`); assert(agents.some(a => a.id === 'ceo'), '列表包含 CEO'); assert(agents.some(a => a.id === 'qa-judge'), '列表包含 qa-judge'); // ─── 9. resolveAgentDir ─── console.log('\n📍 Phase 9: resolveAgentDir 统一寻址'); const companyAgentDir = CompanyService.resolveAgentDir(TEST_COMPANY_ID, 'ceo'); assert(companyAgentDir !== null, '公司内 CEO 可定位'); assert(companyAgentDir.includes(TEST_COMPANY_ID), '路径包含公司 ID'); const globalMasterDir = CompanyService.resolveAgentDir(null, 'master'); assert(globalMasterDir !== null, '全局 Master 可定位'); assert(globalMasterDir.includes('data/agents/master'), '全局路径正确'); const notExist = CompanyService.resolveAgentDir(TEST_COMPANY_ID, 'nonexistent-agent'); assert(notExist === null, '不存在的 Agent 返回 null'); // ─── 10. 解雇 Agent ─── console.log('\n🚪 Phase 10: 解雇 Agent'); const fireResult = svc.fire(TEST_COMPANY_ID, 'qa-judge'); assert(fireResult.status === 'suspended', '解雇返回 suspended'); const companyAfterFire = svc.get(TEST_COMPANY_ID); assert(!companyAfterFire.agents.includes('qa-judge'), 'qa-judge 已从 agents 列表移除'); const firedConfig = JSON.parse(fs.readFileSync(path.join(TEST_COMPANY_DIR, 'agents', 'qa-judge', 'config.json'), 'utf-8')); assert(firedConfig.status === 'suspended', 'qa-judge config 状态为 suspended'); // ─── 11. 更新公司 ─── console.log('\n✏️ Phase 11: 更新公司'); const updated = svc.update(TEST_COMPANY_ID, { status: 'running', heartbeat: { enabled: true }, }); assert(updated.status === 'running', '状态更新为 running'); assert(updated.heartbeat.enabled === true, '心跳已开启'); // ─── 12. 关停公司 ─── console.log('\n🛑 Phase 12: 关停公司'); const shutdown = svc.shutdown(TEST_COMPANY_ID); assert(shutdown.status === 'shutdown', '状态变为 shutdown'); assert(shutdown.heartbeat.enabled === false, '心跳已关闭'); // ─── 清理 ─── cleanup(); // ─── 结果 ─── console.log(`\n${'═'.repeat(50)}`); console.log(` 📊 测试结果: ${passed} 通过, ${failed} 失败 (共 ${passed + failed})`); if (failed === 0) { console.log(' 🎉 ALL PASSED — Phase 1 生产就绪!\n'); } else { console.log(` ⚠️ ${failed} 个测试未通过\n`); process.exit(1); } } main().catch(err => { console.error('测试异常:', err); cleanup(); process.exit(1); });