guanghulab/exe-engine/tests/smoke/exe-engine-p1.test.js
2026-05-10 13:12:44 +08:00

300 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// exe-engine/tests/smoke/exe-engine-p1.test.js
// EXE-Engine · Phase 1 冒烟测试
// PRJ-EXE-001 · Phase 1 · ZY-EXE-P1
// 版权:国作登字-2026-A-00037559
'use strict';
const {
createEngine,
ContextManager,
Session,
MultiModelExecutor,
SchedulerV2,
MonitorDashboard,
QwenAdapter
} = require('../../src/index');
let passed = 0;
let failed = 0;
function assert(condition, message) {
if (condition) {
passed++;
// eslint-disable-next-line no-console
console.log(`${message}`);
} else {
failed++;
// eslint-disable-next-line no-console
console.error(`${message}`);
}
}
// eslint-disable-next-line no-console
console.log('🔧 EXE-Engine Phase 1 冒烟测试\n');
const engine = createEngine();
// ── 测试 1: Phase 1 模块导出 ──
// eslint-disable-next-line no-console
console.log('── 测试 1: Phase 1 模块导出 ──');
assert(typeof ContextManager === 'function', 'ContextManager 是构造函数');
assert(typeof Session === 'function', 'Session 是构造函数');
assert(typeof MultiModelExecutor === 'function', 'MultiModelExecutor 是构造函数');
assert(typeof SchedulerV2 === 'function', 'SchedulerV2 是构造函数');
assert(typeof MonitorDashboard === 'function', 'MonitorDashboard 是构造函数');
assert(engine.contextManager instanceof ContextManager, 'engine.contextManager 已创建');
assert(engine.session instanceof Session, 'engine.session 已创建');
assert(engine.multiModelExecutor instanceof MultiModelExecutor, 'engine.multiModelExecutor 已创建');
assert(engine.scheduler instanceof SchedulerV2, 'engine.scheduler 已创建');
assert(engine.monitor instanceof MonitorDashboard, 'engine.monitor 已创建');
// ── 测试 2: ContextManager ──
// eslint-disable-next-line no-console
console.log('\n── 测试 2: ContextManager 上下文管理 ──');
const cm = engine.contextManager;
const ctxId = cm.createContext('AG-ZY-01', 'sess-test-001', { repo: 'guanghulab' });
assert(typeof ctxId === 'string' && ctxId.startsWith('ctx-'), `创建上下文返回 ID: ${ctxId}`);
const ctx = cm.getContext(ctxId);
assert(ctx !== null, '获取上下文成功');
assert(ctx.agentId === 'AG-ZY-01', '上下文 agentId 正确');
assert(ctx.sessionId === 'sess-test-001', '上下文 sessionId 正确');
assert(ctx.data.repo === 'guanghulab', '上下文初始数据正确');
assert(ctx.status === 'active', '上下文初始状态为 active');
assert(Array.isArray(ctx.messages), '上下文 messages 是数组');
const updateOk = cm.updateContext(ctxId, { branch: 'main' });
assert(updateOk === true, '更新上下文成功');
assert(cm.getContext(ctxId).data.branch === 'main', '更新后数据正确');
const addMsgOk = cm.addMessage(ctxId, 'user', '你好');
assert(addMsgOk === true, '添加消息成功');
assert(cm.getContext(ctxId).messages.length === 1, '消息数量正确');
assert(cm.getContext(ctxId).messages[0].role === 'user', '消息角色正确');
const expireOk = cm.expireContext(ctxId);
assert(expireOk === true, '过期上下文成功');
assert(cm.getContext(ctxId).status === 'expired', '过期后状态正确');
const contexts = cm.listContexts('AG-ZY-01');
assert(contexts.length >= 1, 'listContexts 返回结果');
cm.setGridDBPersistence({ mock: true });
assert(cm._gridDB !== null, 'Grid-DB 预留接口设置成功');
const notFoundCtx = cm.getContext('ctx-nonexistent');
assert(notFoundCtx === null, '不存在的上下文返回 null');
// ── 测试 3: Session 会话管理 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 3: Session 会话管理 ──');
const sess = engine.session;
const { sessionId, contextId } = sess.createSession('AG-ZY-01', { topic: '代码审查' });
assert(typeof sessionId === 'string' && sessionId.startsWith('sess-'), `创建会话: ${sessionId}`);
assert(typeof contextId === 'string' && contextId.startsWith('ctx-'), `关联上下文: ${contextId}`);
const sessObj = sess.getSession(sessionId);
assert(sessObj !== null, '获取会话成功');
assert(sessObj.agentId === 'AG-ZY-01', '会话 agentId 正确');
assert(sessObj.status === 'active', '会话初始状态 active');
assert(sessObj.metadata.topic === '代码审查', '会话元数据正确');
const turnOk = sess.addTurn(sessionId, '帮我看看这段代码', '代码看起来没问题');
assert(turnOk === true, '添加轮次成功');
sess.addTurn(sessionId, '还有优化建议吗', '建议使用 const 替代 let');
const history = sess.getHistory(sessionId, 1);
assert(history.length === 1, 'getHistory limit=1 返回 1 条');
assert(history[0].user === '还有优化建议吗', '历史最后一条消息正确');
const fullHistory = sess.getHistory(sessionId);
assert(fullHistory.length === 2, '完整历史 2 条');
const endOk = sess.endSession(sessionId);
assert(endOk === true, '结束会话成功');
assert(sess.getSession(sessionId).status === 'ended', '结束后状态为 ended');
const addAfterEnd = sess.addTurn(sessionId, 'test', 'test');
assert(addAfterEnd === false, '结束后不能添加轮次');
const sessions = sess.listSessions('AG-ZY-01');
assert(sessions.length >= 1, 'listSessions 返回结果');
// ── 测试 4: MultiModelExecutor ──
// eslint-disable-next-line no-console
console.log('\n── 测试 4: MultiModelExecutor 多模型执行器 ──');
const mme = engine.multiModelExecutor;
const availModels = mme.getAvailableModels();
assert(Array.isArray(availModels), 'getAvailableModels 返回数组');
assert(availModels.length === 4, `可用模型 4 个 (实际: ${availModels.length})`);
assert(availModels.includes('deepseek-v3'), '包含 deepseek-v3');
assert(availModels.includes('qwen-max'), '包含 qwen-max');
// ── 测试 5: SchedulerV2 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 5: SchedulerV2 调度器 ──');
const sched = engine.scheduler;
const t1 = sched.enqueue({ agentId: 'AG-ZY-01', priority: 'high', payload: { action: 'build' } });
assert(typeof t1 === 'string', `入队高优先级任务: ${t1}`);
const t2 = sched.enqueue({ agentId: 'AG-ZY-01', priority: 'low', payload: { action: 'lint' } });
const t3 = sched.enqueue({ agentId: 'AG-SY-01', priority: 'normal', payload: { action: 'review' } });
const queue = sched.getQueue();
assert(queue.length === 3, '队列中 3 个任务');
assert(queue[0].priority === 'high', '高优先级排在前面');
const dequeued = sched.dequeue();
assert(dequeued !== null, '出队成功');
assert(dequeued.priority === 'high', '出队的是高优先级任务');
sched.complete(dequeued.taskId, { success: true });
const status = sched.getStatus();
assert(status.completed === 1, '已完成 1 个');
assert(status.pending === 2, '待处理 2 个');
// 依赖测试
const t4 = sched.enqueue({
taskId: 'task-dep-1',
agentId: 'AG-ZY-01',
priority: 'high',
dependsOn: ['task-not-done'],
payload: { action: 'deploy' }
});
const depDequeue = sched.dequeue();
assert(depDequeue?.taskId !== 'task-dep-1', '依赖未满足的任务不出队');
// 标记失败测试
sched.fail('task-fail-test', '测试失败');
assert(sched.getStatus().failed === 1, 'fail 记录成功');
// ── 测试 6: MonitorDashboard ──
// eslint-disable-next-line no-console
console.log('\n── 测试 6: MonitorDashboard 监控仪表盘 ──');
const mon = engine.monitor;
mon.recordMetric('request_count', 1, { model: 'deepseek-v3' });
mon.recordMetric('request_count', 1, { model: 'qwen-max' });
mon.recordMetric('latency_ms', 150, { model: 'deepseek-v3' });
mon.recordMetric('latency_ms', 200, { model: 'qwen-max' });
mon.recordMetric('tokens_used', 1500, {});
mon.recordMetric('error_count', 0, {});
const reqMetrics = mon.getMetrics('request_count');
assert(reqMetrics.length === 2, '获取 request_count 指标 2 条');
const latMetrics = mon.getMetrics('latency_ms');
assert(latMetrics.length === 2, '获取 latency_ms 指标 2 条');
const snapshot = mon.getSnapshot();
assert(typeof snapshot.qps === 'number', 'snapshot 包含 qps');
assert(typeof snapshot.avgLatency === 'number', 'snapshot 包含 avgLatency');
assert(typeof snapshot.totalTokens === 'number', 'snapshot 包含 totalTokens');
assert(typeof snapshot.errorRate === 'number', 'snapshot 包含 errorRate');
assert(snapshot.totalTokens === 1500, `totalTokens = 1500 (实际: ${snapshot.totalTokens})`);
assert(snapshot.metricsCount === 6, `metricsCount = 6 (实际: ${snapshot.metricsCount})`);
mon.resetMetrics();
assert(mon.getMetrics('request_count').length === 0, 'resetMetrics 清除成功');
// ── 测试 7: HLI Persona 路由 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 7: HLI Persona 路由Phase 1 实装) ──');
(async () => {
const loadResult = await engine.router.handleStubRoute('/hli/persona/load');
assert(loadResult.statusCode === 200 || loadResult.statusCode === 400,
`persona/load 返回 ${loadResult.statusCode}`);
// 带参数调用
const personaStubs = engine.router.getHLIStubs().filter(s => s.path === '/hli/persona/load');
const loadHandler = personaStubs[0].handler;
const loadRes = await loadHandler({ body: { persona_id: 'PER-ZY-01' } });
assert(loadRes.statusCode === 200, 'persona/load 加载铸渊成功');
assert(loadRes.body.persona_id === 'PER-ZY-01', '返回正确的 persona_id');
assert(loadRes.body.status === 'loaded', '状态为 loaded');
assert(loadRes.body.name === '铸渊', '人格体名称正确');
const switchStubs = engine.router.getHLIStubs().filter(s => s.path === '/hli/persona/switch');
const switchHandler = switchStubs[0].handler;
const switchRes = await switchHandler({ body: { from_persona_id: 'PER-ZY-01', to_persona_id: 'PER-SY-01' } });
assert(switchRes.statusCode === 200, 'persona/switch 切换成功');
assert(switchRes.body.active_persona_id === 'PER-SY-01', '活跃人格体切换正确');
assert(switchRes.body.status === 'switched', '状态为 switched');
// ── 测试 8: HLI Dialogue 路由 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 8: HLI Dialogue 路由Phase 1 实装) ──');
const dialogueStubs = engine.router.getHLIStubs();
const sendHandler = dialogueStubs.find(s => s.path === '/hli/dialogue/send').handler;
const sendRes = await sendHandler({ body: { session_id: 'test-sess', message: '你好', persona_id: 'PER-ZY-01' } });
assert(sendRes.statusCode === 200, 'dialogue/send 发送成功');
assert(typeof sendRes.body.reply === 'string', '返回 reply 字符串');
assert(sendRes.body.session_id === 'test-sess', '返回正确的 session_id');
assert(typeof sendRes.body.turn_id === 'string', '返回 turn_id');
const histHandler = dialogueStubs.find(s => s.path === '/hli/dialogue/history').handler;
const histRes = await histHandler({ body: { session_id: 'test-sess' } });
assert(histRes.statusCode === 200, 'dialogue/history 查询成功');
assert(Array.isArray(histRes.body.turns), '返回 turns 数组');
assert(histRes.body.total >= 1, `历史记录 total >= 1 (实际: ${histRes.body.total})`);
const streamHandler = dialogueStubs.find(s => s.path === '/hli/dialogue/stream').handler;
const streamRes = await streamHandler({ body: { session_id: 'test-sess' } });
assert(streamRes.statusCode === 200, 'dialogue/stream 返回 200');
assert(streamRes.body.type === 'stream', 'stream 类型正确');
// ── 测试 9: HLI User 路由 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 9: HLI User 路由Phase 1 实装) ──');
const profileHandler = dialogueStubs.find(s => s.path === '/hli/user/profile').handler;
const profileRes = await profileHandler({ body: { user_id: 'USR-001' } });
assert(profileRes.statusCode === 200, 'user/profile 查询成功');
assert(profileRes.body.user_id === 'USR-001', '返回正确的 user_id');
assert(profileRes.body.name === '冰朔', '用户名正确');
const updateHandler = dialogueStubs.find(s => s.path === '/hli/user/profile/update').handler;
const updateRes = await updateHandler({ body: { user_id: 'USR-001', updates: { role: 'superadmin' } } });
assert(updateRes.statusCode === 200, 'user/profile/update 更新成功');
assert(updateRes.body.role === 'superadmin', '角色更新正确');
const notFoundRes = await profileHandler({ body: { user_id: 'USR-NONEXIST' } });
assert(notFoundRes.statusCode === 404, '不存在的用户返回 404');
const newUserRes = await updateHandler({ body: { user_id: 'USR-NEW', updates: { name: '新用户' } } });
assert(newUserRes.statusCode === 200, '更新不存在的用户自动创建');
assert(newUserRes.body.name === '新用户', '新用户名称正确');
// ── 测试 10: Qwen Adapter executeStream ──
// eslint-disable-next-line no-console
console.log('\n── 测试 10: QwenAdapter executeStream ──');
const qwAdapter = engine.adapters.get('qwen-max');
assert(typeof qwAdapter.executeStream === 'function', 'executeStream 方法存在');
// 验证 _resolveModel 增强
assert(qwAdapter._resolveModel('code_generation') === 'qwen-coder-plus-latest', '_resolveModel code_generation 正确');
assert(qwAdapter._resolveModel('text_processing') === 'qwen-max-latest', '_resolveModel text_processing 正确');
assert(qwAdapter._resolveModel('reasoning') === 'qwen-max', '_resolveModel reasoning 正确');
// ── 测试结果汇总 ──
// eslint-disable-next-line no-console
console.log('\n══════════════════════════════════════');
// eslint-disable-next-line no-console
console.log(`🔧 EXE-Engine Phase 1 冒烟测试完成`);
// eslint-disable-next-line no-console
console.log(` ✅ 通过: ${passed}`);
// eslint-disable-next-line no-console
console.log(` ❌ 失败: ${failed}`);
// eslint-disable-next-line no-console
console.log(` 📊 总计: ${passed + failed}`);
// eslint-disable-next-line no-console
console.log('══════════════════════════════════════\n');
if (failed > 0) {
process.exit(1);
}
})();