按 security-reports/ 的隐私审查台账逐项收口。纯脱敏与表述泛化, 不含任何业务逻辑变更;实测结论与排查步骤一字未动。 P1(可定位到具体设备/个人) - scripts/verify-b2.mjs:NA_URL 去掉硬编码缺省值(原指向一台真实机器),改为必填。 这是全仓库唯一「拿着就能连到某台真机」的常量。 - 测试账户名 pdverify → na-test(docs/VM-VERIFICATION.md 7 处、docs/console-plan.md 1 处) - 虚拟机与宿主的真实网段 → RFC 5737 文档保留段 192.0.2.x(含 CLOSE_WAIT 记录) - 跨项目名与私人称呼 → 中性表述(docs/PRD.md、docs/DEVELOPMENT.md、docs/console-plan.md 等) P2(低风险设备指纹) - 虚拟化产品及其 CLI / 访客工具名 → 中性表述(12 个文件 + 1 个工作流注释) - 测试目录 C:\nodeagent-test → C:\na-test - NAS 设备型号与系统名 → NAS 设备 / NAS 系统(docs/DEPLOY-LINUX.md、scripts/install-linux.sh、README.md) - 真机 CPU 型号、真机物理分辨率、代理软件品牌与监听端口、杀软品牌 → 泛化 - 单元测试示例段去掉真机 IP 尾八位:acl-v11 / net-scripts 的 fixture 与断言同步更新 - .gitignore 补 config.json / agent.json(含真实密钥与节点地址,绝不入库) - 顺带移除混入技术文档的「不构成投资建议」字样(与文档主题无关) 验证:pnpm test:unit 340/340 通过;独立扫描器对 235 个跟踪文件做 17 类模式 全量扫描,命中 0。
102 lines
5.0 KiB
JavaScript
102 lines
5.0 KiB
JavaScript
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import {
|
||
buildApplyCommand,
|
||
buildRollbackScript,
|
||
buildSelfCheck,
|
||
maskToPrefix,
|
||
} from '../../apps/agent/dist/capabilities/network.js';
|
||
|
||
const snap = {
|
||
alias: 'Ethernet',
|
||
dhcp: false,
|
||
ipv4: [{ ip: '192.168.1.20', prefix: 24 }],
|
||
gateway: ['192.168.1.1'],
|
||
dns: ['223.5.5.5'],
|
||
};
|
||
const args = { mode: 'static', ip: '192.168.1.21', mask: '255.255.255.0', gateway: '192.168.1.1' };
|
||
|
||
test('掩码换算:常见掩码与前缀长度互转', () => {
|
||
assert.equal(maskToPrefix('255.255.255.0'), 24);
|
||
assert.equal(maskToPrefix('255.255.0.0'), 16);
|
||
assert.equal(maskToPrefix('255.255.255.192'), 26);
|
||
assert.equal(maskToPrefix('255.255.255.255'), 32);
|
||
assert.equal(maskToPrefix('乱码'), 24, '非法输入回退 24');
|
||
});
|
||
|
||
/**
|
||
* 真机事故回归:曾经「先删旧地址 → 再加新地址」,Windows 残留的默认路由让
|
||
* New-NetIPAddress 报 already exists → 新地址没加上 → 机器掉到 APIPA 彻底失联。
|
||
* 下面几条断言就是那道防线。
|
||
*/
|
||
test('apply(static):必须先加新地址、再清旧地址(顺序不可颠倒)', () => {
|
||
const script = buildApplyCommand('static', 'Ethernet', args, snap);
|
||
const iAdd = script.indexOf('New-NetIPAddress');
|
||
const iRemove = script.indexOf('Remove-NetIPAddress');
|
||
assert.ok(iAdd > 0 && iRemove > 0, '既要加也要清');
|
||
assert.ok(iAdd < iRemove, '必须先 New-NetIPAddress 后 Remove-NetIPAddress(事故根因)');
|
||
});
|
||
|
||
test('apply(static):建路由之前必须先清掉旧默认路由(否则 already exists)', () => {
|
||
const script = buildApplyCommand('static', 'Ethernet', args, snap);
|
||
const iRmRoute = script.indexOf('Remove-NetRoute');
|
||
const iAddRoute = script.indexOf('New-NetRoute');
|
||
assert.ok(iRmRoute > 0, '必须显式清理默认路由');
|
||
assert.ok(iAddRoute > 0, '必须重建默认路由');
|
||
assert.ok(iRmRoute < iAddRoute, 'Remove-NetRoute 必须在 New-NetRoute 之前');
|
||
});
|
||
|
||
test('apply:不得使用会在 Win11 挂起的 netsh set address', () => {
|
||
const staticScript = buildApplyCommand('static', 'Ethernet', args, snap);
|
||
const dhcpScript = buildApplyCommand('dhcp', 'Ethernet', { mode: 'dhcp' }, snap);
|
||
for (const s of [staticScript, dhcpScript]) {
|
||
assert.ok(!/netsh interface ipv4 set address/.test(s), 'netsh set address 在 Win11 会挂起(真机实测)');
|
||
assert.ok(!/netsh interface ipv4 set dnsservers/.test(s), 'netsh set dnsservers 同类问题');
|
||
}
|
||
assert.match(staticScript, /Set-NetIPInterface|New-NetIPAddress/, '应使用原生 cmdlet');
|
||
});
|
||
|
||
test('apply(dhcp):切 DHCP 用原生 cmdlet', () => {
|
||
const s = buildApplyCommand('dhcp', 'Ethernet', { mode: 'dhcp' }, snap);
|
||
// ⚠️ 断言方向在 v2.0.3 **翻转过**:原来写的是 `-InterfaceAlias "Ethernet"`(双引号)。
|
||
// 双引号形态正是 W-01 的根因 —— PS 双引号串会做变量插值与 `$(...)` 展开,
|
||
// 而 `JSON.stringify` 产出的 `\"` 在 PS 里根本不是转义(PS 用反引号)。
|
||
// 现在接口名统一走 `psLit()`(单引号字面量,什么都不展开)。
|
||
// 「CI 断言会过期、而且方向可能反」—— 这条就是活的例子。
|
||
assert.match(s, /Set-NetIPInterface -InterfaceAlias 'Ethernet' -Dhcp Enabled/);
|
||
assert.match(s, /Set-DnsClientServerAddress/);
|
||
assert.ok(
|
||
!/Set-NetIPInterface[^\n]*"/.test(s),
|
||
'接口名不得再出现在双引号串里(PS 双引号会插值,`${`…`}` 形同注入入口)',
|
||
);
|
||
});
|
||
|
||
test('自检:失败时自动执行回滚脚本(自愈)', () => {
|
||
const chk = buildSelfCheck('Ethernet', '192.168.1.21', 'C:/rb.ps1');
|
||
assert.match(chk, /Get-NetIPAddress/);
|
||
assert.match(chk, /192\.168\.1\.21/, '应校验目标地址是否真的生效');
|
||
assert.match(chk, /selfcheck FAILED -> auto rollback/);
|
||
// 同上:路径改为 PS 单引号字面量(原为 `& "C:/rb.ps1"`)
|
||
assert.match(chk, /& 'C:\/rb\.ps1'/, '失败必须回滚,而不是把机器留在无地址状态');
|
||
});
|
||
|
||
test('rollback:同样先加后清 + 先清路由(否则回滚自己也会失败)', () => {
|
||
const rb = buildRollbackScript(snap, 'C:/backup.txt', 'C:/rb.log');
|
||
const iAdd = rb.indexOf('New-NetIPAddress');
|
||
const iRemove = rb.indexOf('Remove-NetIPAddress');
|
||
const iRmRoute = rb.indexOf('Remove-NetRoute');
|
||
const iAddRoute = rb.indexOf('New-NetRoute');
|
||
assert.ok(iAdd < iRemove, '回滚也必须先加后清');
|
||
assert.ok(iRmRoute < iAddRoute, '回滚也必须先清路由');
|
||
assert.match(rb, /192\.168\.1\.20/, '必须恢复快照里的原地址');
|
||
assert.match(rb, /192\.168\.1\.1/, '必须恢复原网关');
|
||
assert.match(rb, /223\.5\.5\.5/, '必须恢复原 DNS');
|
||
assert.ok(!/netsh -f/.test(rb), '不得用 netsh -f 回滚(备份编码 + set address 双重问题)');
|
||
});
|
||
|
||
test('rollback:DHCP 快照走「切回 DHCP」而非设静态', () => {
|
||
const rb = buildRollbackScript({ ...snap, dhcp: true, ipv4: [] }, 'C:/backup.txt', 'C:/rb.log');
|
||
assert.match(rb, /-Dhcp Enabled/);
|
||
assert.ok(!/New-NetIPAddress/.test(rb), 'DHCP 快照不应写静态地址');
|
||
});
|