test: cover sdk gateway integration

This commit is contained in:
Peter Steinberger
2026-04-30 01:39:49 +01:00
parent e9fcbe1533
commit d86c5775b8
3 changed files with 193 additions and 2 deletions

View File

@@ -1,7 +1,10 @@
import type { AddressInfo } from "node:net";
import net from "node:net";
import { afterEach, describe, expect, it } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import { WebSocketServer, type RawData, type WebSocket } from "ws";
import { __testing as agentJobTesting } from "../../../src/gateway/server-methods/agent-job.js";
import { installGatewayTestHooks, startServer } from "../../../src/gateway/test-helpers.js";
import { emitAgentEvent, registerAgentRunContext } from "../../../src/infra/agent-events.js";
import { GatewayClientTransport, OpenClaw } from "./index.js";
type JsonObject = Record<string, unknown>;
@@ -276,3 +279,80 @@ describe("OpenClaw SDK websocket e2e", () => {
}
});
});
describe("OpenClaw SDK real Gateway e2e", () => {
installGatewayTestHooks({ scope: "test" });
it("consumes real Gateway events, waits, and calls current model status RPCs", async () => {
const token = "sdk-real-gateway-token";
const started = await startServer(token, { controlUiEnabled: false });
const transport = new GatewayClientTransport({
url: `ws://127.0.0.1:${started.port}`,
token,
deviceIdentity: null,
requestTimeoutMs: 2_000,
});
const oc = new OpenClaw({ transport });
const runId = "sdk-real-gateway-run";
try {
await oc.connect();
await expect(oc.models.status({ probe: false })).resolves.toMatchObject({
providers: expect.any(Array),
});
registerAgentRunContext(runId, {
sessionKey: "agent:main:dashboard:sdk-real-gateway",
verboseLevel: "off",
});
const run = await oc.runs.get(runId);
const waitPromise = run.wait({ timeoutMs: 1_000 });
await vi.waitFor(() => expect(agentJobTesting.getWaiterCount(runId)).toBeGreaterThan(0));
emitAgentEvent({
runId,
stream: "lifecycle",
data: { phase: "start", startedAt: 111 },
});
emitAgentEvent({
runId,
stream: "assistant",
data: { delta: "hello from real gateway" },
});
emitAgentEvent({
runId,
stream: "lifecycle",
data: { phase: "end", endedAt: 222 },
});
await expect(waitPromise).resolves.toMatchObject({
runId,
status: "completed",
startedAt: 111,
endedAt: 222,
});
const seen: string[] = [];
const sessionKeys: Array<string | undefined> = [];
for await (const event of run.events()) {
seen.push(event.type);
sessionKeys.push(event.sessionKey);
if (event.type === "run.completed") {
break;
}
}
expect(seen).toEqual(["run.started", "assistant.delta", "run.completed"]);
expect(sessionKeys).toEqual([
"agent:main:dashboard:sdk-real-gateway",
"agent:main:dashboard:sdk-real-gateway",
"agent:main:dashboard:sdk-real-gateway",
]);
} finally {
await oc.close();
await started.server.close();
started.envSnapshot.restore();
}
});
});

View File

@@ -30,7 +30,7 @@ class FakeTransport implements OpenClawTransport {
this.calls.push({ method, params, options });
const response = this.responses[method];
if (typeof response === "function") {
return (await response(params, options, this)) as T;
return (await (response as FakeResponseHandler)(params, options, this)) as T;
}
return response as T;
}

View File

@@ -0,0 +1,111 @@
import { spawn } from "node:child_process";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
type CommandResult = {
stdout: string;
stderr: string;
};
const COMMAND_TIMEOUT_MS = 120_000;
const tempDirs: string[] = [];
function runCommand(
command: string,
args: string[],
options: { cwd: string; timeoutMs?: number },
): Promise<CommandResult> {
return new Promise((resolve, reject) => {
const stdout: string[] = [];
const stderr: string[] = [];
const child = spawn(command, args, {
cwd: options.cwd,
env: { ...process.env, npm_config_audit: "false", npm_config_fund: "false" },
stdio: ["ignore", "pipe", "pipe"],
});
const timer = setTimeout(() => {
child.kill("SIGKILL");
reject(
new Error(
`command timed out after ${options.timeoutMs ?? COMMAND_TIMEOUT_MS}ms: ${[
command,
...args,
].join(" ")}`,
),
);
}, options.timeoutMs ?? COMMAND_TIMEOUT_MS);
child.stdout?.setEncoding("utf8");
child.stderr?.setEncoding("utf8");
child.stdout?.on("data", (chunk) => stdout.push(String(chunk)));
child.stderr?.on("data", (chunk) => stderr.push(String(chunk)));
child.once("error", (error) => {
clearTimeout(timer);
reject(error);
});
child.once("exit", (code, signal) => {
clearTimeout(timer);
const result = { stdout: stdout.join(""), stderr: stderr.join("") };
if (code === 0) {
resolve(result);
return;
}
reject(
new Error(
`command failed (${String(code ?? signal)}): ${[command, ...args].join(" ")}\n` +
`--- stdout ---\n${result.stdout}\n--- stderr ---\n${result.stderr}`,
),
);
});
});
}
describe("OpenClaw SDK package e2e", () => {
afterEach(async () => {
await Promise.all(
tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })),
);
});
it("packs and imports from an external temp consumer", async () => {
const repoRoot = process.cwd();
const packageRoot = path.join(repoRoot, "packages", "sdk");
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sdk-consumer-"));
tempDirs.push(tempDir);
await runCommand("pnpm", ["--filter", "@openclaw/sdk", "build"], {
cwd: repoRoot,
timeoutMs: 180_000,
});
await runCommand("pnpm", ["pack", "--pack-destination", tempDir], {
cwd: packageRoot,
});
const packedFiles = (await fs.readdir(tempDir)).filter((file) => file.endsWith(".tgz"));
expect(packedFiles).toHaveLength(1);
const tarball = path.join(tempDir, packedFiles[0] ?? "");
await fs.writeFile(
path.join(tempDir, "package.json"),
JSON.stringify({ private: true, type: "module" }),
);
await runCommand("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund", tarball], {
cwd: tempDir,
});
const importScript = `
import { GatewayClientTransport, OpenClaw, normalizeGatewayEvent } from "@openclaw/sdk";
if (typeof GatewayClientTransport !== "function") throw new Error("missing transport export");
if (typeof OpenClaw !== "function") throw new Error("missing client export");
const event = normalizeGatewayEvent({
event: "agent",
payload: { runId: "pack-smoke", stream: "lifecycle", data: { phase: "start" } }
});
if (event.type !== "run.started") throw new Error("unexpected event normalization");
`;
await runCommand(process.execPath, ["--input-type=module", "-e", importScript], {
cwd: tempDir,
});
});
});