guanghulab/exe-engine/tests/smoke/exe-engine-p1.test.js

300 lines
14 KiB
JavaScript
Raw Permalink Normal View History

// 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);
}
})();