Files
nodeagent/tests/unit/net-scripts.test.mjs
T
jkooe b9236dfaf1 chore(privacy): 公开仓库隐私脱敏 —— 清除可定位到个人与设备的字面量
按 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。
2026-10-13 22:35:40 +08:00

102 lines
5.0 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 {
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 快照不应写静态地址');
});