Files
tyx_AI_xhs/scripts/loopback-boundary-smoke.mjs
T

118 lines
4.1 KiB
JavaScript

import { createHash } from "node:crypto";
import { execFileSync, spawn } from "node:child_process";
import { once } from "node:events";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { request } from "node:http";
import { resolve } from "node:path";
const host = "127.0.0.1";
const port = 43121;
function sha256(value) {
return createHash("sha256").update(value).digest("hex").toUpperCase();
}
function firewallSnapshot() {
return execFileSync(
"netsh",
["advfirewall", "firewall", "show", "rule", "name=all", "dir=in"],
{ encoding: "utf8", windowsHide: true },
);
}
function httpRequest(headers = {}) {
return new Promise((resolveRequest, reject) => {
const outgoing = request({ headers, host, method: "GET", path: "/", port }, (response) => {
response.resume();
response.once("end", () => resolveRequest({ headers: response.headers, status: response.statusCode }));
});
outgoing.once("error", reject);
outgoing.end();
});
}
async function waitUntilReady() {
for (let attempt = 0; attempt < 40; attempt += 1) {
try {
const response = await httpRequest();
if (response.status === 200) return response;
} catch {
// The fixed listener may still be starting.
}
await new Promise((resolveWait) => setTimeout(resolveWait, 125));
}
throw new Error("The fixed loopback listener did not become ready.");
}
const firewallBefore = firewallSnapshot();
const child = spawn(process.execPath, ["apps/api/dist/main.js"], {
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
});
let stderr = "";
child.stderr.on("data", (chunk) => {
stderr += chunk.toString();
});
let rootResponse;
let rejectedResponse;
let listeners;
try {
rootResponse = await waitUntilReady();
rejectedResponse = await httpRequest({ host: "192.168.1.10:43121" });
const netstat = execFileSync("netstat", ["-ano", "-p", "tcp"], { encoding: "utf8", windowsHide: true });
const matchingListeners = netstat
.split(/\r?\n/)
.map((line) => line.trim().split(/\s+/))
.filter((tokens) => tokens[0]?.toUpperCase() === "TCP" && tokens.at(-1) === String(child.pid) && tokens[1]?.endsWith(`:${port}`))
.map((tokens) => ({ local_address: tokens[1], pid: child.pid }));
listeners = [...new Map(matchingListeners.map((listener) => [listener.local_address, listener])).values()];
} finally {
child.kill();
await Promise.race([
once(child, "exit"),
new Promise((_, reject) => setTimeout(() => reject(new Error("API process did not stop.")), 5000)),
]);
}
if (stderr.trim()) throw new Error("The API wrote to stderr during loopback smoke.");
const firewallAfter = firewallSnapshot();
const expectedListener = `${host}:${port}`;
const socketResult = {
expected: expectedListener,
listeners,
status: listeners.length > 0 && listeners.every(({ local_address }) => local_address === expectedListener)
? "passed"
: "failed",
};
const responseResult = {
invalid_host: rejectedResponse,
localhost: rootResponse,
status: rootResponse.status === 200 && rejectedResponse.status === 426 ? "passed" : "failed",
};
const firewallResult = {
after_sha256: sha256(firewallAfter),
before_sha256: sha256(firewallBefore),
status: firewallAfter === firewallBefore ? "passed" : "failed",
unchanged: firewallAfter === firewallBefore,
};
const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_BND;
if (evidenceDirectory) {
mkdirSync(evidenceDirectory, { recursive: true });
writeFileSync(resolve(evidenceDirectory, "socket-listeners.json"), `${JSON.stringify(socketResult, null, 2)}\n`);
writeFileSync(resolve(evidenceDirectory, "response.json"), `${JSON.stringify(responseResult, null, 2)}\n`);
writeFileSync(resolve(evidenceDirectory, "firewall-diff.json"), `${JSON.stringify(firewallResult, null, 2)}\n`);
}
const result = {
firewall: firewallResult.status,
responses: responseResult.status,
sockets: socketResult.status,
status: [firewallResult.status, responseResult.status, socketResult.status].every((status) => status === "passed")
? "passed"
: "failed",
};
console.log(JSON.stringify(result, null, 2));
if (result.status !== "passed") process.exit(1);