Files
dsh_shenxian/test/reachability.test.mjs
T
admin 640813e84e 覆盖网络线 S0+S1 落地:可达性单一入口 + 会合地址出 env
S0(本机):新增 src/net/reachability.ts(Reachability 描述 + agentBaseUrlOf 唯一取值入口)、src/net/rendezvous.ts、docs/architecture.md(四层划分 + 依赖方向 + R1-R4 判据)、scripts/check-layering.mjs + layering-baseline.json、test/reachability.test.mjs;src/supervisor/remote-spawner.ts 与 src/web/server.ts 改为统一走 agentBaseUrlOf(),agentUrl 降级为可选旧字段(向后兼容)。

S1(本机 + 106):新增 DSHS_RENDEZVOUS_URL 出 env(src/config.ts 解析 clusterRendezvousUrl,优先级 overrides > 新变量 > DSHS_TUNNEL_TARGET 兜底 > 空),src/worker/tunnel.ts 新增 normalizeTunnelTarget()、src/worker/agent.ts 改读新配置 ⇒ 双路径并存、可零代码回滚(删掉新 env 即走旧路径)。106 已上线,健康检查 tunnel.ready=true。

package.json 增加 check:layering 脚本并接入 verify;README 登记分层文档。

验收:npm run build 通过;npm test 44/44;check:layering 无新增违规(基线 5 条)。
2026-09-16 15:02:52 +08:00

127 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.
/**
* 覆盖网络 S0 · 可达性 / 会合接口单测(纯函数,不连网、不起进程)。
* 运行:node --test test/reachability.test.mjs(已含在 npm test / npm verify 中)
*
* ## 这个测试的职责
* S0 的唯一验收标准是「**行为零变化**」。所以本文件的核心断言是
* **「可达性解析前后的 agent 基址逐字相等」** —— 把现网 `dsh_hosts` 的真实两行
* 原样写进判据里(**不靠肉眼、不靠人工比对**):
*
* | hostId | dsh_hosts.endpoint(2026-09-16 实测) | 真实语义 |
* |---|---|---|
* | `w-106` | `http://127.0.0.1:19000` | 经 47 上 sshd 的反向隧道落点 |
* | `w-47` | `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-106', endpoint: 'http://127.0.0.1:19000', via: VIA_MANAGER_SSH },
{ hostId: 'w-47', 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, via: reach.via, address: reach.address },
{ hostId: h.hostId, 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',
via: 'x',
address: 'a.example:8443',
scheme: 'https',
})
// 没写 scheme ⇒ 按 http(与 fetch 的补全行为一致)
assert.deepEqual(parseReachability('h', '10.0.0.5:19000', 'x'), {
hostId: 'h',
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-106',
agentUrl: 'http://stale.example:1',
reachability: { hostId: 'w-106', 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-47', '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-47'), {
hostId: 'w-47',
via: VIA_LOCAL,
address: '127.0.0.1:19100',
scheme: 'http',
})
assert.equal(await rv.resolve('w-106'), undefined)
})
test('ManagerSshRendezvous:解析出的基址必须等于现网 endpoint(S2 迁移判据)', async () => {
const table = new Map([
['w-106', '127.0.0.1:19000'],
['w-47', '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())
})