176 lines
8.0 KiB
JavaScript
176 lines
8.0 KiB
JavaScript
/**
|
||||
|
|
* 覆盖网络 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')
|
|||
|
|
})
|