test: keep gateway suites minimal

This commit is contained in:
Peter Steinberger
2026-04-17 22:46:38 +01:00
parent e493d1d2fd
commit 5cf01ac7c1
7 changed files with 113 additions and 79 deletions

View File

@@ -1,4 +1,5 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { isVitestRuntimeEnv } from "../infra/env.js";
import { startHeartbeatRunner, type HeartbeatRunner } from "../infra/heartbeat-runner.js";
import type { ChannelHealthMonitor } from "./channel-health-monitor.js";
import { startChannelHealthMonitor } from "./channel-health-monitor.js";
@@ -87,7 +88,7 @@ export function startGatewayRuntimeServices(params: {
heartbeatRunner: createNoopHeartbeatRunner(),
channelHealthMonitor,
stopModelPricingRefresh:
!params.minimalTestGateway && process.env.VITEST !== "1"
!params.minimalTestGateway && !isVitestRuntimeEnv()
? startGatewayModelPricingRefresh({ config: params.cfgAtStart })
: () => {},
};

View File

@@ -15,7 +15,6 @@ import {
} from "./server-session-events.js";
export function startGatewayEventSubscriptions(params: {
minimalTestGateway: boolean;
broadcast: (event: string, payload: unknown, opts?: { dropIfSlow?: boolean }) => void;
broadcastToConnIds: (
event: string,
@@ -33,47 +32,39 @@ export function startGatewayEventSubscriptions(params: {
sessionMessageSubscribers: SessionMessageSubscriberRegistry;
chatAbortControllers: Map<string, unknown>;
}) {
const agentUnsub = params.minimalTestGateway
? null
: onAgentEvent(
createAgentEventHandler({
broadcast: params.broadcast,
broadcastToConnIds: params.broadcastToConnIds,
nodeSendToSession: params.nodeSendToSession,
agentRunSeq: params.agentRunSeq,
chatRunState: params.chatRunState,
resolveSessionKeyForRun: params.resolveSessionKeyForRun,
clearAgentRunContext: params.clearAgentRunContext,
toolEventRecipients: params.toolEventRecipients,
sessionEventSubscribers: params.sessionEventSubscribers,
isChatSendRunActive: (runId) => params.chatAbortControllers.has(runId),
}),
);
const agentUnsub = onAgentEvent(
createAgentEventHandler({
broadcast: params.broadcast,
broadcastToConnIds: params.broadcastToConnIds,
nodeSendToSession: params.nodeSendToSession,
agentRunSeq: params.agentRunSeq,
chatRunState: params.chatRunState,
resolveSessionKeyForRun: params.resolveSessionKeyForRun,
clearAgentRunContext: params.clearAgentRunContext,
toolEventRecipients: params.toolEventRecipients,
sessionEventSubscribers: params.sessionEventSubscribers,
isChatSendRunActive: (runId) => params.chatAbortControllers.has(runId),
}),
);
const heartbeatUnsub = params.minimalTestGateway
? null
: onHeartbeatEvent((evt) => {
params.broadcast("heartbeat", evt, { dropIfSlow: true });
});
const heartbeatUnsub = onHeartbeatEvent((evt) => {
params.broadcast("heartbeat", evt, { dropIfSlow: true });
});
const transcriptUnsub = params.minimalTestGateway
? null
: onSessionTranscriptUpdate(
createTranscriptUpdateBroadcastHandler({
broadcastToConnIds: params.broadcastToConnIds,
sessionEventSubscribers: params.sessionEventSubscribers,
sessionMessageSubscribers: params.sessionMessageSubscribers,
}),
);
const transcriptUnsub = onSessionTranscriptUpdate(
createTranscriptUpdateBroadcastHandler({
broadcastToConnIds: params.broadcastToConnIds,
sessionEventSubscribers: params.sessionEventSubscribers,
sessionMessageSubscribers: params.sessionMessageSubscribers,
}),
);
const lifecycleUnsub = params.minimalTestGateway
? null
: onSessionLifecycleEvent(
createLifecycleEventBroadcastHandler({
broadcastToConnIds: params.broadcastToConnIds,
sessionEventSubscribers: params.sessionEventSubscribers,
}),
);
const lifecycleUnsub = onSessionLifecycleEvent(
createLifecycleEventBroadcastHandler({
broadcastToConnIds: params.broadcastToConnIds,
sessionEventSubscribers: params.sessionEventSubscribers,
}),
);
return {
agentUnsub,

View File

@@ -310,33 +310,36 @@ export function registerControlUiAndPairingSuite(): void {
});
});
test("allows localhost control ui without device identity when insecure auth is enabled", async () => {
test("allows localhost ui clients without device identity when insecure auth is enabled", async () => {
testState.gatewayControlUi = { allowInsecureAuth: true };
const { server, ws, prevToken } = await startControlUiServerWithClient("secret", {
const { server, ws, port, prevToken } = await startControlUiServerWithClient("secret", {
wsHeaders: { origin: "http://127.0.0.1" },
});
await connectControlUiWithoutDeviceAndExpectOk({ ws, token: "secret" });
ws.close();
await server.close();
restoreGatewayToken(prevToken);
});
let tuiWs: WebSocket | undefined;
try {
await connectControlUiWithoutDeviceAndExpectOk({ ws, token: "secret" });
test("allows localhost tui without device identity when insecure auth is enabled", async () => {
testState.gatewayControlUi = { allowInsecureAuth: true };
const { server, ws, prevToken } = await startControlUiServerWithClient("secret");
await connectControlUiWithoutDeviceAndExpectOk({
ws,
token: "secret",
client: {
id: GATEWAY_CLIENT_NAMES.TUI,
version: "1.0.0",
platform: "darwin",
mode: GATEWAY_CLIENT_MODES.UI,
},
});
ws.close();
await server.close();
restoreGatewayToken(prevToken);
tuiWs = await openWs(port);
await connectControlUiWithoutDeviceAndExpectOk({
ws: tuiWs,
token: "secret",
client: {
id: GATEWAY_CLIENT_NAMES.TUI,
version: "1.0.0",
platform: "darwin",
mode: GATEWAY_CLIENT_MODES.UI,
},
});
} finally {
ws.close();
tuiWs?.close();
await Promise.all([
waitForWsClose(ws, 1_000),
...(tuiWs ? [waitForWsClose(tuiWs, 1_000)] : []),
]);
await server.close();
restoreGatewayToken(prevToken);
}
});
test("allows control ui password-only auth on localhost when insecure auth is enabled", async () => {
@@ -1322,16 +1325,35 @@ export function registerControlUiAndPairingSuite(): void {
}
});
test("allows local gateway backend shared-auth connections without device pairing", async () => {
const { server, ws, prevToken } = await startControlUiServerWithClient("secret");
test("allows gateway backend loopback shared-auth connections without device pairing", async () => {
const { server, ws, port, prevToken } = await startControlUiServerWithClient("secret");
const sockets = [ws];
try {
const localBackend = await connectReq(ws, {
token: "secret",
client: BACKEND_GATEWAY_CLIENT,
});
expect(localBackend.ok).toBe(true);
const backendCases: Array<{
name: string;
headers?: Record<string, string>;
socket?: WebSocket;
}> = [
{ name: "default host", socket: ws },
{ name: "remote-looking host", headers: { host: "gateway.example" } },
{ name: "private host", headers: { host: "172.17.0.2:18789" } },
];
for (const backendCase of backendCases) {
const socket = backendCase.socket ?? (await openWs(port, backendCase.headers));
if (!backendCase.socket) {
sockets.push(socket);
}
const backendConnect = await connectReq(socket, {
token: "secret",
client: BACKEND_GATEWAY_CLIENT,
});
expect(backendConnect.ok, backendCase.name).toBe(true);
}
} finally {
ws.close();
for (const socket of sockets) {
socket.close();
}
await server.close();
restoreGatewayToken(prevToken);
}

View File

@@ -18,7 +18,7 @@ import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
import { resolveMainSessionKey } from "../config/sessions.js";
import { clearAgentRunContext } from "../infra/agent-events.js";
import { isDiagnosticsEnabled } from "../infra/diagnostic-events.js";
import { logAcceptedEnvOption } from "../infra/env.js";
import { isVitestRuntimeEnv, logAcceptedEnvOption } from "../infra/env.js";
import { ensureOpenClawCliOnPath } from "../infra/path-env.js";
import { setGatewaySigusr1RestartPolicy, setPreRestartDeferralCheck } from "../infra/restart.js";
import { enqueueSystemEvent } from "../infra/system-events.js";
@@ -208,7 +208,7 @@ export async function startGatewayServer(
opts: GatewayServerOptions = {},
): Promise<GatewayServer> {
const minimalTestGateway =
process.env.VITEST === "1" && process.env.OPENCLAW_TEST_MINIMAL_GATEWAY === "1";
isVitestRuntimeEnv() && process.env.OPENCLAW_TEST_MINIMAL_GATEWAY === "1";
// Ensure all default port derivations (browser/canvas) see the actual runtime port.
process.env.OPENCLAW_GATEWAY_PORT = String(port);
@@ -599,7 +599,6 @@ export async function startGatewayServer(
Object.assign(
runtimeState,
startGatewayEventSubscriptions({
minimalTestGateway,
broadcast,
broadcastToConnIds,
nodeSendToSession,

View File

@@ -542,9 +542,11 @@ describe("gateway server misc", () => {
"utf-8",
);
const autoPort = await getFreePort();
const autoServer = await startGatewayServer(autoPort);
await autoServer.close();
await withEnvAsync({ OPENCLAW_TEST_MINIMAL_GATEWAY: undefined }, async () => {
const autoPort = await getFreePort();
const autoServer = await startGatewayServer(autoPort);
await autoServer.close();
});
const updated = JSON.parse(await fs.readFile(configPath, "utf-8")) as Record<string, unknown>;
const channels = updated.channels as Record<string, unknown> | undefined;

View File

@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { WebSocket } from "ws";
import { resolveMainSessionKeyFromConfig } from "../config/sessions.js";
import { drainSystemEvents } from "../infra/system-events.js";
import { withEnvAsync } from "../test-utils/env.js";
import {
TALK_TEST_PROVIDER_API_KEY_PATH,
TALK_TEST_PROVIDER_ID,
@@ -369,8 +370,16 @@ describe("gateway hot reload", () => {
);
}
async function withNonMinimalGatewayServer(
fn: Parameters<typeof withGatewayServer>[0],
): ReturnType<typeof withGatewayServer> {
return await withEnvAsync({ OPENCLAW_TEST_MINIMAL_GATEWAY: undefined }, async () =>
withGatewayServer(fn),
);
}
it("applies hot reload actions and emits restart signal", async () => {
await withGatewayServer(async () => {
await withNonMinimalGatewayServer(async () => {
const onHotReload = hoisted.getOnHotReload();
expect(onHotReload).toBeTypeOf("function");
@@ -473,7 +482,7 @@ describe("gateway hot reload", () => {
await writeEnvRefConfig();
process.env.OPENAI_API_KEY = "sk-startup"; // pragma: allowlist secret
await withGatewayServer(async () => {
await withNonMinimalGatewayServer(async () => {
const onHotReload = hoisted.getOnHotReload();
expect(onHotReload).toBeTypeOf("function");
const sessionKey = resolveMainSessionKeyFromConfig();

View File

@@ -67,6 +67,16 @@ export function isTruthyEnvValue(value?: string): boolean {
}
}
export function isVitestRuntimeEnv(env: NodeJS.ProcessEnv = process.env): boolean {
return (
env.VITEST === "true" ||
env.VITEST === "1" ||
env.VITEST_POOL_ID !== undefined ||
env.VITEST_WORKER_ID !== undefined ||
env.NODE_ENV === "test"
);
}
export function normalizeEnv(): void {
normalizeZaiEnv();
}