/** * 覆盖网络线 序 ㉕ · 「逐步拉起」单测 —— **纯函数面**(零 IO、零 systemd、零生产副作用)。 * * ## 为什么只测纯函数 * 被替换掉的是「启动即 `cleanAllStaleScopes()`」。它的输入是 **OS 层既有 scope** * (`systemctl list-units` + `systemctl show -p Description`),在 Windows 开发机上 * 拿不到 ⇒ 真机部分由 §6/S6 在 106 上验收。 * 但**最容易出错、也最致命**的那一段恰好是纯的 —— **把 `Description` 解析回实例三要素**: * 解析错 ⇒ 后续替换时定位到别人的实例(跨租户最坏情形)。故这一段必须穷举式钉住。 * * 另加两条**源码级守卫**(照 序⑦ `T38` 先例):认领逻辑必须真的接在构造函数上、 * 且⛔ 不许把「认领」写成「什么都不做」(那会让档案 30 的孤儿失控)。 * * 运行:`node --test test/orchestrator-rehydrate.test.mjs`(⚠️ **刻意不进 `npm test`** —— * 本序要求零回归基线 `npm test` 176/175/0/1 **逐字不变**,加进去会改测试总数)。 * * @module test/orchestrator-rehydrate */ import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { test } from 'node:test' import { decideScopeAction, parseScopeDescription, parseScopeUnitName, } from '../lib/supervisor/orchestrator.js' /** * 夹具 = 106 上 **真实** 实例 `dsh-100002-ef8d1d12.scope` 的 `Description` 精简等价形态 * (保留全部关键 flag 与其真实相对顺序,省掉无关的 `--ro-bind-try` 白名单项)。 * ⚠️ 顺序刻意保留:`--chdir` 在 bwrap 段、`--profile/--port` 在 setpriv 之后。 */ const REAL_DESC = '/usr/bin/bwrap --ro-bind /usr /usr --tmpfs /etc ' + '--bind /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0/tmp /tmp ' + '--bind /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0 /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0 ' + '--unshare-pid ' + '--chdir /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0/ws ' + '-- setpriv --reuid 100002 --regid 100002 --clear-groups ' + '/usr/bin/dsh --profile web --host 127.0.0.1 --port 21000' const UID = 100002 const USER = '4092b965-2f68-4977-9989-68b3966f7df0' const FOLDER = `/var/lib/dsh-test/users/${USER}/ws` /* ── R1–R5:scope 名解析(⛔ 只认本平台自己的形态) ─────────────────────────── */ test('R1 真机形态:dsh-100002-ef8d1d12.scope ⇒ uid 100002', () => { assert.equal(parseScopeUnitName('dsh-100002-ef8d1d12.scope'), 100002) }) test('R2 ⛔ 大写 hex / 非 scope / 别的单元一律不认', () => { assert.equal(parseScopeUnitName('dsh-100002-EF8D1D12.scope'), undefined) assert.equal(parseScopeUnitName('dsh-100002-ef8d1d12.service'), undefined) assert.equal(parseScopeUnitName('dshs-relay.service'), undefined) assert.equal(parseScopeUnitName('dsh-100002-ef8d1d12.scope.bak'), undefined) assert.equal(parseScopeUnitName(''), undefined) }) test('R3 ⛔ uid=0 / 缺段 一律不认(防误伤 systemd 自身与门户进程)', () => { assert.equal(parseScopeUnitName('dsh-0-ef8d1d12.scope'), undefined) assert.equal(parseScopeUnitName('dsh--ef8d1d12.scope'), undefined) assert.equal(parseScopeUnitName('dsh-100002.scope'), undefined) }) /* ── R6–R11:Description 解析(真机夹具) ──────────────────────────────────── */ test('R6 真机夹具:三要素全部解出且 userId 取自 --chdir(⛔ 不是取自 --bind)', () => { const info = parseScopeDescription(REAL_DESC, UID) assert.deepEqual(info, { userId: USER, role: 'main', port: 21000, folder: FOLDER }) }) test('R7 ⛔ uid 交叉校验不符 ⇒ 拒(scope 名与 argv 非同源 = 半截信息,必须停)', () => { assert.equal(parseScopeDescription(REAL_DESC, 100003), undefined) assert.equal(parseScopeDescription(REAL_DESC.replace('--reuid 100002', '--reuid 100009'), UID), undefined) }) test('R8 ⛔ 缺 --profile 或 profile 非 web/headless ⇒ 拒(role 猜不得)', () => { assert.equal(parseScopeDescription(REAL_DESC.replace('--profile web ', ''), UID), undefined) assert.equal(parseScopeDescription(REAL_DESC.replace('--profile web', '--profile weird'), UID), undefined) }) test('R9 ⛔ main 缺 --port / 端口非数字 / 越界 ⇒ 拒', () => { assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', ''), UID), undefined) assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', '--port abc'), UID), undefined) assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', '--port 0'), UID), undefined) assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', '--port 70000'), UID), undefined) }) test('R10 ⛔ 缺 --chdir / 相对路径 / 路径里没有 /users/ 段 ⇒ 拒(拿不到 userId)', () => { assert.equal(parseScopeDescription(REAL_DESC.replace('--chdir ' + FOLDER + ' ', ''), UID), undefined) assert.equal(parseScopeDescription(REAL_DESC.replace(FOLDER, 'ws'), UID), undefined) assert.equal( parseScopeDescription(REAL_DESC.replace(FOLDER, '/srv/ws').replace(/\/var\/lib\/dshs\/users\//g, '/srv/'), UID), undefined, ) }) test('R11 watchdog:headless 且有端口 ⇒ 拒(不自洽);headless 无端口 ⇒ 解出但 role=watchdog', () => { const headlessWithPort = REAL_DESC.replace('--profile web', '--profile headless') assert.equal(parseScopeDescription(headlessWithPort, UID), undefined) const headless = headlessWithPort.replace(' --port 21000', '') const info = parseScopeDescription(headless, UID) assert.equal(info?.role, 'watchdog') assert.equal(info?.port, undefined) assert.equal(info?.userId, USER) }) /* ── R12–R15:处置判定(认领 vs 按旧行为停) ───────────────────────────────── */ test('R12 合法 main ⇒ adopt', () => { const info = parseScopeDescription(REAL_DESC, UID) assert.deepEqual(decideScopeAction(info, false), { kind: 'adopt' }) }) test('R13 ⛔ 同 uid 多 scope ⇒ 全部停(档案 30 的风险本体:多实例共 profile)', () => { const info = parseScopeDescription(REAL_DESC, UID) assert.deepEqual(decideScopeAction(info, true), { kind: 'stop', reason: 'dup-uid' }) }) test('R14 ⛔ 解析失败 ⇒ 停(宁可清掉,不留半截实例)', () => { assert.deepEqual(decideScopeAction(undefined, false), { kind: 'stop', reason: 'unparsable' }) }) test('R15 ⛔ watchdog ⇒ 停(一次性 headless、无监听端口 ⇒ 无法确认健康,留着无收益)', () => { const info = parseScopeDescription(REAL_DESC.replace('--profile web', '--profile headless').replace(' --port 21000', ''), UID) assert.deepEqual(decideScopeAction(info, false), { kind: 'stop', reason: 'no-probe-target' }) }) /* ── R16–R18:源码级接线守卫(照 序⑦ T38 先例) ────────────────────────────── */ const SRC = await readFile(new URL('../src/supervisor/orchestrator.ts', import.meta.url), 'utf8') test('R16 接线:构造函数必须调 rehydrateAdoptedScopes(⛔ 不是裸 cleanAllStaleScopes)', () => { assert.ok( /this\.portGuard = createPortGuard\(config\.portGuard\)[\s\S]{0,400}this\.rehydrateAdoptedScopes\(\)/.test(SRC), 'constructor 未接认领逻辑(仍是启动即清空)', ) }) test('R17 ⛔ cleanAllStaleScopes 不许被删(回滚路径 + 非 account 回退都还在)', () => { assert.ok(SRC.includes('private cleanAllStaleScopes(): void')) assert.ok( /private rehydrateAdoptedScopes\(\): void \{[\s\S]{0,400}this\.cleanAllStaleScopes\(\)/.test(SRC), '非 account 回退丢了', ) }) test('R18 ⛔ 认领不得写进 mains(写进去 ⇒ enter 复用分支拿不到 token ⇒ 503)', () => { const body = SRC.slice(SRC.indexOf('private adoptOne('), SRC.indexOf('private probeAdopted(')) assert.ok(body.includes('this.adopted.set('), 'adoptOne 未登记到 adopted') assert.ok(!body.includes('this.mains.set('), '⛔ adoptOne 把实例写进了 mains ⇒ 用户会被 503 挡住') assert.ok(!body.includes('spawn('), '⛔ adoptOne 里出现了 spawn ⇒ 违反"不 spawn"') const afterAdopt = body.slice(body.indexOf('this.adopted.set(')) assert.ok(!afterAdopt.includes('stopUnit('), '⛔ adopt 路径上还停了实例(认领应当只登记 + 探活)') }) test('R19 ⛔ 认领/回收路径里不得出现凭据落盘(R11:安全维度不许净变差)', () => { const body = SRC.slice(SRC.indexOf('private rehydrateAdoptedScopes('), SRC.indexOf('/** 档案 30:清掉指定 uid')) for (const banned of ['writeFileSync', 'appendFileSync', 'launchToken']) { assert.ok(!body.includes(banned), `rehydrate 路径里出现了 ${banned}`) } }) test('R20 ⛔ 节流:认领必须逐条经 setTimeout 排队(不得同步一次全跑)', () => { const body = SRC.slice(SRC.indexOf('private rehydrateAdoptedScopes('), SRC.indexOf('private scanExistingScopes(')) assert.ok(body.includes('setTimeout('), '认领没有节流') assert.ok(SRC.includes('DSHS_REHYDRATE_STAGGER_MS'), '节流阈值不是可配的环境键') assert.ok(SRC.includes('DSHS_REHYDRATE_PROBE_MS'), '探活超时不是可配的环境键') })