mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:50:43 +00:00
test: cover sdk gateway integration
This commit is contained in:
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
111
packages/sdk/src/package.e2e.test.ts
Normal file
111
packages/sdk/src/package.e2e.test.ts
Normal 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,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user