mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-01 04:11:03 +00:00
test: stabilize runner and acp mocks
- reuse the shared cli-runner harness in claude runner tests - make ACP session metadata and startup tests use stable static mocks
This commit is contained in:
@@ -10,25 +10,20 @@ const hoisted = vi.hoisted(() => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../config/sessions.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../../config/sessions.js")>(
|
||||
"../../config/sessions.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
resolveAllAgentSessionStoreTargets: (cfg: OpenClawConfig, opts: unknown) =>
|
||||
hoisted.resolveAllAgentSessionStoreTargetsMock(cfg, opts),
|
||||
loadSessionStore: (storePath: string) => hoisted.loadSessionStoreMock(storePath),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../config/sessions.js", () => ({
|
||||
loadSessionStore: (storePath: string) => hoisted.loadSessionStoreMock(storePath),
|
||||
resolveAllAgentSessionStoreTargets: (cfg: OpenClawConfig, opts: unknown) =>
|
||||
hoisted.resolveAllAgentSessionStoreTargetsMock(cfg, opts),
|
||||
resolveStorePath: vi.fn(() => "/tmp/sessions.json"),
|
||||
updateSessionStore: vi.fn(),
|
||||
}));
|
||||
let listAcpSessionEntries: typeof import("./session-meta.js").listAcpSessionEntries;
|
||||
|
||||
describe("listAcpSessionEntries", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
({ listAcpSessionEntries } = await import("./session-meta.js"));
|
||||
vi.clearAllMocks();
|
||||
({ listAcpSessionEntries } = await import("./session-meta.js"));
|
||||
});
|
||||
|
||||
it("reads ACP sessions from resolved configured store targets", async () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ type GatewayClientAuth = {
|
||||
};
|
||||
type ResolveGatewayConnectionAuth = (params: unknown) => Promise<GatewayClientAuth>;
|
||||
|
||||
const mockState = {
|
||||
const mockState = vi.hoisted(() => ({
|
||||
gateways: [] as MockGatewayClient[],
|
||||
gatewayAuth: [] as GatewayClientAuth[],
|
||||
agentSideConnectionCtor: vi.fn(),
|
||||
@@ -21,7 +21,7 @@ const mockState = {
|
||||
token: undefined,
|
||||
password: undefined,
|
||||
})),
|
||||
};
|
||||
}));
|
||||
|
||||
class MockGatewayClient {
|
||||
private callbacks: GatewayClientCallbacks;
|
||||
@@ -63,13 +63,11 @@ vi.mock("../config/config.js", () => ({
|
||||
mode: "local",
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/auth.js", () => ({
|
||||
resolveGatewayAuth: () => ({}),
|
||||
resolveGatewayPort: vi.fn(() => 18_789),
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/call.js", () => ({
|
||||
callGateway: vi.fn(),
|
||||
buildGatewayConnectionDetails: ({ url }: { url?: string }) => {
|
||||
if (typeof url === "string" && url.trim().length > 0) {
|
||||
return {
|
||||
@@ -92,6 +90,10 @@ vi.mock("../gateway/client.js", () => ({
|
||||
GatewayClient: MockGatewayClient,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/is-main.js", () => ({
|
||||
isMainModule: () => false,
|
||||
}));
|
||||
|
||||
vi.mock("./translator.js", () => ({
|
||||
AcpGatewayAgent: class {
|
||||
start(): void {
|
||||
@@ -149,7 +151,7 @@ describe("serveAcpGateway startup", () => {
|
||||
({ serveAcpGateway } = await import("./server.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
mockState.gateways.length = 0;
|
||||
mockState.gatewayAuth.length = 0;
|
||||
mockState.agentSideConnectionCtor.mockReset();
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
spawn: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../process/supervisor/index.js", () => ({
|
||||
getProcessSupervisor: () => ({
|
||||
spawn: (...args: unknown[]) => mocks.spawn(...args),
|
||||
cancel: vi.fn(),
|
||||
cancelScope: vi.fn(),
|
||||
reconcileOrphans: async () => {},
|
||||
getRecord: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
import {
|
||||
setupClaudeCliRunnerTestModule,
|
||||
supervisorSpawnMock,
|
||||
} from "./cli-runner.test-support.js";
|
||||
|
||||
function createDeferred<T>() {
|
||||
let resolve: (value: T) => void = () => {};
|
||||
@@ -52,8 +42,7 @@ function createManagedRun(
|
||||
let runClaudeCliAgent: typeof import("./claude-cli-runner.js").runClaudeCliAgent;
|
||||
|
||||
async function loadFreshClaudeCliRunnerModuleForTest() {
|
||||
vi.resetModules();
|
||||
({ runClaudeCliAgent } = await import("./claude-cli-runner.js"));
|
||||
runClaudeCliAgent = await setupClaudeCliRunnerTestModule();
|
||||
}
|
||||
|
||||
function successExit(payload: { message: string; session_id: string }) {
|
||||
@@ -81,11 +70,11 @@ async function waitForCalls(mockFn: { mock: { calls: unknown[][] } }, count: num
|
||||
describe("runClaudeCliAgent", () => {
|
||||
beforeEach(async () => {
|
||||
await loadFreshClaudeCliRunnerModuleForTest();
|
||||
mocks.spawn.mockClear();
|
||||
supervisorSpawnMock.mockClear();
|
||||
});
|
||||
|
||||
it("starts a new session with --session-id when none is provided", async () => {
|
||||
mocks.spawn.mockResolvedValueOnce(
|
||||
supervisorSpawnMock.mockResolvedValueOnce(
|
||||
createManagedRun(Promise.resolve(successExit({ message: "ok", session_id: "sid-1" }))),
|
||||
);
|
||||
|
||||
@@ -99,16 +88,16 @@ describe("runClaudeCliAgent", () => {
|
||||
runId: "run-1",
|
||||
});
|
||||
|
||||
expect(mocks.spawn).toHaveBeenCalledTimes(1);
|
||||
const spawnInput = mocks.spawn.mock.calls[0]?.[0] as { argv: string[]; mode: string };
|
||||
expect(supervisorSpawnMock).toHaveBeenCalledTimes(1);
|
||||
const spawnInput = supervisorSpawnMock.mock.calls[0]?.[0] as { argv: string[]; mode: string };
|
||||
expect(spawnInput.mode).toBe("child");
|
||||
expect(spawnInput.argv).toContain("claude");
|
||||
expect(spawnInput.argv).toContain("--session-id");
|
||||
expect(spawnInput.argv).toContain("hi");
|
||||
});
|
||||
|
||||
it("uses --resume when a claude session id is provided", async () => {
|
||||
mocks.spawn.mockResolvedValueOnce(
|
||||
it("starts fresh when only a legacy claude session id is provided", async () => {
|
||||
supervisorSpawnMock.mockResolvedValueOnce(
|
||||
createManagedRun(Promise.resolve(successExit({ message: "ok", session_id: "sid-2" }))),
|
||||
);
|
||||
|
||||
@@ -123,11 +112,11 @@ describe("runClaudeCliAgent", () => {
|
||||
claudeSessionId: "c9d7b831-1c31-4d22-80b9-1e50ca207d4b",
|
||||
});
|
||||
|
||||
expect(mocks.spawn).toHaveBeenCalledTimes(1);
|
||||
const spawnInput = mocks.spawn.mock.calls[0]?.[0] as { argv: string[] };
|
||||
expect(spawnInput.argv).toContain("--resume");
|
||||
expect(spawnInput.argv).toContain("c9d7b831-1c31-4d22-80b9-1e50ca207d4b");
|
||||
expect(spawnInput.argv).not.toContain("--session-id");
|
||||
expect(supervisorSpawnMock).toHaveBeenCalledTimes(1);
|
||||
const spawnInput = supervisorSpawnMock.mock.calls[0]?.[0] as { argv: string[] };
|
||||
expect(spawnInput.argv).not.toContain("--resume");
|
||||
expect(spawnInput.argv).not.toContain("c9d7b831-1c31-4d22-80b9-1e50ca207d4b");
|
||||
expect(spawnInput.argv).toContain("--session-id");
|
||||
expect(spawnInput.argv).toContain("hi");
|
||||
});
|
||||
|
||||
@@ -135,7 +124,7 @@ describe("runClaudeCliAgent", () => {
|
||||
const firstDeferred = createDeferred<ReturnType<typeof successExit>>();
|
||||
const secondDeferred = createDeferred<ReturnType<typeof successExit>>();
|
||||
|
||||
mocks.spawn
|
||||
supervisorSpawnMock
|
||||
.mockResolvedValueOnce(createManagedRun(firstDeferred.promise))
|
||||
.mockResolvedValueOnce(createManagedRun(secondDeferred.promise));
|
||||
|
||||
@@ -159,11 +148,11 @@ describe("runClaudeCliAgent", () => {
|
||||
runId: "run-2",
|
||||
});
|
||||
|
||||
await waitForCalls(mocks.spawn, 1);
|
||||
await waitForCalls(supervisorSpawnMock, 1);
|
||||
|
||||
firstDeferred.resolve(successExit({ message: "ok", session_id: "sid-1" }));
|
||||
|
||||
await waitForCalls(mocks.spawn, 2);
|
||||
await waitForCalls(supervisorSpawnMock, 2);
|
||||
|
||||
secondDeferred.resolve(successExit({ message: "ok", session_id: "sid-2" }));
|
||||
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { beforeEach, vi } from "vitest";
|
||||
import { buildAnthropicCliBackend } from "../../extensions/anthropic/test-api.js";
|
||||
import { buildGoogleGeminiCliBackend } from "../../extensions/google/test-api.js";
|
||||
import { buildOpenAICodexCliBackend } from "../../extensions/openai/test-api.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { createEmptyPluginRegistry } from "../plugins/registry.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import type { CliBackendPlugin } from "../plugins/types.js";
|
||||
import { loadBundledPluginTestApiSync } from "../test-utils/bundled-plugin-public-surface.js";
|
||||
import { mergeMockedModule } from "../test-utils/vitest-module-mocks.js";
|
||||
import { setCliRunnerExecuteTestDeps } from "./cli-runner/execute.js";
|
||||
import { setCliRunnerPrepareTestDeps } from "./cli-runner/prepare.js";
|
||||
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
|
||||
import type { WorkspaceBootstrapFile } from "./workspace.js";
|
||||
|
||||
const { buildAnthropicCliBackend } = loadBundledPluginTestApiSync<{
|
||||
buildAnthropicCliBackend: () => CliBackendPlugin;
|
||||
}>("anthropic");
|
||||
const { buildGoogleGeminiCliBackend } = loadBundledPluginTestApiSync<{
|
||||
buildGoogleGeminiCliBackend: () => CliBackendPlugin;
|
||||
}>("google");
|
||||
const { buildOpenAICodexCliBackend } = loadBundledPluginTestApiSync<{
|
||||
buildOpenAICodexCliBackend: () => CliBackendPlugin;
|
||||
}>("openai");
|
||||
|
||||
export const supervisorSpawnMock = vi.fn();
|
||||
export const enqueueSystemEventMock = vi.fn();
|
||||
export const requestHeartbeatNowMock = vi.fn();
|
||||
@@ -39,7 +31,7 @@ const hoisted = vi.hoisted(() => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../process/supervisor/index.js", () => ({
|
||||
setCliRunnerExecuteTestDeps({
|
||||
getProcessSupervisor: () => ({
|
||||
spawn: (...args: unknown[]) => supervisorSpawnMock(...args),
|
||||
cancel: vi.fn(),
|
||||
@@ -47,25 +39,14 @@ vi.mock("../process/supervisor/index.js", () => ({
|
||||
reconcileOrphans: vi.fn(),
|
||||
getRecord: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/system-events.js", () => ({
|
||||
enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/heartbeat-wake.js", async (importOriginal) => {
|
||||
return await mergeMockedModule(
|
||||
await importOriginal<typeof import("../infra/heartbeat-wake.js")>(),
|
||||
() => ({
|
||||
requestHeartbeatNow: (...args: unknown[]) => requestHeartbeatNowMock(...args),
|
||||
}),
|
||||
);
|
||||
requestHeartbeatNow: (...args: unknown[]) => requestHeartbeatNowMock(...args),
|
||||
});
|
||||
|
||||
vi.mock("./bootstrap-files.js", () => ({
|
||||
setCliRunnerPrepareTestDeps({
|
||||
makeBootstrapWarn: () => () => {},
|
||||
resolveBootstrapContextForRun: hoisted.resolveBootstrapContextForRunMock,
|
||||
}));
|
||||
});
|
||||
|
||||
type MockRunExit = {
|
||||
reason:
|
||||
@@ -160,37 +141,18 @@ export async function setupCliRunnerTestModule() {
|
||||
bootstrapFiles: [],
|
||||
contextFiles: [],
|
||||
});
|
||||
|
||||
vi.resetModules();
|
||||
vi.doMock("../process/supervisor/index.js", () => ({
|
||||
getProcessSupervisor: () => ({
|
||||
spawn: (...args: unknown[]) => supervisorSpawnMock(...args),
|
||||
cancel: vi.fn(),
|
||||
cancelScope: vi.fn(),
|
||||
reconcileOrphans: vi.fn(),
|
||||
getRecord: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
vi.doMock("../infra/system-events.js", () => ({
|
||||
enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args),
|
||||
}));
|
||||
vi.doMock("../infra/heartbeat-wake.js", async () => {
|
||||
return await mergeMockedModule(
|
||||
await vi.importActual<typeof import("../infra/heartbeat-wake.js")>(
|
||||
"../infra/heartbeat-wake.js",
|
||||
),
|
||||
() => ({
|
||||
requestHeartbeatNow: (...args: unknown[]) => requestHeartbeatNowMock(...args),
|
||||
}),
|
||||
);
|
||||
});
|
||||
vi.doMock("./bootstrap-files.js", () => ({
|
||||
makeBootstrapWarn: () => () => {},
|
||||
resolveBootstrapContextForRun: hoisted.resolveBootstrapContextForRunMock,
|
||||
}));
|
||||
return (await import("./cli-runner.js")).runCliAgent;
|
||||
}
|
||||
|
||||
export async function setupClaudeCliRunnerTestModule() {
|
||||
const runCliAgent = await setupCliRunnerTestModule();
|
||||
return (params: Parameters<typeof import("./claude-cli-runner.js").runClaudeCliAgent>[0]) =>
|
||||
runCliAgent({
|
||||
...params,
|
||||
provider: params.provider ?? "claude-cli",
|
||||
});
|
||||
}
|
||||
|
||||
export function stubBootstrapContext(params: {
|
||||
bootstrapFiles: WorkspaceBootstrapFile[];
|
||||
contextFiles: EmbeddedContextFile[];
|
||||
|
||||
Reference in New Issue
Block a user