Files
dsh_shenxian/test/overlay-device-endpoint.test.mjs
T
admin 5ec99ce227
build / build-and-scan (push) Canceled after 0s
chore: 文档库全量重新入库(2026-10-20)
2026-10-20 15:05:55 +08:00

285 lines
13 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.
/**
* 手机接入线 · **设备级取址**单测(S2)—— 纯函数,不连网、不起进程。
* 运行:`node --test test/overlay-device-endpoint.test.mjs`(已含进 `npm test` / `npm verify`)。
*
* ## 这个测试的职责
* S2 的验收标准有两条,都必须**机器可判**:
* 1. **解析口径与改造前逐条等价** —— `indexRelayStatus` 的容忍面(丢非法行、丢 `localPort<=0`、
* `network` 缺失按 `ops`)此前活在 `web/server.ts` 的内联循环里,抽出来后不得有语义漂移;
* 2. 🔴 **失败必须关闭** —— 查不到 / 离线 / 多端口,**⛔ 不许**返回任何"看起来像地址"的东西
* (回落 = 把请求打到控制面本机同号端口上,与 `reachability.ts` P0-3 同族的静默写坏)。
*/
import { test } from 'node:test'
import assert from 'node:assert/strict'
import {
deviceWebDecision,
indexRelayStatus,
listDeviceEndpoints,
relayEndpointKey,
resolveDeviceReachability,
deviceWebSwitchOn,
} from '../lib/net/relay-endpoints.js'
import { parseDevicePath } from '../lib/web/routes/device-web.js'
import { VIA_RELAY } from '../lib/net/reachability.js'
/** 造一份 relay `/status` 响应体(只带本测试用得到的字段)。 */
function status(endpoints) {
return { endpoints }
}
test('indexRelayStatus:丢非法行、丢 localPort<=0、network 缺失按 ops', () => {
const index = indexRelayStatus(
status([
{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: true },
{ hostId: 'd-b', network: 'u:5', port: 20101, localPort: 0, online: true }, // 纯流转发 / 未绑定
{ hostId: 'd-c', port: 20102, localPort: 42002, online: true }, // 无 network ⇒ ops
{ hostId: 42, network: 'u:5', port: 20103, localPort: 42003, online: true }, // hostId 非法
{ hostId: 'd-d', network: 'u:5', port: '20104', localPort: 42004, online: true }, // port 非法
]),
)
assert.deepEqual([...index.keys()].sort(), [relayEndpointKey('ops', 'd-c', 20102), relayEndpointKey('u:5', 'd-a', 20100)])
assert.deepEqual(index.get(relayEndpointKey('u:5', 'd-a', 20100)), { localPort: 42000, online: true })
})
test('indexRelayStatus:body 形状异常 ⇒ 空索引(不抛)', () => {
assert.equal(indexRelayStatus(undefined).size, 0)
assert.equal(indexRelayStatus({}).size, 0)
assert.equal(indexRelayStatus({ endpoints: 'nope' }).size, 0)
})
test('listDeviceEndpoints:只列本设备、按端口升序、不误命中前缀更长的 hostId', () => {
const index = indexRelayStatus(
status([
{ hostId: 'd-1', network: 'u:5', port: 20101, localPort: 42001, online: true },
{ hostId: 'd-1', network: 'u:5', port: 20100, localPort: 42000, online: true },
{ hostId: 'd-10', network: 'u:5', port: 20100, localPort: 43000, online: true }, // ⛔ 不是 d-1 的
{ hostId: 'd-1', network: 'u:9', port: 20100, localPort: 44000, online: true }, // ⛔ 别的租户网
]),
)
const got = listDeviceEndpoints(index, 'u:5', 'd-1')
assert.deepEqual(got.map((e) => [e.port, e.localPort]), [
[20100, 42000],
[20101, 42001],
])
})
test('resolveDeviceReachability:单端口在线 ⇒ via=relay 的落点可达性', () => {
const index = indexRelayStatus(status([{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: true }]))
const res = resolveDeviceReachability(index, { network: 'u:5', hostId: 'd-a' })
assert.equal(res.ok, true)
assert.equal(res.port, 20100)
assert.equal(res.localPort, 42000)
assert.deepEqual(res.reachability, {
hostId: 'd-a',
networkId: 'u:5',
via: VIA_RELAY,
address: '127.0.0.1:42000',
scheme: 'http',
})
})
test('跨租户结构上不可达:查 u:9 命中不到 u:5 的设备', () => {
const index = indexRelayStatus(status([{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: true }]))
const res = resolveDeviceReachability(index, { network: 'u:9', hostId: 'd-a' })
assert.equal(res.ok, false)
assert.equal(res.reason, 'device-no-endpoints')
assert.equal('reachability' in res, false) // 🔴 失败关闭:不许带地址出门
})
test('设备一个端口都没声明 ⇒ device-no-endpoints(且不带地址)', () => {
const res = resolveDeviceReachability(indexRelayStatus(status([])), { network: 'u:5', hostId: 'd-a' })
assert.equal(res.ok, false)
assert.equal(res.reason, 'device-no-endpoints')
assert.equal('reachability' in res, false)
})
test('声明的端口全部离线 ⇒ device-offline', () => {
const index = indexRelayStatus(
status([
{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: false },
{ hostId: 'd-a', network: 'u:5', port: 20101, localPort: 42001, online: false },
]),
)
const res = resolveDeviceReachability(index, { network: 'u:5', hostId: 'd-a' })
assert.equal(res.ok, false)
assert.equal(res.reason, 'device-offline')
assert.match(res.detail, /20100,20101/u)
})
test('多个在线端口且未指定 ⇒ ambiguous-device-ports(⛔ 不猜)', () => {
const index = indexRelayStatus(
status([
{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: true },
{ hostId: 'd-a', network: 'u:5', port: 20101, localPort: 42001, online: true },
]),
)
const res = resolveDeviceReachability(index, { network: 'u:5', hostId: 'd-a' })
assert.equal(res.ok, false)
assert.equal(res.reason, 'ambiguous-device-ports')
})
test('指定端口:存在且在线的那个能被选中(多端口下唯一可用形态)', () => {
const index = indexRelayStatus(
status([
{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: false },
{ hostId: 'd-a', network: 'u:5', port: 20101, localPort: 42001, online: true },
]),
)
const res = resolveDeviceReachability(index, { network: 'u:5', hostId: 'd-a', preferredPort: 20101 })
assert.equal(res.ok, true)
assert.equal(res.reachability.address, '127.0.0.1:42001')
})
test('指定端口:设备没声明过 ⇒ port-not-declared(列出已声明端口)', () => {
const index = indexRelayStatus(status([{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: true }]))
const res = resolveDeviceReachability(index, { network: 'u:5', hostId: 'd-a', preferredPort: 20999 })
assert.equal(res.ok, false)
assert.equal(res.reason, 'port-not-declared')
assert.match(res.detail, /20100/u)
})
test('指定端口:存在但离线 ⇒ device-offline(⛔ 不因为"端口在"就放行)', () => {
const index = indexRelayStatus(status([{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: false }]))
const res = resolveDeviceReachability(index, { network: 'u:5', hostId: 'd-a', preferredPort: 20100 })
assert.equal(res.ok, false)
assert.equal(res.reason, 'device-offline')
})
// ── S3:设备 Web 入口的准入判据(纯函数)──────────────────────────────────────
/**
* ⚠️ **2026-09-27(S5)对本节用例的改动**:S5 在平台闸之后加了**用户自己的开关**那一道
* (`userEnabled`,缺省 = 关)。下面 ②~⑤ 与"全过"这几条测的是**台账 / 取址**那几道闸,
* 因此必须**显式**把用户开关写成 `true` —— 否则它们会在第一道闸就返回 `user-switch-off`
* (这正是"漏传不许默认放行"的失败方向,⛔ 不是 bug)。
* 🔴 用户开关自己的判据 ⇒ `test/device-web-switch.test.mjs`(含先红后绿夹具 + 独立进程急停腿)。
*/
/** 造一个"设备在线且已声明端口"的取址结果。 */
function reachableDevice() {
const index = indexRelayStatus(status([{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: true }]))
return resolveDeviceReachability(index, { network: 'u:5', hostId: 'd-a' })
}
test('deviceWebDecision:① 开关未开 ⇒ switch-off(默认关闭的第一道闸,且不带地址)', () => {
const d = deviceWebDecision({
enabled: false,
ledger: { status: 'active', kind: 'desktop' },
hostId: 'd-a',
resolved: reachableDevice(),
})
assert.equal(d.ok, false)
assert.equal(d.reason, 'switch-off')
assert.equal('address' in d, false)
})
test('deviceWebDecision:② 台账查无此设备 ⇒ device-unknown', () => {
const d = deviceWebDecision({ enabled: true,
userEnabled: true, ledger: undefined, hostId: 'd-a', resolved: reachableDevice() })
assert.equal(d.ok, false)
assert.equal(d.reason, 'device-unknown')
})
test('deviceWebDecision:③ 停发(status=revoked)⇒ device-revoked,⛔ 与"到不了"分开', () => {
const d = deviceWebDecision({
enabled: true,
userEnabled: true,
ledger: { status: 'revoked', kind: 'desktop' },
hostId: 'd-a',
resolved: reachableDevice(),
})
assert.equal(d.ok, false)
assert.equal(d.reason, 'device-revoked')
})
test('deviceWebDecision:④ kind=instance ⇒ device-not-desktop(平台代签的实例条目不走这条路)', () => {
const d = deviceWebDecision({
enabled: true,
userEnabled: true,
ledger: { status: 'active', kind: 'instance' },
hostId: 'd-a',
resolved: reachableDevice(),
})
assert.equal(d.ok, false)
assert.equal(d.reason, 'device-not-desktop')
})
test('deviceWebDecision:⑤ 台账通过但取址失败 ⇒ device-unreachable(带具名 detail,⛔ 不带地址)', () => {
const offline = resolveDeviceReachability(
indexRelayStatus(status([{ hostId: 'd-a', network: 'u:5', port: 20100, localPort: 42000, online: false }])),
{ network: 'u:5', hostId: 'd-a' },
)
const d = deviceWebDecision({
enabled: true,
userEnabled: true,
ledger: { status: 'active', kind: 'desktop' },
hostId: 'd-a',
resolved: offline,
})
assert.equal(d.ok, false)
assert.equal(d.reason, 'device-unreachable')
assert.match(d.detail, /离线/u)
assert.equal('address' in d, false)
})
test('deviceWebDecision:四闸全过 ⇒ ok,地址 = 解析出的落点', () => {
const d = deviceWebDecision({
enabled: true,
userEnabled: true,
ledger: { status: 'active', kind: 'desktop' },
hostId: 'd-a',
resolved: reachableDevice(),
})
assert.equal(d.ok, true)
assert.equal(d.address, '127.0.0.1:42000')
assert.equal(d.port, 42000) // 落点口(连的就是它)
assert.equal(d.declaredPort, 20100) // 设备声明的口(只作诊断)
})
// ── S3:开关读法 与 路径解析(纯函数)────────────────────────────────────────
test('deviceWebSwitchOn:语义已改为「平台没强制关」(S5)—— 只有字面量 0 才是关', () => {
// 🔴 2026-09-27 改口径:原语义「只有 1 才算开,其余当关」已作废。
// 现在 = 运维急停读法:显式 0 ⇒ false(强制关所有人);**其余一切**(含未设)⇒ true
// (平台不拦,交给用户自己的开关 —— 那一道的判据在 device-web-switch.test.mjs)。
assert.equal(deviceWebSwitchOn({ DSHS_DEVICE_WEB: '0' }), false)
assert.equal(deviceWebSwitchOn({ DSHS_DEVICE_WEB: ' 0 ' }), false)
for (const v of ['1', 'true', 'yes', 'on', '', undefined]) {
assert.equal(deviceWebSwitchOn({ DSHS_DEVICE_WEB: v }), true, String(v))
}
assert.equal(deviceWebSwitchOn({}), true)
})
test('deviceWebDecision:ok 形态带 host(= relay 回环主机名)与 port,供代理腿直接用', () => {
const d = deviceWebDecision({
enabled: true,
userEnabled: true,
ledger: { status: 'active', kind: 'desktop' },
hostId: 'd-a',
resolved: reachableDevice(),
})
assert.equal(d.ok, true)
assert.equal(d.host, '127.0.0.1')
assert.equal(d.port, 42000) // 落点口(连的就是它)
assert.equal(d.declaredPort, 20100) // 设备声明的口(只作诊断)
})
test('parseDevicePath:命中 /u/<uid>/desk/<hostId>/… ⇒ 拆出 rest(含 query)与前缀', () => {
const m = parseDevicePath('/u/u1/desk/d-1-abc/assets/app.js?rev=2')
assert.deepEqual(m, {
userId: 'u1',
hostId: 'd-1-abc',
prefix: '/u/u1/desk/d-1-abc',
rest: '/assets/app.js?rev=2',
})
})
test('parseDevicePath:根路径与无子路径两种边界都要能转发(rest 回落 /)', () => {
assert.equal(parseDevicePath('/u/u1/desk/d-1')?.rest, '/')
assert.equal(parseDevicePath('/u/u1/desk/d-1/')?.rest, '/')
})
test('parseDevicePath:⛔ 非设备入口一律 undefined(这是"关着时零影响"的判据)', () => {
for (const url of ['/', '/login.html', '/u/u1/dsh/x', '/api/overlay/my-devices', '/u/u1/desk/', '/u//desk/d-1']) {
assert.equal(parseDevicePath(url), undefined, url)
}
})