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

286 lines
12 KiB
JavaScript
Raw Permalink Normal View History

// exe-engine/tests/smoke/exe-engine.test.js
// EXE-Engine · 冒烟测试
// PRJ-EXE-001 · Phase 0 · ZY-EXE-P0-004
// 版权:国作登字-2026-A-00037559
'use strict';
const path = require('path');
// 直接引入模块(不走 npm install
const {
createEngine,
AGERouter,
LoadBalancer,
ResourceMeter,
ContextCache,
AgentController,
BaseAdapter,
DeepSeekAdapter,
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 0\n');
// ── 测试 1: 模块导出 ──
// eslint-disable-next-line no-console
console.log('── 测试 1: 模块导出完整性 ──');
assert(typeof createEngine === 'function', 'createEngine 是函数');
assert(typeof AGERouter === 'function', 'AGERouter 是构造函数');
assert(typeof LoadBalancer === 'function', 'LoadBalancer 是构造函数');
assert(typeof ResourceMeter === 'function', 'ResourceMeter 是构造函数');
assert(typeof ContextCache === 'function', 'ContextCache 是构造函数');
assert(typeof AgentController === 'function', 'AgentController 是构造函数');
assert(typeof BaseAdapter === 'function', 'BaseAdapter 是构造函数');
assert(typeof DeepSeekAdapter === 'function', 'DeepSeekAdapter 是构造函数');
assert(typeof QwenAdapter === 'function', 'QwenAdapter 是构造函数');
// ── 测试 2: createEngine 初始化 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 2: createEngine 初始化 ──');
const engine = createEngine();
assert(engine.router instanceof AGERouter, 'router 是 AGERouter 实例');
assert(engine.meter instanceof ResourceMeter, 'meter 是 ResourceMeter 实例');
assert(engine.cache instanceof ContextCache, 'cache 是 ContextCache 实例');
assert(engine.controller instanceof AgentController, 'controller 是 AgentController 实例');
assert(engine.balancer instanceof LoadBalancer, 'balancer 是 LoadBalancer 实例');
assert(engine.adapters instanceof Map, 'adapters 是 Map 实例');
assert(engine.adapters.size === 4, `4 个适配器已注册 (实际: ${engine.adapters.size})`);
// ── 测试 3: 适配器注册 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 3: 适配器注册 ──');
assert(engine.adapters.has('deepseek-v3'), 'deepseek-v3 适配器已注册');
assert(engine.adapters.has('deepseek-r1'), 'deepseek-r1 适配器已注册');
assert(engine.adapters.has('qwen-max'), 'qwen-max 适配器已注册');
assert(engine.adapters.has('qwen-coder'), 'qwen-coder 适配器已注册');
const dsAdapter = engine.adapters.get('deepseek-v3');
assert(dsAdapter instanceof DeepSeekAdapter, 'deepseek-v3 是 DeepSeekAdapter 实例');
assert(dsAdapter.supports('code'), 'deepseek-v3 支持 code 能力');
assert(dsAdapter.supports('reasoning'), 'deepseek-v3 支持 reasoning 能力');
const qwAdapter = engine.adapters.get('qwen-max');
assert(qwAdapter instanceof QwenAdapter, 'qwen-max 是 QwenAdapter 实例');
assert(qwAdapter.supports('text'), 'qwen-max 支持 text 能力');
// ── 测试 4: BaseAdapter 不可直接实例化 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 4: BaseAdapter 约束 ──');
let baseError = null;
try {
new BaseAdapter({ name: 'test' });
} catch (e) {
baseError = e;
}
assert(baseError !== null, 'BaseAdapter 不可直接实例化');
assert(baseError && baseError.message.includes('不可直接实例化'), '错误信息正确');
// ── 测试 5: 适配器状态 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 5: 适配器状态 ──');
const dsStatus = dsAdapter.getStatus();
assert(dsStatus.name === 'deepseek-v3', '状态名称正确');
assert(dsStatus.healthy === true, '初始状态健康');
assert(dsStatus.inCooldown === false, '初始无冷却');
assert(dsStatus.failureCount === 0, '初始无失败');
// ── 测试 6: 冷却期机制 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 6: 冷却期机制 ──');
const testAdapter = engine.adapters.get('deepseek-r1');
testAdapter.recordFailure('test1');
testAdapter.recordFailure('test2');
assert(testAdapter.isInCooldown() === false, '2 次失败不触发冷却');
testAdapter.recordFailure('test3');
assert(testAdapter.isInCooldown() === true, '3 次失败触发冷却');
assert(testAdapter.getStatus().healthy === false, '冷却期标记不健康');
testAdapter.resetFailures();
assert(testAdapter.isInCooldown() === false, '重置后退出冷却');
assert(testAdapter.getStatus().healthy === true, '重置后恢复健康');
// ── 测试 7: LoadBalancer 选择 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 7: LoadBalancer 选择 ──');
const selected = engine.balancer.select({
taskType: 'code_generation',
model: 'auto',
priority: 'balanced'
});
assert(selected !== null, 'balanced 策略返回适配器');
assert(selected.name === 'deepseek-v3', 'code_generation 首选 deepseek-v3');
const costSelected = engine.balancer.select({
taskType: 'code_generation',
model: 'auto',
priority: 'cost'
});
assert(costSelected !== null, 'cost 策略返回适配器');
const qualitySelected = engine.balancer.select({
taskType: 'reasoning',
model: 'auto',
priority: 'quality'
});
assert(qualitySelected !== null, 'quality 策略返回适配器');
assert(qualitySelected.name === 'deepseek-r1', 'reasoning 首选 deepseek-r1');
// 指定模型
const specificSelected = engine.balancer.select({
taskType: 'text_processing',
model: 'qwen-max',
priority: 'balanced'
});
assert(specificSelected !== null, '指定模型返回适配器');
assert(specificSelected.name === 'qwen-max', '返回指定的 qwen-max');
// 故障转移
const failover = engine.balancer.failover('deepseek-v3', 'code_generation');
assert(failover !== null, '故障转移返回备选');
assert(failover.name !== 'deepseek-v3', '备选不等于失败模型');
// ── 测试 8: ResourceMeter 计量 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 8: ResourceMeter 计量 ──');
const freshMeter = new ResourceMeter();
// Clear any previous state by using a fresh meter and tracking deltas
const beforeSummary = freshMeter.getSummary();
const beforeRequests = beforeSummary.totalRequests;
freshMeter.record({
requestId: 'test-001',
agentId: 'AG-ZY-01',
model: 'deepseek-v3',
taskType: 'code_generation',
resourcePool: 'default',
mode: 'pool',
inputTokens: 1000,
outputTokens: 500,
cost: 0.002,
costUnit: 'CNY',
latency: 1500,
status: 'success'
});
const summary = freshMeter.getSummary();
assert(summary.totalRequests === beforeRequests + 1, '总请求数增加 1');
assert(summary.byModel['deepseek-v3'] !== undefined, '按模型统计存在 deepseek-v3');
assert(summary.byAgent['AG-ZY-01'] !== undefined, '按 Agent 统计存在 AG-ZY-01');
const agentUsage = freshMeter.getAgentUsage('AG-ZY-01');
assert(agentUsage !== null, 'Agent 使用记录存在');
assert(agentUsage.inputTokens >= 1000, 'Agent 输入 token >= 1000');
const recent = freshMeter.getRecentRecords(10);
assert(recent.length >= 1, '最近记录数 >= 1');
assert(recent[recent.length - 1].requestId === 'test-001', '最近一条记录 ID 正确');
// ── 测试 9: ContextCache ──
// eslint-disable-next-line no-console
console.log('\n── 测试 9: ContextCache 缓存 ──');
engine.cache.set('test-key', { repo: 'guanghulab', branch: 'main' });
const cached = engine.cache.get('test-key');
assert(cached !== null, '缓存命中');
assert(cached.repo === 'guanghulab', '缓存内容正确');
const missed = engine.cache.get('nonexistent');
assert(missed === null, '缓存未命中返回 null');
const cacheStatus = engine.cache.getStatus();
assert(cacheStatus.size === 1, '缓存大小 = 1');
assert(cacheStatus.maxEntries === 100, '最大容量 = 100');
engine.cache.delete('test-key');
assert(engine.cache.get('test-key') === null, '删除后缓存未命中');
// ── 测试 10: AgentController ──
// eslint-disable-next-line no-console
console.log('\n── 测试 10: AgentController 调度 ──');
const zyPref = engine.controller.getPreference('AG-ZY-01');
assert(zyPref.model === 'auto', '铸渊偏好 model=auto');
assert(zyPref.priority === 'balanced', '铸渊偏好 priority=balanced');
const syPref = engine.controller.getPreference('AG-SY-01');
assert(syPref.priority === 'quality', '霜砚偏好 priority=quality');
const qqPref = engine.controller.getPreference('AG-QQ-01');
assert(qqPref.priority === 'cost', '秋秋偏好 priority=cost');
const unknownPref = engine.controller.getPreference('AG-UNKNOWN');
assert(unknownPref.model === 'auto', '未注册 Agent 返回默认偏好');
const exeRequest = engine.controller.buildExeRequest('AG-ZY-01', {
prompt: 'Hello World',
taskType: 'code_generation'
});
assert(exeRequest.agentId === 'AG-ZY-01', 'EXE 请求 agentId 正确');
assert(exeRequest.taskType === 'code_generation', 'EXE 请求 taskType 正确');
assert(exeRequest.prompt === 'Hello World', 'EXE 请求 prompt 正确');
assert(exeRequest.resourcePool === 'default', 'EXE 请求 resourcePool 正确');
const agents = engine.controller.getRegisteredAgents();
assert(agents.length === 3, '3 个 Agent 已注册');
// ── 测试 11: AGE-Router 状态 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 11: AGE-Router 状态 ──');
const routerStatus = engine.router.getStatus();
assert(routerStatus.router === 'AGE-Router v1.0.0', 'Router 版本正确');
assert(routerStatus.project === 'PRJ-EXE-001', '项目编号正确');
assert(routerStatus.phase === 'Phase 0', '阶段正确');
assert(Array.isArray(routerStatus.adapters), '适配器状态是数组');
assert(routerStatus.adapters.length === 4, '4 个适配器状态');
// ── 测试 12: AGE-Router 错误处理(无 API Key ──
// eslint-disable-next-line no-console
console.log('\n── 测试 12: AGE-Router 错误处理 ──');
(async () => {
const result = await engine.router.execute({
agentId: 'AG-ZY-01',
taskType: 'code_generation',
prompt: 'test prompt',
preferences: { model: 'auto', priority: 'balanced' }
});
// 无 API Key 时应该返回错误(网络错误或认证错误)
assert(result.requestId && result.requestId.startsWith('exe-'), '返回带 exe- 前缀的 requestId');
assert(typeof result.latency === 'number', '返回 latency');
assert(result.status === 'error', '无 API Key 时返回 error 状态');
assert(result.error && result.error.code, '错误响应包含 error.code');
assert(result.error && result.error.message, '错误响应包含 error.message');
// ── 测试结果汇总 ──
// eslint-disable-next-line no-console
console.log('\n══════════════════════════════════════');
// eslint-disable-next-line no-console
console.log(`🔧 EXE-Engine 冒烟测试完成`);
// 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);
}
})();