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

427 lines
22 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.

// 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} 个测试通过`);
}