Files
dsh_shenxian/test/reachability.test.mjs
admin 452924d89c feat(config): 涉密内容外置到配置目录(档案 140)
把散落在代码里的真实部署值统一收进 config/,代码改为引用配置,
使仓库副本/开源导出不再带出生产域名、IP、内网路径与凭据。

新增 config/:platform.env.example(模板)· load.sh(shell 加载器)·
index.cjs(node 加载器)· README.md(键一览与优先级)。
真实值放 config/platform.env —— 已 .gitignore 排除,不入库、不进导出。

TS 侧新增 src/platform-paths.ts 作部署路径的唯一解析处(零副作用):
platformDir/stateDir/backupDir/artifactDir/installDir/scriptPath。
config.ts 接入这些字段;内置中继种子由生产 URL 改为空(改由
DSHS_OVERLAY_BOOTSTRAP_SEEDS 提供)。修掉 5 处硬编码绝对路径,
src/** 注释中性化 116 行/53 文件。

scripts/** 36 个内部运维脚本:真令牌/PG 口令/隧道目标/主机号/路径
一律改从配置取;web/wake.html 的注册域白名单改为运行时从
location.hostname 推导;test/** 夹具 119 行/13 文件改 RFC 2606/5737
保留值,并把「内置种子必须为空」固化为回归断言。

取证:tsc 0 错;npm test 373/375(唯一失败 lease 属既有);
全仓扫描(大小写不敏感)代码面涉密标识 = 0;已部署 47 并零回归
(/opt/dsh/* 未搬家,/var/lib/dshs/platform 未被误建)。
2026-09-19 15:12:19 +08:00

177 lines
8.0 KiB
JavaScript
Raw Permalink 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.
/**
* 覆盖网络 S0 · 可达性 / 会合接口单测(纯函数,不连网、不起进程)。
* 运行:node --test test/reachability.test.mjs(已含在 npm test / npm verify 中)
*
* ## 这个测试的职责
* S0 的唯一验收标准是「**行为零变化**」。所以本文件的核心断言是
* **「可达性解析前后的 agent 基址逐字相等」** —— 把现网 `dsh_hosts` 的真实两行
* 原样写进判据里(**不靠肉眼、不靠人工比对**):
*
* | hostId | dsh_hosts.endpoint(2026-09-16 实测) | 真实语义 |
* |---|---|---|
* | `w-2` | `http://127.0.0.1:19000` | 经 47 上 sshd 的反向隧道落点 |
* | `w-1` | `http://127.0.0.1:19100` | 同机直连(node 自己监听) |
*/
import { test } from 'node:test'
import assert from 'node:assert/strict'
import {
agentBaseUrl,
agentBaseUrlOf,
parseReachability,
toEndpoint,
VIA_LOCAL,
VIA_MANAGER_SSH,
} from '../lib/net/reachability.js'
import { LocalRendezvous, ManagerSshRendezvous, RendezvousRegistry } from '../lib/net/rendezvous.js'
/** 现网真实两条(2026-09-16 在 47 上 `SELECT id, endpoint FROM dsh_hosts` 实测)。 */
const LIVE_HOSTS = [
{ hostId: 'w-2', endpoint: 'http://127.0.0.1:19000', via: VIA_MANAGER_SSH },
{ hostId: 'w-1', endpoint: 'http://127.0.0.1:19100', via: VIA_LOCAL },
]
test('S0 等价性:旧 agentUrl 取址与可达性取址逐字相等', () => {
for (const h of LIVE_HOSTS) {
// 旧路径(今天生产用的):直接把 endpoint 当 agent 基址
const legacy = h.endpoint.replace(/\/$/, '')
// 新路径(S0 起的唯一入口):没给 reachability ⇒ 必须回退到同一个值
assert.equal(agentBaseUrlOf({ hostId: h.hostId, agentUrl: h.endpoint }), legacy, h.hostId)
}
})
test('parseReachability → agentBaseUrl 对现网两行是往返恒等的', () => {
for (const h of LIVE_HOSTS) {
const reach = parseReachability(h.hostId, h.endpoint, h.via)
assert.equal(agentBaseUrl(reach), h.endpoint, `${h.hostId} 往返不一致`)
assert.equal(toEndpoint(reach), h.endpoint, `${h.hostId} toEndpoint 不一致`)
assert.equal(reach.scheme, 'http')
assert.deepEqual(
{ hostId: reach.hostId, networkId: reach.networkId, via: reach.via, address: reach.address },
// 裸 hostId ⇒ 落运维网(P0-3 的过渡期兼容:旧调用方一个字都不用改)
{ hostId: h.hostId, networkId: 'ops', via: h.via, address: h.endpoint.slice('http://'.length) },
)
}
})
test('parseReachability:https / 裸 host:port / 尾斜杠 三种兼容面', () => {
assert.deepEqual(parseReachability('h', 'https://a.example:8443', 'x'), {
hostId: 'h',
networkId: 'ops',
via: 'x',
address: 'a.example:8443',
scheme: 'https',
})
// 没写 scheme ⇒ 按 http(与 fetch 的补全行为一致)
assert.deepEqual(parseReachability('h', '10.0.0.5:19000', 'x'), {
hostId: 'h',
networkId: 'ops',
via: 'x',
address: '10.0.0.5:19000',
scheme: 'http',
})
assert.equal(agentBaseUrl(parseReachability('h', 'http://127.0.0.1:19000/', 'x')), 'http://127.0.0.1:19000')
})
test('可达性优先于旧 agentUrl', () => {
const host = {
hostId: 'w-2',
agentUrl: 'http://stale.example:1',
reachability: { hostId: 'w-2', via: VIA_MANAGER_SSH, address: '127.0.0.1:19000', scheme: 'http' },
}
assert.equal(agentBaseUrlOf(host), 'http://127.0.0.1:19000')
})
test('两者皆缺 ⇒ 抛错(禁止静默打到空地址)', () => {
assert.throws(() => agentBaseUrlOf({ hostId: 'w-x' }), /既无 reachability 也无 agentUrl/)
assert.throws(() => agentBaseUrlOf({ hostId: 'w-x', agentUrl: '' }), /既无 reachability 也无 agentUrl/)
})
test('LocalRendezvous:命中给 local,未命中回 undefined 不抛', async () => {
const table = new Map([['w-1', '127.0.0.1:19100']])
const rv = new LocalRendezvous((id) => table.get(id))
assert.equal(rv.id, VIA_LOCAL)
assert.equal(rv.dialTarget(), '(direct)')
assert.deepEqual(await rv.resolve('w-1'), {
hostId: 'w-1',
networkId: 'ops',
via: VIA_LOCAL,
address: '127.0.0.1:19100',
scheme: 'http',
})
assert.equal(await rv.resolve('w-2'), undefined)
})
test('ManagerSshRendezvous:解析出的基址必须等于现网 endpoint(S2 迁移判据)', async () => {
const table = new Map([
['w-2', '127.0.0.1:19000'],
['w-1', '127.0.0.1:19100'],
])
const rv = new ManagerSshRendezvous({
target: '[email protected]:32022',
addressOf: (id) => table.get(id),
})
assert.equal(rv.id, VIA_MANAGER_SSH)
assert.equal(rv.dialTarget(), '[email protected]:32022')
for (const h of LIVE_HOSTS) {
const resolved = await rv.resolve(h.hostId)
assert.equal(agentBaseUrl(resolved), h.endpoint, `${h.hostId} 迁移后基址变了`)
}
})
test('RendezvousRegistry:按 via 取实现', () => {
const local = new LocalRendezvous(() => undefined)
const ssh = new ManagerSshRendezvous({ target: 't', addressOf: () => undefined })
const reg = new RendezvousRegistry([local, ssh])
assert.equal(reg.get(VIA_LOCAL), local)
assert.equal(reg.get(VIA_MANAGER_SSH), ssh)
assert.equal(reg.get('relay:backbone-1'), undefined)
assert.deepEqual(reg.ids().sort(), [VIA_LOCAL, VIA_MANAGER_SSH].sort())
})
// ── S2 验收判据 ────────────────────────────────────────────────────────────
// 「迁移前 `agentUrl`」必须 **逐条逐字等于** 「迁移后 `resolve()` 的结果」。
// 这里把 `hostsProvider` 的真实接线原样复刻一遍(先读 via → 选实现 → 解析 → 拼基址),
// 两条数据用现网实测值写死 ⇒ **不靠肉眼、不靠人工比对**。
test('S2:via → Rendezvous → Reachability 后取址与旧 agentUrl 逐条相等', async () => {
// 现网 `dsh_hosts` 真实两行(2026-09-16 实测;`via` = 回填后的目标值)
const rows = LIVE_HOSTS.map((h) => ({ id: h.hostId, endpoint: h.endpoint, via: h.via }))
// P0-3:控制面的键 = **逻辑名**(现网全在 `ops` ⇒ 与裸 id 等价)
const nameOf = (h) => `ops/${h.id}`
const hostAddresses = new Map()
const rendezvous = new RendezvousRegistry([
new LocalRendezvous((id) => hostAddresses.get(id)),
new ManagerSshRendezvous({ target: 'ssh://[email protected]:32022', addressOf: (id) => hostAddresses.get(id) }),
])
// hostsProvider 第一遍:同步地址表
for (const row of rows) {
hostAddresses.set(nameOf(row), parseReachability(nameOf(row), row.endpoint, row.via).address)
}
for (const row of rows) {
const impl = rendezvous.get(row.via) ?? rendezvous.get(VIA_MANAGER_SSH)
assert.notEqual(impl, undefined, `${row.id} 的 via=${row.via} 必须能选到实现`)
const reach = await impl.resolve(nameOf(row))
assert.notEqual(reach, undefined, `${row.id} 必须能解析出可达性`)
assert.equal(reach.via, row.via, `${row.id} 解析后 via 变了`)
assert.equal(reach.hostId, row.id, `${row.id} 解析出的 hostId 必须是**裸** id`)
assert.equal(reach.networkId, 'ops', `${row.id} 解析出的网络段必须是 ops`)
// 判据:新路径拼出来的基址 == 旧路径直接用的 endpoint
assert.equal(agentBaseUrlOf({ hostId: row.id, reachability: reach }), row.endpoint, `${row.id} 取址变了`)
assert.equal(agentBaseUrlOf({ hostId: row.id, agentUrl: row.endpoint }), row.endpoint, `${row.id} 旧路径变了`)
}
})
test('S2:via 认不出来 ⇒ 回退 manager-ssh,不抛(过渡期要能跑)', async () => {
const hostAddresses = new Map([['w-x', '10.0.0.9:19000']])
const rendezvous = new RendezvousRegistry([
new LocalRendezvous((id) => hostAddresses.get(id)),
new ManagerSshRendezvous({ target: '', addressOf: (id) => hostAddresses.get(id) }),
])
const impl = rendezvous.get('relay:not-deployed-yet') ?? rendezvous.get(VIA_MANAGER_SSH)
const reach = await impl.resolve('w-x')
assert.equal(reach.via, VIA_MANAGER_SSH)
assert.equal(agentBaseUrlOf({ hostId: 'w-x', reachability: reach }), 'http://10.0.0.9:19000')
})