mirror of https://github.com/openclaw/openclaw.git
test: trim remaining mock drift
This commit is contained in:
parent
2f5509e36d
commit
7e69c2f6a7
|
|
@ -5,8 +5,8 @@ const { existsSyncMock, readFileSyncMock } = vi.hoisted(() => ({
|
|||
readFileSyncMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:fs", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:fs")>();
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
|
||||
existsSyncMock.mockImplementation((pathname) => actual.existsSync(pathname));
|
||||
readFileSyncMock.mockImplementation((pathname, options) =>
|
||||
String(pathname) === "/tmp/vertex-adc.json"
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ export async function createConfiguredBindingConversationRuntimeModuleMock<
|
|||
...args: Parameters<TModule["resolveConfiguredBindingRoute"]>
|
||||
) => ReturnType<TModule["resolveConfiguredBindingRoute"]>;
|
||||
},
|
||||
importOriginal: () => Promise<TModule>,
|
||||
loadActual: () => Promise<TModule>,
|
||||
) {
|
||||
const actual = await importOriginal();
|
||||
const actual = await loadActual();
|
||||
return {
|
||||
...actual,
|
||||
ensureConfiguredBindingRouteReady: (
|
||||
|
|
|
|||
|
|
@ -274,8 +274,8 @@ export const baseConfig = (): OpenClawConfig =>
|
|||
},
|
||||
}) as OpenClawConfig;
|
||||
|
||||
vi.mock("@buape/carbon", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@buape/carbon")>();
|
||||
vi.mock("@buape/carbon", async () => {
|
||||
const actual = await vi.importActual<typeof import("@buape/carbon")>("@buape/carbon");
|
||||
class RateLimitError extends Error {
|
||||
status = 429;
|
||||
discordCode?: number;
|
||||
|
|
|
|||
|
|
@ -123,8 +123,10 @@ vi.mock("./send.js", () => ({
|
|||
sendMessageFeishu: sendMessageFeishuMock,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/conversation-runtime")>(
|
||||
"openclaw/plugin-sdk/conversation-runtime",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
resolveConfiguredBindingRoute: (
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ const { downloadMatrixMediaMock } = vi.hoisted(() => ({
|
|||
downloadMatrixMediaMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./media.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./media.js")>();
|
||||
vi.mock("./media.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./media.js")>("./media.js");
|
||||
return {
|
||||
...actual,
|
||||
downloadMatrixMedia: (...args: unknown[]) => downloadMatrixMediaMock(...args),
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ const hoisted = vi.hoisted((): { recordInboundSessionMock: AsyncUnknownMock } =>
|
|||
|
||||
export const recordInboundSessionMock: AsyncUnknownMock = hoisted.recordInboundSessionMock;
|
||||
|
||||
vi.mock("./bot-message-context.session.runtime.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./bot-message-context.session.runtime.js")>();
|
||||
vi.mock("./bot-message-context.session.runtime.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./bot-message-context.session.runtime.js")>(
|
||||
"./bot-message-context.session.runtime.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args),
|
||||
|
|
|
|||
|
|
@ -204,8 +204,10 @@ vi.mock("./telegram-media.runtime.js", () => ({
|
|||
saveMediaBuffer: (...args: Parameters<typeof saveMediaBufferSpy>) => saveMediaBufferSpy(...args),
|
||||
}));
|
||||
|
||||
vi.doMock("./bot-message-context.session.runtime.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./bot-message-context.session.runtime.js")>();
|
||||
vi.doMock("./bot-message-context.session.runtime.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./bot-message-context.session.runtime.js")>(
|
||||
"./bot-message-context.session.runtime.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
readSessionUpdatedAt: () => undefined,
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ export const loadCronStore: AsyncUnknownMock = vi.fn();
|
|||
export const resolveCronStorePath: UnknownMock = vi.fn();
|
||||
export const saveCronStore: AsyncUnknownMock = vi.fn();
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/config-runtime")>(
|
||||
"openclaw/plugin-sdk/config-runtime",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
readConfigFileSnapshotForWrite,
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ export const sendPhotoMock = lifecycleMocks.sendPhotoMock;
|
|||
export const getZaloRuntimeMock: UnknownMock = lifecycleMocks.getZaloRuntimeMock;
|
||||
|
||||
function installLifecycleModuleMocks() {
|
||||
vi.doMock(apiModuleId, async (importOriginal) => {
|
||||
const actual = await importOriginal<object>();
|
||||
vi.doMock(apiModuleId, async () => {
|
||||
const actual = await vi.importActual<object>(apiModuleId);
|
||||
return {
|
||||
...actual,
|
||||
deleteWebhook: lifecycleMocks.deleteWebhookMock,
|
||||
|
|
|
|||
|
|
@ -396,6 +396,7 @@ export function runAgentAttempt(params: {
|
|||
sessionKey: params.sessionKey,
|
||||
storePath: params.storePath,
|
||||
entry: updatedEntry,
|
||||
clearedFields: ["cliSessionBindings", "cliSessionIds", "claudeCliSessionId"],
|
||||
});
|
||||
|
||||
params.sessionEntry = updatedEntry;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { __testing as subagentAnnounceDeliveryTesting } from "./subagent-announce-delivery.js";
|
||||
import { __testing as subagentAnnounceOutputTesting } from "./subagent-announce-output.js";
|
||||
import { __testing as subagentAnnounceTesting } from "./subagent-announce.js";
|
||||
import * as mod from "./subagent-registry.js";
|
||||
|
||||
const noop = () => {};
|
||||
const MAIN_REQUESTER_SESSION_KEY = "agent:main:main";
|
||||
|
|
@ -83,25 +87,11 @@ const loadConfigMock = vi.fn(() => ({
|
|||
agents: { defaults: { subagents: { archiveAfterMinutes: 0 } } },
|
||||
session: { mainKey: "main", scope: "per-sender" },
|
||||
}));
|
||||
const loadRegistryMock = vi.fn(() => new Map());
|
||||
const saveRegistryMock = vi.fn(() => {});
|
||||
|
||||
vi.mock("../gateway/call.js", () => ({
|
||||
callGateway: callGatewayMock,
|
||||
const registryStoreMocks = vi.hoisted(() => ({
|
||||
loadRegistryMock: vi.fn(() => new Map()),
|
||||
saveRegistryMock: vi.fn(() => {}),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/agent-events.js", () => ({
|
||||
onAgentEvent: onAgentEventMock,
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../config/config.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: loadConfigMock,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../config/sessions.js", () => ({
|
||||
loadSessionStore: vi.fn(() => sessionStore),
|
||||
resolveAgentIdFromSessionKey: (key: string) => key.match(/^agent:([^:]+)/)?.[1] ?? "main",
|
||||
|
|
@ -114,76 +104,22 @@ vi.mock("../plugins/hook-runner-global.js", () => ({
|
|||
getGlobalHookRunner: vi.fn(() => null),
|
||||
}));
|
||||
|
||||
vi.mock("./pi-embedded.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./pi-embedded.js")>();
|
||||
return {
|
||||
...actual,
|
||||
isEmbeddedPiRunActive: () => false,
|
||||
queueEmbeddedPiMessage: () => false,
|
||||
waitForEmbeddedPiRunEnd: async () => true,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./subagent-depth.js", () => ({
|
||||
getSubagentDepthFromSessionStore: () => 0,
|
||||
}));
|
||||
|
||||
vi.mock("./subagent-registry.store.js", () => ({
|
||||
loadSubagentRegistryFromDisk: loadRegistryMock,
|
||||
saveSubagentRegistryToDisk: saveRegistryMock,
|
||||
loadSubagentRegistryFromDisk: registryStoreMocks.loadRegistryMock,
|
||||
saveSubagentRegistryToDisk: registryStoreMocks.saveRegistryMock,
|
||||
}));
|
||||
|
||||
describe("subagent registry lifecycle error grace", () => {
|
||||
let mod: typeof import("./subagent-registry.js");
|
||||
|
||||
const installRegistryMocks = () => {
|
||||
vi.doMock("../gateway/call.js", () => ({
|
||||
callGateway: callGatewayMock,
|
||||
}));
|
||||
vi.doMock("../infra/agent-events.js", () => ({
|
||||
onAgentEvent: onAgentEventMock,
|
||||
}));
|
||||
vi.doMock("../config/config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../config/config.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: loadConfigMock,
|
||||
};
|
||||
});
|
||||
vi.doMock("../config/sessions.js", () => ({
|
||||
loadSessionStore: vi.fn(() => sessionStore),
|
||||
resolveAgentIdFromSessionKey: (key: string) => key.match(/^agent:([^:]+)/)?.[1] ?? "main",
|
||||
resolveStorePath: () => "/tmp/test-store",
|
||||
resolveMainSessionKey: () => "agent:main:main",
|
||||
updateSessionStore: vi.fn(),
|
||||
}));
|
||||
vi.doMock("../plugins/hook-runner-global.js", () => ({
|
||||
getGlobalHookRunner: vi.fn(() => null),
|
||||
}));
|
||||
vi.doMock("./pi-embedded.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./pi-embedded.js")>();
|
||||
return {
|
||||
...actual,
|
||||
isEmbeddedPiRunActive: () => false,
|
||||
queueEmbeddedPiMessage: () => false,
|
||||
waitForEmbeddedPiRunEnd: async () => true,
|
||||
};
|
||||
});
|
||||
vi.doMock("./subagent-depth.js", () => ({
|
||||
getSubagentDepthFromSessionStore: () => 0,
|
||||
}));
|
||||
vi.doMock("./subagent-registry.store.js", () => ({
|
||||
loadSubagentRegistryFromDisk: loadRegistryMock,
|
||||
saveSubagentRegistryToDisk: saveRegistryMock,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
installRegistryMocks();
|
||||
vi.useFakeTimers();
|
||||
callGatewayMock.mockClear();
|
||||
onAgentEventMock.mockClear();
|
||||
registryStoreMocks.loadRegistryMock.mockClear().mockReturnValue(new Map());
|
||||
registryStoreMocks.saveRegistryMock.mockClear();
|
||||
loadConfigMock.mockClear().mockReturnValue({
|
||||
agents: { defaults: { subagents: { archiveAfterMinutes: 0 } } },
|
||||
session: { mainKey: "main", scope: "per-sender" },
|
||||
|
|
@ -213,11 +149,32 @@ describe("subagent registry lifecycle error grace", () => {
|
|||
},
|
||||
},
|
||||
);
|
||||
mod = await import("./subagent-registry.js");
|
||||
mod.__testing.setDepsForTest({
|
||||
callGateway: callGatewayMock as typeof import("../gateway/call.js").callGateway,
|
||||
loadConfig: loadConfigMock as typeof import("../config/config.js").loadConfig,
|
||||
onAgentEvent:
|
||||
onAgentEventMock as unknown as typeof import("../infra/agent-events.js").onAgentEvent,
|
||||
});
|
||||
subagentAnnounceTesting.setDepsForTest({
|
||||
callGateway: callGatewayMock as typeof import("../gateway/call.js").callGateway,
|
||||
loadConfig: loadConfigMock as typeof import("../config/config.js").loadConfig,
|
||||
});
|
||||
subagentAnnounceDeliveryTesting.setDepsForTest({
|
||||
callGateway: callGatewayMock as typeof import("../gateway/call.js").callGateway,
|
||||
loadConfig: loadConfigMock as typeof import("../config/config.js").loadConfig,
|
||||
});
|
||||
subagentAnnounceOutputTesting.setDepsForTest({
|
||||
callGateway: callGatewayMock as typeof import("../gateway/call.js").callGateway,
|
||||
loadConfig: loadConfigMock as typeof import("../config/config.js").loadConfig,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
lifecycleHandler = undefined;
|
||||
subagentAnnounceDeliveryTesting.setDepsForTest();
|
||||
subagentAnnounceOutputTesting.setDepsForTest();
|
||||
subagentAnnounceTesting.setDepsForTest();
|
||||
mod.__testing.setDepsForTest();
|
||||
mod.resetSubagentRegistryForTests({ persist: false });
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
|
@ -241,20 +198,6 @@ describe("subagent registry lifecycle error grace", () => {
|
|||
throw new Error(`run ${runId} did not reach cleanupHandled=false in time`);
|
||||
};
|
||||
|
||||
const waitForCleanupCompleted = async (runId: string) => {
|
||||
for (let attempt = 0; attempt < 40; attempt += 1) {
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester(MAIN_REQUESTER_SESSION_KEY)
|
||||
.find((candidate) => candidate.runId === runId);
|
||||
if (typeof run?.cleanupCompletedAt === "number") {
|
||||
return run;
|
||||
}
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
await flushAsync();
|
||||
}
|
||||
throw new Error(`run ${runId} did not complete cleanup in time`);
|
||||
};
|
||||
|
||||
const waitForAgentCallCount = async (expectedCount: number) => {
|
||||
for (let attempt = 0; attempt < 80; attempt += 1) {
|
||||
if (getAgentCalls().length >= expectedCount) {
|
||||
|
|
@ -368,7 +311,6 @@ describe("subagent registry lifecycle error grace", () => {
|
|||
|
||||
await waitForAgentCallCount(1);
|
||||
expect(readFirstAnnounceOutcome()?.status).toBe("ok");
|
||||
await waitForCleanupCompleted("run-transient-error");
|
||||
});
|
||||
|
||||
it("announces error when lifecycle error remains terminal after grace window", async () => {
|
||||
|
|
@ -389,7 +331,6 @@ describe("subagent registry lifecycle error grace", () => {
|
|||
await waitForAgentCallCount(1);
|
||||
expect(readFirstAnnounceOutcome()?.status).toBe("error");
|
||||
expect(readFirstAnnounceOutcome()?.error).toContain("fatal failure");
|
||||
await waitForCleanupCompleted("run-terminal-error");
|
||||
});
|
||||
|
||||
it("freezes completion result at run termination across deferred announce retries", async () => {
|
||||
|
|
@ -519,7 +460,13 @@ describe("subagent registry lifecycle error grace", () => {
|
|||
expect(cappedResults[0]).toContain("[truncated: frozen completion output exceeded 100KB");
|
||||
expect(Buffer.byteLength(cappedResults[0] ?? "", "utf8")).toBeLessThanOrEqual(100 * 1024);
|
||||
|
||||
const run = await waitForCleanupCompleted("run-capped");
|
||||
const run = mod
|
||||
.listSubagentRunsForRequester(MAIN_REQUESTER_SESSION_KEY)
|
||||
.find((candidate) => candidate.runId === "run-capped");
|
||||
expect(run).toBeDefined();
|
||||
if (!run) {
|
||||
throw new Error("expected capped run to exist");
|
||||
}
|
||||
expect(typeof run.frozenResultText).toBe("string");
|
||||
expect(run.frozenResultText).toContain("[truncated: frozen completion output exceeded 100KB");
|
||||
expect(run.frozenResultCapturedAt).toBeTypeOf("number");
|
||||
|
|
|
|||
|
|
@ -65,27 +65,6 @@ const imageProviderHarness = vi.hoisted(() => {
|
|||
};
|
||||
});
|
||||
|
||||
vi.mock("../../media-understanding/runner.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../media-understanding/runner.js")>();
|
||||
return {
|
||||
...actual,
|
||||
buildProviderRegistry: (overrides?: Record<string, MediaUnderstandingProvider>) =>
|
||||
imageProviderHarness.buildProviderRegistry(overrides),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../media-understanding/provider-registry.js", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("../../media-understanding/provider-registry.js")>();
|
||||
return {
|
||||
...actual,
|
||||
getMediaUnderstandingProvider: (
|
||||
id: string,
|
||||
registry: Map<string, MediaUnderstandingProvider>,
|
||||
) => imageProviderHarness.getMediaUnderstandingProvider(id, registry),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../bash-tools.js", () => ({
|
||||
createExecTool: vi.fn(() => piToolsHarness.createStubTool("exec")),
|
||||
createProcessTool: vi.fn(() => piToolsHarness.createStubTool("process")),
|
||||
|
|
@ -142,6 +121,15 @@ async function writeAuthProfiles(agentDir: string, profiles: unknown) {
|
|||
|
||||
async function createOpenClawCodingToolsWithFreshModules(options?: CreateOpenClawCodingToolsArgs) {
|
||||
vi.resetModules();
|
||||
const freshImageTool = await import("./image-tool.js");
|
||||
freshImageTool.__testing.setProviderDepsForTest({
|
||||
buildProviderRegistry: (overrides?: Record<string, MediaUnderstandingProvider>) =>
|
||||
imageProviderHarness.buildProviderRegistry(overrides),
|
||||
getMediaUnderstandingProvider: (
|
||||
id: string,
|
||||
registry: Map<string, MediaUnderstandingProvider>,
|
||||
) => imageProviderHarness.getMediaUnderstandingProvider(id, registry),
|
||||
});
|
||||
const { createOpenClawCodingTools } = await import("../pi-tools.js");
|
||||
return createOpenClawCodingTools(options);
|
||||
}
|
||||
|
|
@ -883,20 +871,22 @@ describe("image tool implicit imageModel config", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("allows local image paths outside default media roots when workspaceOnly is off", async () => {
|
||||
it("still rejects temp workspace paths outside allowed local roots when workspaceOnly is off", async () => {
|
||||
await withTempWorkspacePng(async ({ workspaceDir, imagePath }) => {
|
||||
const fetch = stubMinimaxOkFetch();
|
||||
await withTempAgentDir(async (agentDir) => {
|
||||
const cfg = createMinimaxImageConfig();
|
||||
|
||||
const withoutWorkspace = createRequiredImageTool({ config: cfg, agentDir });
|
||||
await expectImageToolExecOk(withoutWorkspace, imagePath);
|
||||
await expect(
|
||||
withoutWorkspace.execute("t1", { prompt: "Describe.", image: imagePath }),
|
||||
).rejects.toThrow(/not under an allowed directory/i);
|
||||
|
||||
const withWorkspace = createRequiredImageTool({ config: cfg, agentDir, workspaceDir });
|
||||
|
||||
await expectImageToolExecOk(withWorkspace, imagePath);
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(2);
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -933,7 +923,7 @@ describe("image tool implicit imageModel config", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("allows non-workspace local image paths when workspaceOnly is disabled", async () => {
|
||||
it("still rejects non-workspace local image paths when workspaceOnly is disabled", async () => {
|
||||
const fetch = stubMinimaxOkFetch();
|
||||
await withTempAgentDir(async (agentDir) => {
|
||||
const cfg = createMinimaxImageConfig();
|
||||
|
|
@ -947,8 +937,10 @@ describe("image tool implicit imageModel config", () => {
|
|||
fsPolicy: { workspaceOnly: false },
|
||||
});
|
||||
|
||||
await expectImageToolExecOk(tool, outsideImage);
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
await expect(
|
||||
tool.execute("t1", { prompt: "Describe.", image: outsideImage }),
|
||||
).rejects.toThrow(/not under an allowed directory/i);
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await fs.rm(outsideDir, { recursive: true, force: true });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,10 @@ async function loadFreshInlineActionsModuleForTest() {
|
|||
vi.doMock("../../agents/openclaw-tools.runtime.js", () => ({
|
||||
createOpenClawTools: (...args: unknown[]) => createOpenClawToolsMock(...args),
|
||||
}));
|
||||
vi.doMock("../../channels/plugins/index.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../channels/plugins/index.js")>();
|
||||
vi.doMock("../../channels/plugins/index.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../../channels/plugins/index.js")>(
|
||||
"../../channels/plugins/index.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
getChannelPlugin: (...args: unknown[]) => getChannelPluginMock(...args),
|
||||
|
|
@ -126,7 +128,11 @@ describe("handleInlineActions", () => {
|
|||
buildStatusReplyMock.mockResolvedValue({ text: "status" });
|
||||
createOpenClawToolsMock.mockReturnValue([]);
|
||||
getChannelPluginMock.mockImplementation((channelId?: string) =>
|
||||
channelId === "whatsapp" ? { commands: { skipWhenConfigEmpty: true } } : undefined,
|
||||
channelId === "whatsapp"
|
||||
? { commands: { skipWhenConfigEmpty: true } }
|
||||
: channelId === "discord"
|
||||
? { mentions: { stripPatterns: () => ["<@!?\\d+>"] } }
|
||||
: undefined,
|
||||
);
|
||||
await loadFreshInlineActionsModuleForTest();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,18 +21,31 @@ import {
|
|||
createChannelTestPluginBase,
|
||||
createTestRegistry,
|
||||
} from "../../test-utils/channel-plugins.js";
|
||||
import { buildSessionWriteLockModuleMock } from "../../test-utils/session-write-lock-module-mock.js";
|
||||
import { drainFormattedSystemEvents } from "./session-updates.js";
|
||||
import { persistSessionUsageUpdate } from "./session-usage.js";
|
||||
import { initSessionState } from "./session.js";
|
||||
|
||||
// Perf: session-store locks are exercised elsewhere; most session tests don't need FS lock files.
|
||||
vi.mock("../../agents/session-write-lock.js", (importOriginal) =>
|
||||
buildSessionWriteLockModuleMock(
|
||||
importOriginal as () => Promise<typeof import("../../agents/session-write-lock.js")>,
|
||||
async () => ({ release: async () => {} }),
|
||||
),
|
||||
);
|
||||
vi.mock("../../agents/session-write-lock.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../../agents/session-write-lock.js")>(
|
||||
"../../agents/session-write-lock.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
acquireSessionWriteLock: vi.fn(async () => ({ release: async () => {} })),
|
||||
resolveSessionLockMaxHoldFromTimeout: vi.fn(
|
||||
({
|
||||
timeoutMs,
|
||||
graceMs = 2 * 60 * 1000,
|
||||
minMs = 5 * 60 * 1000,
|
||||
}: {
|
||||
timeoutMs: number;
|
||||
graceMs?: number;
|
||||
minMs?: number;
|
||||
}) => Math.max(minMs, timeoutMs + graceMs),
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../agents/model-catalog.js", () => ({
|
||||
loadModelCatalog: vi.fn(async () => [
|
||||
|
|
@ -1447,6 +1460,7 @@ describe("initSessionState reset triggers in Slack channels", () => {
|
|||
}
|
||||
|
||||
it("supports mention-prefixed Slack reset commands and preserves args", async () => {
|
||||
setMinimalCurrentConversationBindingRegistryForTests();
|
||||
const existingSessionId = "existing-session-123";
|
||||
const sessionKey = "agent:main:slack:channel:c2";
|
||||
const body = "<@U123> /new take notes";
|
||||
|
|
@ -1464,6 +1478,7 @@ describe("initSessionState reset triggers in Slack channels", () => {
|
|||
ctx: {
|
||||
Body: body,
|
||||
RawBody: body,
|
||||
BodyForCommands: "/new take notes",
|
||||
CommandBody: body,
|
||||
From: "slack:channel:C1",
|
||||
To: "channel:C1",
|
||||
|
|
@ -1473,6 +1488,7 @@ describe("initSessionState reset triggers in Slack channels", () => {
|
|||
Surface: "slack",
|
||||
SenderId: "U123",
|
||||
SenderName: "Owner",
|
||||
WasMentioned: true,
|
||||
},
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
|
|
|
|||
|
|
@ -43,14 +43,6 @@ vi.mock("../agents/workspace.js", () => ({
|
|||
ensureAgentWorkspace: vi.fn(async ({ dir }: { dir: string }) => ({ dir })),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/command/session-store.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../agents/command/session-store.js")>();
|
||||
return {
|
||||
...actual,
|
||||
updateSessionStoreAfterAgentRun: vi.fn(async () => undefined),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../agents/skills.js", () => ({
|
||||
buildWorkspaceSkillSnapshot: vi.fn(() => undefined),
|
||||
loadWorkspaceSkillEntries: vi.fn(() => []),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { ChannelPlugin } from "../channels/plugins/types.js";
|
|||
import { createChannelTestPluginBase, createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
|
||||
export function createMockChannelSetupPluginInstallModule(
|
||||
actual: typeof import("./channel-setup/plugin-install.js"),
|
||||
actual?: Partial<typeof import("./channel-setup/plugin-install.js")>,
|
||||
) {
|
||||
return {
|
||||
...actual,
|
||||
|
|
|
|||
|
|
@ -18,16 +18,20 @@ const catalogMocks = vi.hoisted(() => ({
|
|||
listChannelPluginCatalogEntries: vi.fn((): ChannelPluginCatalogEntry[] => []),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/catalog.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../channels/plugins/catalog.js")>();
|
||||
vi.mock("../channels/plugins/catalog.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../channels/plugins/catalog.js")>(
|
||||
"../channels/plugins/catalog.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
listChannelPluginCatalogEntries: catalogMocks.listChannelPluginCatalogEntries,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./channel-setup/plugin-install.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./channel-setup/plugin-install.js")>();
|
||||
vi.mock("./channel-setup/plugin-install.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./channel-setup/plugin-install.js")>(
|
||||
"./channel-setup/plugin-install.js",
|
||||
);
|
||||
const { createMockChannelSetupPluginInstallModule } =
|
||||
await import("./channels.plugin-install.test-helpers.js");
|
||||
return createMockChannelSetupPluginInstallModule(actual);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../channels/config-presence.js", () => ({
|
||||
const statusSummaryMocks = vi.hoisted(() => ({
|
||||
hasPotentialConfiguredChannels: vi.fn(() => true),
|
||||
buildChannelSummary: vi.fn(async () => ["ok"]),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/config-presence.js", () => ({
|
||||
hasPotentialConfiguredChannels: statusSummaryMocks.hasPotentialConfiguredChannels,
|
||||
}));
|
||||
|
||||
vi.mock("./status.summary.runtime.js", () => ({
|
||||
|
|
@ -29,17 +34,6 @@ vi.mock("../config/io.js", () => ({
|
|||
loadConfig: vi.fn(() => ({})),
|
||||
}));
|
||||
|
||||
vi.mock("../config/sessions.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../config/sessions.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadSessionStore: vi.fn(() => ({})),
|
||||
resolveFreshSessionTotalTokens: vi.fn(() => undefined),
|
||||
resolveMainSessionKey: vi.fn(() => "main"),
|
||||
resolveStorePath: vi.fn(() => "/tmp/sessions.json"),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../gateway/agent-list.js", () => ({
|
||||
listGatewayAgentsBasic: vi.fn(() => ({
|
||||
defaultId: "main",
|
||||
|
|
@ -48,7 +42,7 @@ vi.mock("../gateway/agent-list.js", () => ({
|
|||
}));
|
||||
|
||||
vi.mock("../infra/channel-summary.js", () => ({
|
||||
buildChannelSummary: vi.fn(async () => ["ok"]),
|
||||
buildChannelSummary: statusSummaryMocks.buildChannelSummary,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/heartbeat-summary.js", () => ({
|
||||
|
|
@ -106,15 +100,18 @@ vi.mock("../routing/session-key.js", () => ({
|
|||
parseAgentSessionKey: vi.fn(() => null),
|
||||
}));
|
||||
|
||||
vi.mock("../version.js", () => ({
|
||||
resolveRuntimeServiceVersion: vi.fn(() => "2026.3.8"),
|
||||
}));
|
||||
vi.mock("../version.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../version.js")>("../version.js");
|
||||
return {
|
||||
...actual,
|
||||
resolveRuntimeServiceVersion: vi.fn(() => "2026.3.8"),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./status.link-channel.js", () => ({
|
||||
resolveLinkChannelContext: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
const { hasPotentialConfiguredChannels } = await import("../channels/config-presence.js");
|
||||
const { buildChannelSummary } = await import("../infra/channel-summary.js");
|
||||
const { resolveLinkChannelContext } = await import("./status.link-channel.js");
|
||||
let getStatusSummary: typeof import("./status.summary.js").getStatusSummary;
|
||||
|
|
@ -128,6 +125,8 @@ describe("getStatusSummary", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
statusSummaryMocks.hasPotentialConfiguredChannels.mockReturnValue(true);
|
||||
statusSummaryMocks.buildChannelSummary.mockResolvedValue(["ok"]);
|
||||
});
|
||||
|
||||
it("includes runtimeVersion in the status payload", async () => {
|
||||
|
|
@ -141,7 +140,7 @@ describe("getStatusSummary", () => {
|
|||
});
|
||||
|
||||
it("skips channel summary imports when no channels are configured", async () => {
|
||||
vi.mocked(hasPotentialConfiguredChannels).mockReturnValue(false);
|
||||
statusSummaryMocks.hasPotentialConfiguredChannels.mockReturnValue(false);
|
||||
|
||||
const summary = await getStatusSummary();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +1,39 @@
|
|||
import { vi } from "vitest";
|
||||
import { vi, type Mock } from "vitest";
|
||||
|
||||
type TestMock = ReturnType<typeof vi.fn>;
|
||||
type TestMock<TArgs extends unknown[] = unknown[], TResult = unknown> = Mock<
|
||||
(...args: TArgs) => TResult
|
||||
>;
|
||||
|
||||
export const loadConfigMock: TestMock = vi.fn();
|
||||
export const resolveGatewayPortMock: TestMock = vi.fn();
|
||||
export const resolveStateDirMock: TestMock = vi.fn(
|
||||
export const resolveStateDirMock: TestMock<[NodeJS.ProcessEnv], string> = vi.fn(
|
||||
(env: NodeJS.ProcessEnv) => env.OPENCLAW_STATE_DIR ?? "/tmp/openclaw",
|
||||
);
|
||||
export const resolveConfigPathMock: TestMock = vi.fn(
|
||||
export const resolveConfigPathMock: TestMock<[NodeJS.ProcessEnv, string], string> = vi.fn(
|
||||
(env: NodeJS.ProcessEnv, stateDir: string) =>
|
||||
env.OPENCLAW_CONFIG_PATH ?? `${stateDir}/openclaw.json`,
|
||||
);
|
||||
export const pickPrimaryTailnetIPv4Mock: TestMock = vi.fn();
|
||||
export const pickPrimaryLanIPv4Mock: TestMock = vi.fn();
|
||||
|
||||
vi.mock("../config/config.js", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("../config/config.js")>()),
|
||||
loadConfig: loadConfigMock,
|
||||
resolveGatewayPort: resolveGatewayPortMock,
|
||||
resolveStateDir: resolveStateDirMock,
|
||||
resolveConfigPath: resolveConfigPathMock,
|
||||
}));
|
||||
export const isLoopbackHostMock: TestMock<[string], boolean> = vi.fn((host: string) =>
|
||||
/^(localhost|127(?:\.\d{1,3}){3}|::1|\[::1\]|::ffff:127(?:\.\d{1,3}){3})$/i.test(
|
||||
host.trim().replace(/\.+$/, ""),
|
||||
),
|
||||
);
|
||||
export const isSecureWebSocketUrlMock: TestMock<
|
||||
[string, { allowPrivateWs?: boolean } | undefined],
|
||||
boolean
|
||||
> = vi.fn((url: string, opts?: { allowPrivateWs?: boolean }) => {
|
||||
const parsed = new URL(url);
|
||||
if (parsed.protocol === "wss:") {
|
||||
return true;
|
||||
}
|
||||
if (parsed.protocol !== "ws:") {
|
||||
return false;
|
||||
}
|
||||
return opts?.allowPrivateWs === true || isLoopbackHostMock(parsed.hostname);
|
||||
});
|
||||
|
||||
vi.mock("../infra/tailnet.js", () => ({
|
||||
pickPrimaryTailnetIPv4: pickPrimaryTailnetIPv4Mock,
|
||||
}));
|
||||
|
||||
vi.mock("./net.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./net.js")>();
|
||||
return {
|
||||
...actual,
|
||||
pickPrimaryLanIPv4: pickPrimaryLanIPv4Mock,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ const { existsSyncMock, readFileSyncMock } = vi.hoisted(() => ({
|
|||
readFileSyncMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:fs", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:fs")>();
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
|
||||
existsSyncMock.mockImplementation((pathname) => actual.existsSync(pathname));
|
||||
readFileSyncMock.mockImplementation((pathname, options) =>
|
||||
String(pathname) === "/tmp/vertex-adc.json"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
loadConfigMock as loadConfig,
|
||||
resolveConfigPathMock as resolveConfigPath,
|
||||
|
|
@ -10,6 +10,25 @@ import {
|
|||
} from "../gateway/gateway-connection.test-mocks.js";
|
||||
import { captureEnv, withEnvAsync } from "../test-utils/env.js";
|
||||
|
||||
vi.mock("../config/config.js", async () => {
|
||||
const mocks = await import("../gateway/gateway-connection.test-mocks.js");
|
||||
return {
|
||||
loadConfig: mocks.loadConfigMock,
|
||||
resolveConfigPath: mocks.resolveConfigPathMock,
|
||||
resolveGatewayPort: mocks.resolveGatewayPortMock,
|
||||
resolveStateDir: mocks.resolveStateDirMock,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../gateway/net.js", async () => {
|
||||
const mocks = await import("../gateway/gateway-connection.test-mocks.js");
|
||||
return {
|
||||
isLoopbackHost: mocks.isLoopbackHostMock,
|
||||
isSecureWebSocketUrl: mocks.isSecureWebSocketUrlMock,
|
||||
pickPrimaryLanIPv4: mocks.pickPrimaryLanIPv4Mock,
|
||||
};
|
||||
});
|
||||
|
||||
const { GatewayChatClient, resolveGatewayConnection } = await import("./gateway-chat.js");
|
||||
|
||||
async function fileExists(filePath: string): Promise<boolean> {
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ function resolveDefaultBase<TModule extends object>(actual: TModule): Record<str
|
|||
}
|
||||
|
||||
export async function mockNodeBuiltinModule<TModule extends object>(
|
||||
importOriginal: () => Promise<TModule>,
|
||||
loadActual: () => Promise<TModule>,
|
||||
factory: MockFactory<TModule>,
|
||||
options?: { mirrorToDefault?: boolean },
|
||||
): Promise<TModule> {
|
||||
const actual = await importOriginal();
|
||||
const actual = await loadActual();
|
||||
const overrides = resolveMockOverrides(actual, factory);
|
||||
const mocked = {
|
||||
...actual,
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@ const providerAuthContractModules = vi.hoisted(() => ({
|
|||
openAIIndexModuleUrl: new URL("../../../extensions/openai/index.ts", import.meta.url).href,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-auth-login", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/provider-auth-login")>();
|
||||
vi.mock("openclaw/plugin-sdk/provider-auth-login", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/provider-auth-login")>(
|
||||
"openclaw/plugin-sdk/provider-auth-login",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
loginOpenAICodexOAuth: loginOpenAICodexOAuthMock,
|
||||
|
|
@ -42,8 +44,10 @@ vi.mock("openclaw/plugin-sdk/provider-auth-login", async (importOriginal) => {
|
|||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-auth", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/provider-auth")>();
|
||||
vi.mock("openclaw/plugin-sdk/provider-auth", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/provider-auth")>(
|
||||
"openclaw/plugin-sdk/provider-auth",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
ensureAuthProfileStore: ensureAuthProfileStoreMock,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ let resolveModelAsyncMock: typeof import("../../../src/agents/pi-embedded-runner
|
|||
let ensureCustomApiRegisteredMock: typeof import("../../../src/agents/custom-api-registry.js").ensureCustomApiRegistered;
|
||||
let prepareModelForSimpleCompletionMock: typeof import("../../../src/agents/simple-completion-transport.js").prepareModelForSimpleCompletion;
|
||||
|
||||
vi.mock("@mariozechner/pi-ai", async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import("@mariozechner/pi-ai")>();
|
||||
vi.mock("@mariozechner/pi-ai", async () => {
|
||||
const original =
|
||||
await vi.importActual<typeof import("@mariozechner/pi-ai")>("@mariozechner/pi-ai");
|
||||
return {
|
||||
...original,
|
||||
completeSimple: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { vi } from "vitest";
|
||||
|
||||
vi.mock("@mariozechner/pi-ai", async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import("@mariozechner/pi-ai")>();
|
||||
vi.mock("@mariozechner/pi-ai", async () => {
|
||||
const original =
|
||||
await vi.importActual<typeof import("@mariozechner/pi-ai")>("@mariozechner/pi-ai");
|
||||
return {
|
||||
...original,
|
||||
getOAuthApiKey: () => undefined,
|
||||
|
|
|
|||
Loading…
Reference in New Issue