test(cli): expose remaining doctor HTTP waits

This commit is contained in:
dev-guard committed 2026-11-05 11:46:05 +00:00
1 parent 911c407d46
commit 00bbcddd0c
1 file changed
+119 -3
+119 -3
View File
@@ -1,7 +1,8 @@
import { describe, it, expect, vi } from "vitest";
import { createProgram } from "../src/index.js";
import { Command } from "commander";
import { doctorCommand, runDoctorChecks, type DoctorDeps } from "../src/commands/doctor.js";
import { doctorCommand, runDoctorChecks, readLiveLogicalIds, type DoctorDeps } from "../src/commands/doctor.js";
import { DaemonClient } from "../src/client.js";
import { resolveCmuxSettingsPath } from "../src/cmux-config.js";
const CMUX_SETTINGS_PATH = resolveCmuxSettingsPath();
@@ -567,8 +568,9 @@ describe("runDoctorChecks", () => {
it("settles a /healthz body that never finishes as owner unknown within its bound, and releases the request", async () => {
const signals: AbortSignal[] = [];
const deps = makeDeps({ checkPort: async () => false, callerUid: 1002, ownerReportTimeoutMs: 50,
fetch: async (_url: string, init?: { signal?: AbortSignal }) => {
if (init?.signal) signals.push(init.signal);
fetch: async (url: string, init?: { signal?: AbortSignal }) => {
// This owner-body cell measures the two health probes; status has its own deadline cells below.
if (init?.signal && url.endsWith("/healthz")) signals.push(init.signal);
return { ok: true, json: () => new Promise<unknown>(() => {}) };
} });
const started = Date.now();
@@ -796,3 +798,117 @@ describe("rig doctor", () => {
expect(portArgs).toContain(7433);
});
});
describe("doctor JSON HTTP deadlines", () => {
it.each(["cmux headers", "cmux body", "healthy"] as const)("returns an honest diagnostic when a real listener holds %s", async (held) => {
const { createServer } = await import("node:http");
const requests: string[] = [];
const server = createServer((req, res) => {
requests.push(req.url!);
if (req.url === "/healthz") {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ ok: true, pid: 4242, euid: 1002 }));
} else if (held === "healthy") {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ available: true }));
} else if (held === "cmux body") {
res.writeHead(200, { "content-type": "application/json" });
res.write('{"available":');
}
});
await new Promise<void>(resolve => server.listen(0, "127.0.0.1", resolve));
const port = (server.address() as { port: number }).port;
const log = vi.spyOn(console, "log").mockImplementation(() => {});
const priorExitCode = process.exitCode;
const program = new Command();
program.addCommand(doctorCommand(makeDeps({ checkPort: async () => false, callerUid: 1002,
ownerReportTimeoutMs: 100, fetch: (url, init) => globalThis.fetch(url, init),
configStore: { resolve: () => ({ ...defaultConfig, daemon: { host: "127.0.0.1", port } }) },
})));
const task = program.parseAsync(["node", "rig", "doctor", "--json"]);
let observationTimer: ReturnType<typeof setTimeout> | undefined;
try {
const outcome = await Promise.race([task.then(() => "settled"), new Promise<string>(resolve => {
observationTimer = setTimeout(() => resolve("still-waiting"), 1500);
})]);
expect(["darwin", "linux"]).toContain(process.platform);
expect(requests).toContain("/healthz");
expect(requests).toContain("/api/adapters/cmux/status");
console.info("[doctor HTTP deadline]", JSON.stringify({ platform: process.platform, held, requests, outcome }));
expect(outcome).toBe("settled");
const result = JSON.parse(String(log.mock.calls.at(-1)![0]));
const portCheck = result.checks.find((c: { name: string }) => c.name === "port");
const cmux = result.checks.find((c: { name: string }) => c.name === "cmux_daemon");
expect(portCheck.status).toBe("pass");
expect(cmux.status).toBe(held === "healthy" ? "pass" : "warn");
} finally {
clearTimeout(observationTimer);
server.closeAllConnections();
await new Promise<void>(resolve => server.close(() => resolve()));
await Promise.allSettled([task]);
log.mockRestore(); process.exitCode = priorExitCode;
expect(server.listening).toBe(false);
}
});
});
describe("doctor --spec topology HTTP deadlines", () => {
it.each(["rig headers", "rig body", "nodes headers", "nodes body", "healthy"] as const)(
"returns unknown or measured conformance when the topology listener is %s", async held => {
const { createServer } = await import("node:http");
const requests: string[] = [];
const heldPath = held.startsWith("rig") ? "/api/rigs" : "/api/rigs/rig-id/nodes";
const server = createServer((req, res) => {
requests.push(req.url!);
if (held !== "healthy" && req.url === heldPath) {
if (held.endsWith("body")) { res.writeHead(200, { "content-type": "application/json" }); res.write("["); }
return;
}
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify(req.url === "/healthz" ? { ok: true, pid: 4242, euid: 1002 }
: req.url === "/api/rigs" ? [{ id: "rig-id", name: "fixture" }]
: req.url === "/api/rigs/rig-id/nodes" ? [{ logicalId: "dev.worker" }] : { available: true }));
});
await new Promise<void>(resolve => server.listen(0, "127.0.0.1", resolve));
const port = (server.address() as { port: number }).port;
const base = `http://127.0.0.1:${port}`;
vi.stubEnv("OPENRIG_TRANSPORT_LOCAL_USER_GUARD", "true");
vi.stubEnv("OPENRIG_URL", ""); vi.stubEnv("RIGGED_URL", "");
const ownerProbe = vi.fn(async () => ({ state: "unknown" as const, reason: "fixture-unconfirmed" }));
const ownerClient = new DaemonClient(base, { ownerProbe });
const specPath = "/fixture/rig.yaml";
const spec = 'version: "0.2"\nname: fixture\npods:\n - id: dev\n label: Development\n members:\n - id: worker\n';
const log = vi.spyOn(console, "log").mockImplementation(() => {});
const priorExitCode = process.exitCode;
const program = new Command().addCommand(doctorCommand(makeDeps({ callerUid: 1002,
checkPort: async () => false, specPath, readFile: path => path === specPath ? spec : null,
configStore: { resolve: () => ({ ...defaultConfig, daemon: { host: "127.0.0.1", port } }) },
fetch: (url, init) => globalThis.fetch(url, init),
fetchLiveLogicalIds: name => readLiveLogicalIds(base, name, ownerClient),
})));
const task = program.parseAsync(["node", "rig", "doctor", "--json", "--spec", specPath]);
let timer: ReturnType<typeof setTimeout> | undefined;
try {
const outcome = await Promise.race([task.then(() => "settled"), new Promise<string>(resolve => {
timer = setTimeout(() => resolve("still-waiting"), 4500);
})]);
expect(["darwin", "linux"]).toContain(process.platform);
expect(requests).toContain(held === "healthy" ? "/api/rigs/rig-id/nodes" : heldPath);
expect(ownerProbe).toHaveBeenCalled();
console.info("[doctor spec HTTP deadline]", JSON.stringify({ platform: process.platform, held, requests, outcome }));
expect(outcome).toBe("settled");
const result = JSON.parse(String(log.mock.calls.at(-1)![0]));
const check = result.checks.find((c: { name: string }) => c.name === "spec_live_conformance");
expect(check.status).toBe(held === "healthy" ? "pass" : "skipped");
if (held === "healthy") expect(ownerProbe).toHaveBeenCalledTimes(2);
} finally {
clearTimeout(timer); server.closeAllConnections();
await new Promise<void>(resolve => server.close(() => resolve()));
await Promise.allSettled([task]);
log.mockRestore(); process.exitCode = priorExitCode; vi.unstubAllEnvs();
expect(server.listening).toBe(false);
}
});
});