文件
hyb-oa/scripts/qa/tests/full-local-environment.test.mjs
T

144 行
9.3 KiB
JavaScript

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"));
});