import assert from "node:assert/strict"; import test from "node:test"; import { spawnSync } from "node:child_process"; import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs"; import path from "node:path"; import os from "node:os"; import { fileURLToPath } from "node:url"; import { createServer } from "node:net"; import { topologyMatches, validateEnvironmentFacts, validateEnvironmentReceipt, verifyManagedEnvironment, requestSupervisorObservation } from "../full-local-environment.mjs"; function receipt() { return { schemaVersion: 1, profile: "isolated-online-v1", policyDigest: "policy", supervisorObservation: { source: "live-supervisor-unix-ipc", sampledAt: "2026-09-07T00:00:00Z", namespace: "parent" }, namespace: { inheritedRunner: "parent", ownedChild: "child", runnerCreated: true, currentMatchesOwnedChild: true, topologyMatched: true }, nativeOnlineProbe: { valid: true, online: true, browserNamespace: "child", browserMatchesOwnedChild: true, noNavigatorOverride: true, engines: ["chromium", "webkit"].map(name => ({ name, version: "synthetic", online: true, valid: true, noNavigatorOverride: true, browserNamespace: "child", browserMatchesOwnedChild: true })) }, bootstrapObservation: { valid: true, observationPoint: "guest-entry-black-box", freshContext: true, initialSessionHint: false, listenerBeforeNavigation: true, noRouteMocks: true, loginFormVisible: true, refreshRequestCount: 0, loginRequestCount: 0, nativeOnlineSamples: { beforeNavigation: true, documentStart: true, afterForm: true }, browserNamespace: "child", browserMatchesOwnedChild: true, noNavigatorOverride: true, documentStatus: 200, stayedOnLogin: true, }, }; } test("环境回执必须消费事实而非 valid 标记", () => { assert.deepEqual(validateEnvironmentReceipt(receipt(), "policy"), []); for (const section of ["namespace", "nativeOnlineProbe", "bootstrapObservation", "supervisorObservation"]) { for (const key of Object.keys(receipt()[section])) { const value = receipt(); delete value[section][key]; assert.ok(validateEnvironmentReceipt(value, "policy").length, `${section}.${key}`); } } for (const mutate of [ value => { value.bootstrapObservation.refreshRequestCount = 1; }, value => { value.bootstrapObservation.nativeOnlineSamples.documentStart = false; }, value => { value.namespace.ownedChild = "parent"; }, value => { value.nativeOnlineProbe.browserNamespace = "other"; }, value => { value.policyDigest = "old"; }, ]) { const value = receipt(); mutate(value); assert.ok(validateEnvironmentReceipt(value, "policy").length); } }); test("两引擎逐项必需:诊断记录不构成失败引擎的豁免", () => { for (const index of [0, 1]) { for (const mutate of [ engine => { engine.valid = false; engine.failureStage = "launch"; engine.failures = [{ stage: "launch", errorKind: "browser_process_closed" }]; }, engine => { engine.online = false; }, engine => { engine.browserNamespace = "other"; }, engine => { engine.browserMatchesOwnedChild = false; }, engine => { engine.noNavigatorOverride = false; }, engine => { delete engine.version; }, ]) { const value = receipt(); mutate(value.nativeOnlineProbe.engines[index]); assert.ok(validateEnvironmentReceipt(value, "policy").includes("native_engine_probe_invalid")); } } for (const engines of [[], [receipt().nativeOnlineProbe.engines[0]], receipt().nativeOnlineProbe.engines.toReversed()]) { const value = receipt(); value.nativeOnlineProbe.engines = engines; assert.ok(validateEnvironmentReceipt(value, "policy").includes("native_engine_probe_invalid")); } }); test("父 runner 实时 IPC 绑定本轮、进程和当前策略,错误响应拒绝", async context => { const scratch = mkdtempSync(path.join(os.tmpdir(), "hyb-env-ipc-test-")); const owner = { scratch, runId: "current", supervisorPid: process.pid, policyDigest: "policy" }; let observation = { runId: owner.runId, pid: owner.supervisorPid, policyDigest: owner.policyDigest, namespace: "parent" }; const server = createServer(connection => connection.end(JSON.stringify(observation))); context.after(async () => { await new Promise(resolve => server.close(resolve)); rmSync(scratch, { recursive: true, force: true }); }); await new Promise(resolve => server.listen(path.join(scratch, "supervisor.sock"), resolve)); assert.equal((await requestSupervisorObservation(owner)).namespace, "parent"); for (const key of ["runId", "pid", "policyDigest", "namespace"]) { const previous = observation; observation = { ...previous, [key]: null }; await assert.rejects(requestSupervisorObservation(owner), /response_invalid/); observation = previous; } await assert.rejects(requestSupervisorObservation({ ...owner, scratch: path.join(scratch, "missing") }), /unavailable/); }); test("缺 context 不接受 GATE_PASSED 或已有 true", () => { assert.throws(() => verifyManagedEnvironment(""), /context_required/); const context = { schemaVersion: 1, profile: "isolated-online-v1", policyDigest: "policy", inheritedNamespace: "parent", ownedNamespace: "child" }; const facts = { policyDigest: "policy", supervisorRecognized: true, descendantOfSupervisor: true, supervisorNamespace: "parent", currentNamespace: "child", topologyMatched: true }; assert.deepEqual(validateEnvironmentFacts(context, facts), []); for (const key of Object.keys(facts)) assert.ok(validateEnvironmentFacts(context, { ...facts, [key]: null }).length, key); }); test("拓扑只接纳同 namespace 内部 veth 默认路由,拒绝外部对端和网关", () => { const addresses = [{ ifname: "lo" }, ...["qaonline", "qapeer"].map((ifname, index) => ({ ifname, flags: ["UP"], operstate: "UP", addr_info: [{ local: `192.0.2.${index + 1}`, prefixlen: 30 }] }))]; const links = [{ifname:"lo",ifindex:1},{ifname:"qaonline",ifindex:2,link:"qapeer",linkinfo:{info_kind:"veth"}},{ifname:"qapeer",ifindex:3,link:"qaonline",linkinfo:{info_kind:"veth"}}]; const routes = [{dst:"default",dev:"qaonline",scope:"link"}]; assert.equal(topologyMatches(addresses, routes, [], links), true); for(const mutate of [ f=>{f.routes=[];}, f=>{f.routes.push(f.routes[0]);}, f=>{f.routes[0].gateway="192.0.2.2";}, f=>{f.routes[0].dev="eth0";}, f=>{f.routes[0].scope="global";}, f=>{f.routes[0].multipath=[];}, f=>{f.ipv6=[{dst:"default",dev:"qaonline"}];}, f=>{f.addresses.push({ifname:"eth0"});}, f=>{f.links[1].link_netnsid=0;}, f=>{f.links[1].link="outside";}, f=>{f.links[1].linkinfo.info_kind="bridge";}, f=>{f.links[1].master="outside";}, f=>{f.links.pop();}, f=>{f.links=undefined;}, ]) { const f=structuredClone({addresses,links,routes,ipv6:[]});mutate(f); assert.equal(topologyMatches(f.addresses,f.routes,f.ipv6,f.links),false); } }); for (const mismatch of [false, true]) test(`namespace 创建失败不降级,清理须匹配 owner:mismatch=${mismatch}`, context => { const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../.."); const temporary = mkdtempSync(path.join(os.tmpdir(), "hyb-env-command-test-")); const report = mkdtempSync(path.join(root, "artifacts/qa-flow/environment-command-test-")); const commands = path.join(temporary, "commands.jsonl"); mkdirSync(path.join(temporary, "bin")); context.after(() => { try { const owned = JSON.parse(readFileSync(path.join(report, "environment/context.private.json"))); assert.ok(path.basename(owned.scratch).startsWith("hyb-qa-managed-")); rmSync(owned.scratch, { recursive: true, force: true }); } finally { rmSync(temporary, { recursive: true, force: true }); rmSync(report, { recursive: true, force: true }); } }); writeFileSync(path.join(temporary, "bin/docker"), `#!${process.execPath} const fs = require('node:fs'); const args = process.argv.slice(2); fs.appendFileSync(${JSON.stringify(commands)},JSON.stringify(args)+'\\n'); if (args[0] === 'image') console.log('sha256:synthetic'); if (args[0] === 'run') { fs.writeFileSync(${JSON.stringify(path.join(temporary, "owner"))},args[args.indexOf('--name')+1]); console.log('a'.repeat(64)); } if (args[0] === 'inspect') console.log('a'.repeat(64)+'|'+(${mismatch} ? 'other-owner' : fs.readFileSync(${JSON.stringify(path.join(temporary, "owner"))},'utf8'))+'|none'); `, { mode: 0o755 }); writeFileSync(path.join(temporary, "bin/unshare"), "#!/bin/sh\nexit 1\n", { mode: 0o755 }); const result = spawnSync(process.execPath, [path.join(root, "scripts/qa/full-local-environment.mjs"), "full-local-diagnostic"], { cwd: root, encoding: "utf8", timeout: 30000, env: { PATH: `${path.join(temporary, "bin")}:${process.env.PATH}`, HOME: temporary, QA_DIAGNOSTIC_REPORT_DIR: report, QA_DIAGNOSTIC_REASON: "synthetic test", QA_DIAGNOSTIC_OPERATOR: "fixture", QA_DIAGNOSTIC_APPROVAL_REF: "fixture-only", ENVIRONMENT_PASSED: "1" }, }); assert.equal(result.status, 2, result.stderr); const receipt = JSON.parse(readFileSync(path.join(report, "environment/result.json"))); assert.equal(receipt.status, "ENVIRONMENT_INVALID"); assert.equal(receipt.normalReleaseEligible, false); assert.equal(receipt.cleanup.containerRemoved, !mismatch); const calls = readFileSync(commands, "utf8").trim().split("\n").map(JSON.parse); assert.equal(calls.some(args => args[0] === "rm"), !mismatch); assert.ok(calls.find(args => args[0] === "run").includes("--pull=never")); });