Files
nodeagent/tests/unit/monitor.test.mjs
T
jkooe e23788d717 refactor(versioning): 统一版本号 —— semver 为唯一版本号,开发批次代号并入其归属版本
## 问题(皇上指出)
项目里两套编号并行,无法判断哪个是"当前版本":
- **发布版本**(semver):v1.1.0 ~ v1.6.0、v2.0.0
- **开发批次代号**:v1 ~ v26(README 路线表 / 代码注释 / 文档)
时间线证据:v1.1.0 首发(09-29)时批次已推进到 v12.4 —— **二者从来不是一对一**(26 批次只发过 10 版)。

## 规则(新增 docs/VERSIONING.md §0 版本号唯一性规范)
- **唯一版本号 = semver `vX.Y.Z`**,两个来源必须永远一致:git tag 与 package.json(构建期注入 agent)
- **批次代号不是版本号**,不再单独出现;需要区分同版内多批改动时用**功能名**
- 附「批次 → 归属发布版本」映射表(按 tag 时间线与功能内容推定,并注明以 CHANGELOG 为准)

## 统一动作
1. **批次 v23 ~ v26 → v2.0.0**(22 文件 / 53 处)—— 它们确实属于 v2.0.0
2. **臣的批次号 v1.7 / v1.8 → v2.0.0**(27 文件)—— 未单独发版
3. **简写补全三段式**:v1.5 → v1.5.0、v1.6 → v1.6.0
4. **README 路线表**:`**v1**` → `**批次 1**`;表头加「批次 ≠ 版本号」说明并链到 §0;
   补齐批次 16~29 与 **v2.0.0 发布行**;删掉过时的「待办」行(提到的分支早已删除)
5. `docs/VERSIONING.md` 里**故意保留** 3 处批次号 —— 那是规范说明本身

## 保留项(真实发布版本,不动)
v1.1.0 / v1.2.1 / v1.3.0 / v1.4.0~v1.4.3 / v1.5.0 / v1.6.0 / v2.0.0(10 个 tag)

## 验证
- 版本号一致性:package.json = tag = 8 个 workspace = tauri.conf.json = **2.0.0** ✓
- v1.7/v1.8/v23~v26 全仓残留 **3 处**(均在 VERSIONING.md 规范说明中,属预期)
- 7 包构建 · 单测 273 · e2e 30 · ps1-encoding 24 全绿
2026-10-10 14:27:12 +08:00

74 lines
3.5 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.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { findCapability, validate, CapabilityNames } from '../../packages/protocol/dist/index.js';
/**
* monitor.* 的契约层测试(v2.0.0 方向二第二块)。
* 运行时行为(真采样/落盘/摘要)在本地真机探针里验证:port 探测 + metric 采样 +
* report 摘要 + stop 落盘。
*/
function check(capName, args) {
return validate(args, findCapability(capName).params_schema);
}
test('monitor 五项能力均已登记且为低危只读/本地', () => {
for (const n of [CapabilityNames.MonitorStart, CapabilityNames.MonitorReport, CapabilityNames.MonitorStop, CapabilityNames.MonitorList, CapabilityNames.MonitorDelete, CapabilityNames.LogQuery]) {
const cap = findCapability(n);
assert.ok(cap, `缺少 ${n}`);
assert.equal(cap.risk, 'low', `${n} 应为 low risk`);
}
});
test('monitor.start:四类 source 通过,非法 source 被拒', () => {
for (const s of ['port', 'process', 'command', 'metric']) {
assert.equal(check('monitor.start', { source: s, target: 'x:1' }).length, 0, `${s} 应通过`);
}
assert.ok(check('monitor.start', { source: 'bogus' }).length > 0, '非法 source 应拒');
assert.ok(check('monitor.start', {}).length > 0, '缺 source 应拒');
});
test('monitor.start:interval_ms 有上下限(500~600000)', () => {
assert.equal(check('monitor.start', { source: 'metric', interval_ms: 500 }).length, 0);
assert.equal(check('monitor.start', { source: 'metric', interval_ms: 600000 }).length, 0);
assert.ok(check('monitor.start', { source: 'metric', interval_ms: 10 }).length > 0, '过小应拒');
assert.ok(check('monitor.start', { source: 'metric', interval_ms: 10 ** 7 }).length > 0, '过大应拒');
});
test('monitor.report/stop/delete:id 必填', () => {
for (const n of ['monitor.report', 'monitor.stop', 'monitor.delete']) {
assert.ok(check(n, {}).length > 0, `${n} 缺 id 应拒`);
assert.equal(check(n, { id: 'm1' }).length, 0, `${n} 带 id 应通过`);
}
assert.equal(check('monitor.list', {}).length, 0, 'monitor.list 无参数');
});
test('log.query:path 必填,limit 上限 5000', () => {
assert.ok(check('log.query', {}).length > 0, '缺 path 应拒');
assert.equal(check('log.query', { path: '/var/log/x.log' }).length, 0);
assert.equal(check('log.query', { path: 'x', limit: 5000 }).length, 0);
assert.ok(check('log.query', { path: 'x', limit: 99999 }).length > 0, '超上限应拒');
assert.ok(check('log.query', { path: 'x', level: 'FATAL' }).length > 0, '非法 level 应拒');
});
test('摘要算法:port 断线次数与最长中断(与 monitor.ts 的 summarize 同步)', () => {
// 复刻 summarize 的核心逻辑,锁住"最长连续中断"的语义(最容易写错的一处)
const samples = [
{ up: true }, { up: false }, { up: false }, { up: false },
{ up: true }, { up: false }, { up: true },
];
let run = 0, worst = 0, downs = 0;
for (const s of samples) {
if (s.up === true) { run = 0; } else { run += 1; worst = Math.max(worst, run); downs += 1; }
}
assert.equal(downs, 4, 'down 计数');
assert.equal(worst, 3, '最长连续中断应为 3 个样本(中间那段)');
assert.equal(samples.filter((s) => s.up).length, 3, 'up 计数');
});
test('摘要算法:command 的成功/失败与出现过的退出码', () => {
const codes = [0, 0, 1, 0, 127];
assert.equal(codes.filter((c) => c === 0).length, 3);
assert.deepEqual([...new Set(codes)], [0, 1, 127]);
});