test-company-e2e.js
7.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/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);
});