Files
dsh_shenxian/test/reachability.test.mjs
T

176 lines
8.0 KiB
JavaScript
Raw Normal View History

/**
* 覆盖网络 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')
})