zhizhi/scripts/agents/tests/governance.test.js

427 lines
22 KiB
JavaScript
Raw Normal View History

// scripts/agents/tests/governance.test.js
// Governance System · 治理系统综合测试
// ZY-TEST-GOV-001 · Phase 1
// 版权:国作登字-2026-A-00037559
'use strict';
const path = require('path');
const fs = require('fs');
let passed = 0;
let failed = 0;
function assert(condition, message) {
if (condition) {
passed++;
console.log(`${message}`);
} else {
failed++;
console.error(`${message}`);
}
}
console.log('🏛️ Governance System 综合测试\n');
// ══════════════════════════════════════════════════════════════════════════
// 测试 1: Twin Merge Engine
// ══════════════════════════════════════════════════════════════════════════
console.log('── 测试 1: Twin Merge Engine ──');
const { merge, computeComposite } = require('../twin-merge');
const leftMetrics = { taskCompletion: 0.9, testPassRate: 0.85, codeCoverage: 1.0, securityScore: 1.0 };
const rightMetrics = { taskCompletion: 0.95, testPassRate: 0.9, codeCoverage: 1.0, securityScore: 1.0 };
const mergeResult = merge(leftMetrics, rightMetrics);
assert(typeof mergeResult.left === 'object', 'merge 返回 left 对象');
assert(typeof mergeResult.right === 'object', 'merge 返回 right 对象');
assert(typeof mergeResult.balance === 'number', 'merge 返回 balance 数值');
assert(mergeResult.balance >= 0 && mergeResult.balance <= 1, 'balance 在 0-1 范围内');
assert(typeof mergeResult.timestamp === 'string', 'merge 返回 timestamp');
// 验证 composite 计算
const expectedLeft = Math.round((0.4*0.9 + 0.25*0.85 + 0.2*1.0 + 0.15*1.0) * 10000) / 10000;
assert(mergeResult.left.composite === expectedLeft, `左翼 composite = ${expectedLeft} (实际: ${mergeResult.left.composite})`);
// 完全相同的指标 → balance = 1.0
const perfectMerge = merge(leftMetrics, leftMetrics);
assert(perfectMerge.balance === 1, '相同指标 → 完美平衡 (balance=1.0)');
// computeComposite 单独测试
const comp = computeComposite({ taskCompletion: 1, testPassRate: 1, codeCoverage: 1, securityScore: 1 });
assert(comp === 1, '全满分 composite = 1.0');
// ══════════════════════════════════════════════════════════════════════════
// 测试 2: Balance Checker
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 2: Balance Checker ──');
const { check } = require('../balance-checker');
// 完美平衡
const balancedResult = check(perfectMerge);
assert(balancedResult.balanced === true, '完美平衡 → balanced=true');
assert(balancedResult.alert === null, '完美平衡 → alert=null');
assert(balancedResult.drift === 0, '完美平衡 → drift=0');
// 有偏差
const driftResult = check(mergeResult);
assert(typeof driftResult.balanced === 'boolean', 'check 返回 balanced 布尔值');
assert(typeof driftResult.drift === 'number', 'check 返回 drift 数值');
// 大偏差测试
const bigDrift = merge(
{ taskCompletion: 0.5, testPassRate: 0.5, codeCoverage: 0.5, securityScore: 0.5 },
{ taskCompletion: 1.0, testPassRate: 1.0, codeCoverage: 1.0, securityScore: 1.0 }
);
const bigCheck = check(bigDrift);
assert(bigCheck.balanced === false, '大偏差 → balanced=false');
assert(bigCheck.alert !== null, '大偏差 → 有告警');
assert(bigCheck.alert.level === 'high', '大偏差 (0.5) → high 级别');
// 小偏差测试
const smallDrift = merge(
{ taskCompletion: 0.98, testPassRate: 0.98, codeCoverage: 1.0, securityScore: 1.0 },
{ taskCompletion: 1.0, testPassRate: 1.0, codeCoverage: 1.0, securityScore: 1.0 }
);
const smallCheck = check(smallDrift);
if (!smallCheck.balanced && smallCheck.alert) {
assert(smallCheck.alert.level === 'low', '小偏差 → low 级别');
} else {
assert(true, '小偏差 → 近似平衡');
}
// ══════════════════════════════════════════════════════════════════════════
// 测试 3: Auto-Repair Classifier
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 3: Auto-Repair Classifier ──');
const { diagnose, classify, verify } = require('../auto-repair');
const lowAlert = { level: 'low', laggingSide: 'left', deficit: 0.02 };
const medAlert = { level: 'medium', laggingSide: 'right', deficit: 0.1 };
const highAlert = { level: 'high', laggingSide: 'left', deficit: 0.3 };
const lowDiag = diagnose(lowAlert);
assert(lowDiag.severity === 'low', 'low alert → severity=low');
const medDiag = diagnose(medAlert);
assert(medDiag.severity === 'medium', 'medium alert → severity=medium');
const highDiag = diagnose(highAlert);
assert(highDiag.severity === 'high', 'high alert → severity=high');
const lowClass = classify(lowDiag);
assert(lowClass.level === 'L1', 'low severity → L1 自动修复');
assert(lowClass.autoFixable === true, 'L1 → autoFixable=true');
const medClass = classify(medDiag);
assert(medClass.level === 'L2', 'medium severity → L2 技术干预');
assert(medClass.autoFixable === false, 'L2 → autoFixable=false');
const highClass = classify(highDiag);
assert(highClass.level === 'L3', 'high severity → L3 架构干预');
// verify 测试
const verifySuccess = verify({ success: true });
assert(verifySuccess.verified === true, '成功修复 → verified=true');
const verifyFail = verify({ success: false, level: 'L2' });
assert(verifyFail.verified === false, '未修复 → verified=false');
// null alert
const nullDiag = diagnose(null);
assert(nullDiag.severity === 'none', 'null alert → severity=none');
// ══════════════════════════════════════════════════════════════════════════
// 测试 4: Escalation Router
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 4: Escalation Router ──');
const { classify: escClassify, route, escalate } = require('../escalation-router');
// L1 类型
assert(escClassify({ type: 'test_flaky' }).level === 'L1', 'test_flaky → L1');
assert(escClassify({ type: 'lint_error' }).level === 'L1', 'lint_error → L1');
assert(escClassify({ type: 'stub_missing' }).level === 'L1', 'stub_missing → L1');
assert(escClassify({ type: 'format_issue' }).level === 'L1', 'format_issue → L1');
// L2 类型
assert(escClassify({ type: 'test_persistent_fail' }).level === 'L2', 'test_persistent_fail → L2');
assert(escClassify({ type: 'dependency_conflict' }).level === 'L2', 'dependency_conflict → L2');
assert(escClassify({ type: 'security_alert' }).level === 'L2', 'security_alert → L2');
// L3 类型
assert(escClassify({ type: 'balance_long_drift' }).level === 'L3', 'balance_long_drift → L3');
assert(escClassify({ type: 'ontology_conflict' }).level === 'L3', 'ontology_conflict → L3');
assert(escClassify({ type: 'cross_system_architecture' }).level === 'L3', 'cross_system_architecture → L3');
// 路由测试
const l1Route = route({ type: 'test_flaky' });
assert(l1Route.handler === 'auto_repair', 'L1 handler = auto_repair');
assert(l1Route.notify === null, 'L1 notify = null');
const l2Route = route({ type: 'security_alert' });
assert(l2Route.notify !== null, 'L2 有通知目标');
// 完整升级流程
const escResult = escalate({ type: 'ontology_conflict', description: '测试' });
assert(escResult.classification.level === 'L3', 'escalate 返回正确分类');
assert(typeof escResult.timestamp === 'string', 'escalate 返回 timestamp');
// ══════════════════════════════════════════════════════════════════════════
// 测试 5: Bulletin Manager
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 5: Bulletin Manager ──');
const { addEvent, updateStatus, getActive, getRecent, prune, loadBulletin, saveBulletin } = require('../bulletin-manager');
// 清理测试环境
const ROOT = path.resolve(__dirname, '../../..');
const bulletinTestPath = path.join(ROOT, '.github/tianyen/bulletin-data.json');
const bulletinBackup = fs.existsSync(bulletinTestPath) ? fs.readFileSync(bulletinTestPath, 'utf8') : null;
// 重置
saveBulletin({ events: [] });
// 添加事件
const added1 = addEvent({ id: 'TEST-001', event: '测试事件1', status: 'active', handler: 'auto', priority: 1 });
assert(added1 === true, 'addEvent 成功添加');
const added2 = addEvent({ id: 'TEST-002', event: '测试事件2', status: 'active', handler: 'manual', priority: 2 });
assert(added2 === true, 'addEvent 添加第二个事件');
// 去重
const addDup = addEvent({ id: 'TEST-001', event: '重复事件', status: 'active' });
assert(addDup === false, 'addEvent 去重:相同 id 不重复添加');
// 获取活跃事件
const active = getActive();
assert(active.length === 2, `getActive 返回 2 个活跃事件 (实际: ${active.length})`);
// 更新状态
const updated = updateStatus('TEST-001', 'resolved');
assert(updated === true, 'updateStatus 成功更新');
const activeAfter = getActive();
assert(activeAfter.length === 1, `resolved 后活跃事件 = 1 (实际: ${activeAfter.length})`);
// 获取最近事件
const recent = getRecent(5);
assert(recent.length === 2, `getRecent 返回所有事件 (含 resolved)`);
// 清理过期事件(用极小 maxAge 测试)
addEvent({ id: 'TEST-OLD', timestamp: '2020-01-01T00:00:00Z', event: '旧事件', status: 'active' });
const pruned = prune(1000); // 1秒前的都清理
assert(pruned >= 1, `prune 清理了至少 1 条过期事件 (清理了: ${pruned})`);
// 恢复测试数据
if (bulletinBackup) {
fs.writeFileSync(bulletinTestPath, bulletinBackup, 'utf8');
} else {
saveBulletin({ events: [] });
}
// ══════════════════════════════════════════════════════════════════════════
// 测试 6: Actions Parser
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 6: Actions Parser ──');
const { parseWorkflowOutput, parsePRMerge, parseTestReport } = require('../actions-parser');
// parseWorkflowOutput
const wfOutput = ' ✅ test1\n ✅ test2\n ❌ test3\n Error: something\n';
const wfResult = parseWorkflowOutput(wfOutput);
assert(wfResult.tests.passed === 2, 'parseWorkflowOutput: 2 passed');
assert(wfResult.tests.failed === 1, 'parseWorkflowOutput: 1 failed');
assert(wfResult.tests.total === 3, 'parseWorkflowOutput: 3 total');
assert(wfResult.errors.length >= 1, 'parseWorkflowOutput: 检测到错误行');
// 空输入
const emptyResult = parseWorkflowOutput('');
assert(emptyResult.tests.total === 0, 'parseWorkflowOutput: 空输入 → 0 total');
const nullResult = parseWorkflowOutput(null);
assert(nullResult.tests.total === 0, 'parseWorkflowOutput: null 输入 → 0 total');
// parsePRMerge
const prResult = parsePRMerge({ title: 'Fix bug', number: 42, author: 'test', files: ['a.js', 'b.js'] });
assert(prResult.filesChanged === 2, 'parsePRMerge: 2 files changed');
assert(prResult.summary.includes('#42'), 'parsePRMerge: 包含 PR 编号');
const nullPr = parsePRMerge(null);
assert(nullPr.filesChanged === 0, 'parsePRMerge: null → 0 files');
// parseTestReport
const reportResult = parseTestReport('✅ a\n✅ b\n❌ c');
assert(reportResult.passed === 2, 'parseTestReport: 2 passed');
assert(reportResult.failed === 1, 'parseTestReport: 1 failed');
assert(reportResult.passRate > 0.6 && reportResult.passRate < 0.7, 'parseTestReport: passRate ~0.6667');
// ══════════════════════════════════════════════════════════════════════════
// 测试 7: TianYen Scheduler
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 7: TianYen Scheduler ──');
const { evaluateSchedule, setFrequency, FREQUENCY_LEVELS } = require('../../tianyen/scheduler');
// 无指标 → default
const defEval = evaluateSchedule('AG-TEST', null);
assert(defEval.level === 'default', '无指标 → default 频率');
// 高偏差 → max
const maxEval = evaluateSchedule('AG-TEST', { drift: 0.2 });
assert(maxEval.level === 'max', '高偏差 (0.2) → max 频率');
// 中偏差 → high
const highEval = evaluateSchedule('AG-TEST', { drift: 0.08 });
assert(highEval.level === 'high', '中偏差 (0.08) → high 频率');
// 完美平衡 → low
const lowEval = evaluateSchedule('AG-TEST', { balance: 1.0, drift: 0 });
assert(lowEval.level === 'low', '完美平衡 → low 频率');
// 活跃变更 → high/medium
const activeEval = evaluateSchedule('AG-TEST', { recentChanges: 15 });
assert(activeEval.level === 'high', '大量变更 → high 频率');
const medEval = evaluateSchedule('AG-TEST', { recentChanges: 5 });
assert(medEval.level === 'medium', '中等变更 → medium 频率');
// FREQUENCY_LEVELS 完整性
assert(Object.keys(FREQUENCY_LEVELS).length === 5, '5 个频率级别');
assert(FREQUENCY_LEVELS.max.cron === '*/30 * * * *', 'max cron 正确');
// ══════════════════════════════════════════════════════════════════════════
// 测试 8: Agent Checkin Module
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 8: Agent Checkin Module ──');
const { checkin, checkout, getLastCheckin, detectTimeout, loadCheckinLog, saveCheckinLog } = require('../../tianyen/agent-checkin-module');
// 备份
const checkinLogPath = path.join(ROOT, '.github/tianyen/checkin-log.json');
const checkinBackup = fs.existsSync(checkinLogPath) ? fs.readFileSync(checkinLogPath, 'utf8') : null;
// 重置
saveCheckinLog({ version: '1.0.0', checkins: {} });
// checkin
const ci = checkin('AG-TEST-001');
assert(ci.agentId === 'AG-TEST-001', 'checkin 返回正确 agentId');
assert(ci.status === 'running', 'checkin status = running');
assert(typeof ci.timestamp === 'string', 'checkin 返回 timestamp');
// getLastCheckin
const last = getLastCheckin('AG-TEST-001');
assert(last !== null, 'getLastCheckin 返回记录');
assert(last.status === 'running', 'getLastCheckin status = running');
// detectTimeout — 刚签到,不应超时
const timeoutCheck = detectTimeout('AG-TEST-001', 600000);
assert(timeoutCheck.timedOut === false, '刚签到 → 未超时');
// detectTimeout — 用极小 maxDuration 强制超时
const shortTimeout = detectTimeout('AG-TEST-001', 0);
assert(shortTimeout.timedOut === true, '0ms maxDuration → 超时');
// checkout
const co = checkout('AG-TEST-001', 'success', { testsRun: 10 });
assert(co.status === 'success', 'checkout status = success');
assert(typeof co.duration === 'number', 'checkout 返回 duration');
// checkout 后不再检测为运行中
const afterCo = detectTimeout('AG-TEST-001', 0);
assert(afterCo.timedOut === false, 'checkout 后不超时');
// 不存在的 Agent
const noAgent = getLastCheckin('AG-NONEXISTENT');
assert(noAgent === null, '不存在的 Agent → null');
const noTimeout = detectTimeout('AG-NONEXISTENT');
assert(noTimeout.timedOut === false, '不存在的 Agent → 不超时');
// 恢复
if (checkinBackup) {
fs.writeFileSync(checkinLogPath, checkinBackup, 'utf8');
} else {
saveCheckinLog({ version: '1.0.0', checkins: {} });
}
// ══════════════════════════════════════════════════════════════════════════
// 测试 9: Twin Collector (不执行实际测试,只验证模块导出)
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 9: Twin Collector 模块导出 ──');
const collector = require('../twin-collector');
assert(typeof collector.collectMetrics === 'function', 'collectMetrics 是函数');
assert(typeof collector.collectLeft === 'function', 'collectLeft 是函数');
assert(typeof collector.collectRight === 'function', 'collectRight 是函数');
assert(typeof collector.collectBoth === 'function', 'collectBoth 是函数');
assert(typeof collector.WEIGHTS === 'object', 'WEIGHTS 已导出');
assert(collector.WEIGHTS.taskCompletion === 0.4, 'taskCompletion 权重 = 0.4');
assert(collector.WEIGHTS.testPassRate === 0.25, 'testPassRate 权重 = 0.25');
// ══════════════════════════════════════════════════════════════════════════
// 测试 10: README Generator (SKINS 和布局)
// ══════════════════════════════════════════════════════════════════════════
console.log('\n── 测试 10: README Generator ──');
const {
SKINS, selectSkin, generateLayout, renderDashboard,
renderTwinBalance, renderBulletin, generateReadme
} = require('../readme-generator');
assert(Object.keys(SKINS).length === 5, '5 种皮肤');
assert(SKINS.CALM.badgeColor === 'blue', 'CALM badgeColor = blue');
assert(SKINS.EMERGENCY.badgeColor === 'red', 'EMERGENCY badgeColor = red');
// selectSkin
assert(selectSkin(null).name === SKINS.CALM.name, 'null state → CALM');
assert(selectSkin({ alert: { level: 'high' } }).name === SKINS.EMERGENCY.name, 'high alert → EMERGENCY');
assert(selectSkin({ alert: { level: 'medium' } }).name === SKINS.ATTENTION.name, 'medium alert → ATTENTION');
assert(selectSkin({ building: true }).name === SKINS.BUILDING.name, 'building → BUILDING');
// generateLayout
const emergencyLayout = generateLayout({ alert: { level: 'high' }, balanced: false });
assert(emergencyLayout[0] === 'critical_alert', '紧急布局首项 = critical_alert');
const calmLayout = generateLayout({});
assert(calmLayout.includes('dashboard'), '常规布局包含 dashboard');
// renderDashboard
const dashMd = renderDashboard({});
assert(dashMd.includes('系统状态'), 'dashboard 包含系统状态');
// renderTwinBalance
const twinMd = renderTwinBalance({ metrics: { left: { composite: 0.9, dimensions: { taskCompletion: 0.9, testPassRate: 0.8, codeCoverage: 1.0, securityScore: 1.0 } }, right: { composite: 0.95, dimensions: { taskCompletion: 0.95, testPassRate: 0.9, codeCoverage: 1.0, securityScore: 1.0 } } }, balance: 0.95 });
assert(twinMd.includes('双子天平'), 'twin balance 包含标题');
assert(twinMd.includes('EXE-Engine'), 'twin balance 包含 EXE-Engine');
// renderBulletin
const bulletinMd = renderBulletin([]);
assert(bulletinMd.includes('暂无公告'), '空公告 → 暂无公告');
const bulletinMd2 = renderBulletin([{ timestamp: '2026-01-01T00:00:00Z', event: '测试', status: 'active', handler: 'auto' }]);
assert(bulletinMd2.includes('测试'), '公告包含事件内容');
// generateReadme
const readme = generateReadme({});
assert(readme.includes('光湖'), 'README 包含光湖');
assert(readme.includes('版权'), 'README 包含版权');
// ══════════════════════════════════════════════════════════════════════════
// 结果汇总
// ══════════════════════════════════════════════════════════════════════════
console.log(`\n${'═'.repeat(60)}`);
console.log(`🏛️ 治理系统测试结果: ${passed} passed, ${failed} failed`);
if (failed > 0) {
console.log(`\n${failed} 个测试失败`);
process.exit(1);
} else {
console.log(`\n✅ 全部 ${passed} 个测试通过`);
}