test(gateway): speed up slow e2e test setup

This commit is contained in:
Peter Steinberger
2026-02-24 00:59:44 +00:00
parent 13478cc79a
commit 0cc327546b
7 changed files with 61 additions and 55 deletions

View File

@@ -91,9 +91,9 @@ async function ensureResponseConsumed(res: Response) {
}
describe("OpenResponses HTTP API (e2e)", () => {
it("rejects when disabled (default + config)", { timeout: 120_000 }, async () => {
it("rejects when disabled (default + config)", { timeout: 30_000 }, async () => {
const port = await getFreePort();
const _server = await startServer(port);
const server = await startServer(port);
try {
const res = await postResponses(port, {
model: "openclaw",
@@ -102,7 +102,7 @@ describe("OpenResponses HTTP API (e2e)", () => {
expect(res.status).toBe(404);
await ensureResponseConsumed(res);
} finally {
// shared server
await server.close({ reason: "test done" });
}
const disabledPort = await getFreePort();

View File

@@ -20,17 +20,22 @@ installGatewayTestHooks({ scope: "suite" });
let server: Awaited<ReturnType<typeof startServerWithClient>>["server"];
let ws: Awaited<ReturnType<typeof startServerWithClient>>["ws"];
let sharedSessionStoreDir: string;
let sharedSessionStorePath: string;
beforeAll(async () => {
const started = await startServerWithClient();
server = started.server;
ws = started.ws;
await connectOk(ws);
sharedSessionStoreDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gw-session-"));
sharedSessionStorePath = path.join(sharedSessionStoreDir, "sessions.json");
});
afterAll(async () => {
ws.close();
await server.close();
await fs.rm(sharedSessionStoreDir, { recursive: true, force: true });
});
const BASE_IMAGE_PNG =
@@ -49,8 +54,7 @@ async function setTestSessionStore(params: {
entries: Record<string, Record<string, unknown>>;
agentId?: string;
}) {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gw-"));
testState.sessionStorePath = path.join(dir, "sessions.json");
testState.sessionStorePath = sharedSessionStorePath;
await writeSessionStore({
entries: params.entries,
agentId: params.agentId,
@@ -213,10 +217,7 @@ describe("gateway server agent", () => {
test("agent preserves spawnDepth on subagent sessions", async () => {
setRegistry(defaultRegistry);
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gw-"));
const storePath = path.join(dir, "sessions.json");
testState.sessionStorePath = storePath;
await writeSessionStore({
await setTestSessionStore({
entries: {
"agent:main:subagent:depth": {
sessionId: "sess-sub-depth",
@@ -234,7 +235,7 @@ describe("gateway server agent", () => {
});
expect(res.ok).toBe(true);
const raw = await fs.readFile(storePath, "utf-8");
const raw = await fs.readFile(sharedSessionStorePath, "utf-8");
const persisted = JSON.parse(raw) as Record<
string,
{ spawnDepth?: number; spawnedBy?: string }

View File

@@ -14,6 +14,7 @@ import {
installGatewayTestHooks({ scope: "suite" });
let startedServer: Awaited<ReturnType<typeof startServerWithClient>> | null = null;
let sharedTempRoot: string;
function requireWs(): Awaited<ReturnType<typeof startServerWithClient>>["ws"] {
if (!startedServer) {
@@ -23,6 +24,7 @@ function requireWs(): Awaited<ReturnType<typeof startServerWithClient>>["ws"] {
}
beforeAll(async () => {
sharedTempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-config-"));
startedServer = await startServerWithClient(undefined, { controlUiEnabled: true });
await connectOk(requireWs());
});
@@ -34,8 +36,16 @@ afterAll(async () => {
startedServer.ws.close();
await startedServer.server.close();
startedServer = null;
await fs.rm(sharedTempRoot, { recursive: true, force: true });
});
async function resetTempDir(name: string): Promise<string> {
const dir = path.join(sharedTempRoot, name);
await fs.rm(dir, { recursive: true, force: true });
await fs.mkdir(dir, { recursive: true });
return dir;
}
describe("gateway config methods", () => {
it("rejects config.patch when raw is not an object", async () => {
const res = await rpcReq<{ ok?: boolean }>(requireWs(), "config.patch", {
@@ -48,7 +58,7 @@ describe("gateway config methods", () => {
describe("gateway server sessions", () => {
it("filters sessions by agentId", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-agents-"));
const dir = await resetTempDir("agents");
testState.sessionConfig = {
store: path.join(dir, "{agentId}", "sessions.json"),
};
@@ -109,7 +119,7 @@ describe("gateway server sessions", () => {
});
it("resolves and patches main alias to default agent main key", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const dir = await resetTempDir("main-alias");
const storePath = path.join(dir, "sessions.json");
testState.sessionStorePath = storePath;
testState.agentsConfig = { list: [{ id: "ops", default: true }] };

View File

@@ -7,10 +7,11 @@ import { startGatewayServerHarness, type GatewayServerHarness } from "./server.e
import { installGatewayTestHooks, onceMessage } from "./test-helpers.js";
installGatewayTestHooks({ scope: "suite" });
const HEALTH_E2E_TIMEOUT_MS = 30_000;
const HEALTH_E2E_TIMEOUT_MS = 20_000;
const PRESENCE_EVENT_TIMEOUT_MS = 6_000;
const SHUTDOWN_EVENT_TIMEOUT_MS = 3_000;
const FINGERPRINT_TIMEOUT_MS = 3_000;
const CLI_PRESENCE_TIMEOUT_MS = 3_000;
let harness: GatewayServerHarness;
@@ -45,26 +46,19 @@ describe("gateway server health/presence", () => {
ws,
(o) => o.type === "res" && o.id === "presence1",
);
const channelsP = onceMessage<GatewayFrame>(
ws,
(o) => o.type === "res" && o.id === "channels1",
);
const sendReq = (id: string, method: string) =>
ws.send(JSON.stringify({ type: "req", id, method }));
sendReq("health1", "health");
sendReq("status1", "status");
sendReq("presence1", "system-presence");
sendReq("channels1", "channels.status");
const health = await healthP;
const status = await statusP;
const presence = await presenceP;
const channels = await channelsP;
expect(health.ok).toBe(true);
expect(status.ok).toBe(true);
expect(presence.ok).toBe(true);
expect(channels.ok).toBe(true);
expect(Array.isArray(presence.payload)).toBe(true);
ws.close();
@@ -292,7 +286,7 @@ describe("gateway server health/presence", () => {
const presenceP = onceMessage<GatewayFrame>(
ws,
(o) => o.type === "res" && o.id === "cli-presence",
4000,
CLI_PRESENCE_TIMEOUT_MS,
);
ws.send(
JSON.stringify({

View File

@@ -193,7 +193,7 @@ describe("gateway server models + voicewake", () => {
test(
"voicewake.get returns defaults and voicewake.set broadcasts",
{ timeout: 60_000 },
{ timeout: 20_000 },
async () => {
await withTempHome(async (homeDir) => {
const initial = await rpcReq<{ triggers: string[] }>(ws, "voicewake.get");
@@ -379,7 +379,7 @@ describe("gateway server misc", () => {
});
});
test("send dedupes by idempotencyKey", { timeout: 60_000 }, async () => {
test("send dedupes by idempotencyKey", { timeout: 15_000 }, async () => {
const prevRegistry = getActivePluginRegistry() ?? emptyRegistry;
try {
setActivePluginRegistry(whatsappRegistry);
@@ -452,8 +452,9 @@ describe("gateway server misc", () => {
test("refuses to start when port already bound", async () => {
const { server: blocker, port: blockedPort } = await occupyPort();
await expect(startGatewayServer(blockedPort)).rejects.toBeInstanceOf(GatewayLockError);
await expect(startGatewayServer(blockedPort)).rejects.toThrow(/already listening/i);
const startup = startGatewayServer(blockedPort);
await expect(startup).rejects.toBeInstanceOf(GatewayLockError);
await expect(startup).rejects.toThrow(/already listening/i);
blocker.close();
});

View File

@@ -93,22 +93,27 @@ vi.mock("../discord/monitor/thread-bindings.js", async (importOriginal) => {
installGatewayTestHooks({ scope: "suite" });
let harness: GatewayServerHarness;
let sharedSessionStoreDir: string;
let sharedSessionStorePath: string;
beforeAll(async () => {
harness = await startGatewayServerHarness();
sharedSessionStoreDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
sharedSessionStorePath = path.join(sharedSessionStoreDir, "sessions.json");
});
afterAll(async () => {
await harness.close();
await fs.rm(sharedSessionStoreDir, { recursive: true, force: true });
});
const openClient = async (opts?: Parameters<typeof connectOk>[1]) => await harness.openClient(opts);
async function createSessionStoreDir() {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
testState.sessionStorePath = storePath;
return { dir, storePath };
await fs.rm(sharedSessionStoreDir, { recursive: true, force: true });
await fs.mkdir(sharedSessionStoreDir, { recursive: true });
testState.sessionStorePath = sharedSessionStorePath;
return { dir: sharedSessionStoreDir, storePath: sharedSessionStorePath };
}
async function writeSingleLineSession(dir: string, sessionId: string, content: string) {
@@ -472,9 +477,7 @@ describe("gateway server sessions", () => {
});
test("sessions.preview returns transcript previews", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-preview-"));
const storePath = path.join(dir, "sessions.json");
testState.sessionStorePath = storePath;
const { dir } = await createSessionStoreDir();
const sessionId = "sess-preview";
const transcriptPath = path.join(dir, `${sessionId}.jsonl`);
const lines = createToolSummaryPreviewTranscriptLines(sessionId);
@@ -498,9 +501,7 @@ describe("gateway server sessions", () => {
});
test("sessions.preview resolves legacy mixed-case main alias with custom mainKey", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-preview-alias-"));
const storePath = path.join(dir, "sessions.json");
testState.sessionStorePath = storePath;
const { dir, storePath } = await createSessionStoreDir();
testState.agentsConfig = { list: [{ id: "ops", default: true }] };
testState.sessionConfig = { mainKey: "work" };
const sessionId = "sess-legacy-main";
@@ -533,9 +534,7 @@ describe("gateway server sessions", () => {
});
test("sessions.resolve and mutators clean legacy main-alias ghost keys", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-cleanup-alias-"));
const storePath = path.join(dir, "sessions.json");
testState.sessionStorePath = storePath;
const { dir, storePath } = await createSessionStoreDir();
testState.agentsConfig = { list: [{ id: "ops", default: true }] };
testState.sessionConfig = { mainKey: "work" };
const sessionId = "sess-alias-cleanup";
@@ -1044,9 +1043,7 @@ describe("gateway server sessions", () => {
});
test("webchat clients cannot patch or delete sessions", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-webchat-"));
const storePath = path.join(dir, "sessions.json");
testState.sessionStorePath = storePath;
await createSessionStoreDir();
await writeSessionStore({
entries: {

View File

@@ -1,4 +1,12 @@
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
loadOrCreateDeviceIdentity,
publicKeyRawBase64UrlFromPem,
signDevicePayload,
} from "../infra/device-identity.js";
import { buildDeviceAuthPayload } from "./device-auth.js";
import {
connectOk,
installGatewayTestHooks,
@@ -10,21 +18,16 @@ import { withServer } from "./test-with-server.js";
installGatewayTestHooks({ scope: "suite" });
type GatewaySocket = Parameters<Parameters<typeof withServer>[0]>[0];
const TALK_CONFIG_DEVICE_PATH = path.join(
os.tmpdir(),
`openclaw-talk-config-device-${process.pid}.json`,
);
const TALK_CONFIG_DEVICE = loadOrCreateDeviceIdentity(TALK_CONFIG_DEVICE_PATH);
async function createFreshOperatorDevice(scopes: string[], nonce: string) {
const { randomUUID } = await import("node:crypto");
const { tmpdir } = await import("node:os");
const { join } = await import("node:path");
const { buildDeviceAuthPayload } = await import("./device-auth.js");
const { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem, signDevicePayload } =
await import("../infra/device-identity.js");
const identity = loadOrCreateDeviceIdentity(
join(tmpdir(), `openclaw-talk-config-${randomUUID()}.json`),
);
const signedAtMs = Date.now();
const payload = buildDeviceAuthPayload({
deviceId: identity.deviceId,
deviceId: TALK_CONFIG_DEVICE.deviceId,
clientId: "test",
clientMode: "test",
role: "operator",
@@ -35,9 +38,9 @@ async function createFreshOperatorDevice(scopes: string[], nonce: string) {
});
return {
id: identity.deviceId,
publicKey: publicKeyRawBase64UrlFromPem(identity.publicKeyPem),
signature: signDevicePayload(identity.privateKeyPem, payload),
id: TALK_CONFIG_DEVICE.deviceId,
publicKey: publicKeyRawBase64UrlFromPem(TALK_CONFIG_DEVICE.publicKeyPem),
signature: signDevicePayload(TALK_CONFIG_DEVICE.privateKeyPem, payload),
signedAt: signedAtMs,
nonce,
};