Tests: refresh Discord and WhatsApp channel mocks

This commit is contained in:
Alexander Davydov 2026-03-19 18:50:26 +03:00
parent 79fd617019
commit 4ffdb2bb19
11 changed files with 192 additions and 83 deletions

View File

@ -8,12 +8,16 @@ export const dispatchMock: MockFn = vi.fn();
export const readAllowFromStoreMock: MockFn = vi.fn();
export const upsertPairingRequestMock: MockFn = vi.fn();
vi.mock("./send.js", () => ({
sendMessageDiscord: (...args: unknown[]) => sendMock(...args),
reactMessageDiscord: async (...args: unknown[]) => {
reactMock(...args);
},
}));
vi.mock("./send.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./send.js")>();
return {
...actual,
sendMessageDiscord: (...args: unknown[]) => sendMock(...args),
reactMessageDiscord: async (...args: unknown[]) => {
reactMock(...args);
},
};
});
vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/reply-runtime")>();
@ -36,7 +40,13 @@ function createPairingStoreMocks() {
};
}
vi.mock("openclaw/plugin-sdk/conversation-runtime", () => createPairingStoreMocks());
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
return {
...actual,
...createPairingStoreMocks(),
};
});
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();

View File

@ -9,9 +9,13 @@ vi.mock("../../../../src/media/fetch.js", () => ({
fetchRemoteMedia: (...args: unknown[]) => fetchRemoteMedia(...args),
}));
vi.mock("../../../../src/media/store.js", () => ({
saveMediaBuffer: (...args: unknown[]) => saveMediaBuffer(...args),
}));
vi.mock("../../../../src/media/store.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../../../../src/media/store.js")>();
return {
...actual,
saveMediaBuffer: (...args: unknown[]) => saveMediaBuffer(...args),
};
});
vi.mock("../../../../src/globals.js", () => ({
logVerbose: () => {},

View File

@ -205,9 +205,19 @@ describe("agent components", () => {
await button.run(interaction, { componentId: "hello" } as ComponentData);
expect(defer).toHaveBeenCalledWith({ ephemeral: true });
expect(reply).toHaveBeenCalledWith({ content: "You are not authorized to use this button." });
expect(enqueueSystemEventMock).not.toHaveBeenCalled();
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
expect(reply).toHaveBeenCalledWith({ content: "✓" });
expect(enqueueSystemEventMock).toHaveBeenCalledWith(
expect.stringContaining("[Discord component: hello clicked"),
expect.objectContaining({
contextKey: "discord:agent-button:dm-channel:hello:123456789",
sessionKey: "agent:main:main",
}),
);
expect(readAllowFromStoreMock).toHaveBeenCalledWith({
provider: "discord",
accountId: "default",
dmPolicy: "allowlist",
});
});
it("matches tag-based allowlist entries for DM select menus", async () => {

View File

@ -1,5 +1,4 @@
import { beforeEach, describe, expect, it } from "vitest";
import { clearPluginCommands, registerPluginCommand } from "../../../../src/plugins/commands.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
baseConfig,
baseRuntime,
@ -7,18 +6,39 @@ import {
resetDiscordProviderMonitorMocks,
} from "../../../../test/helpers/extensions/discord-provider.test-support.js";
const { createDiscordNativeCommandMock, clientHandleDeployRequestMock, monitorLifecycleMock } =
getProviderMonitorTestMocks();
const {
createDiscordNativeCommandMock,
clientHandleDeployRequestMock,
monitorLifecycleMock,
resolveDiscordAccountMock,
} = getProviderMonitorTestMocks();
describe("monitorDiscordProvider real plugin registry", () => {
beforeEach(() => {
clearPluginCommands();
beforeEach(async () => {
vi.resetModules();
resetDiscordProviderMonitorMocks({
nativeCommands: [{ name: "status", description: "Status", acceptsArgs: false }],
});
vi.doMock("../accounts.js", () => ({
resolveDiscordAccount: (...args: Parameters<typeof resolveDiscordAccountMock>) =>
resolveDiscordAccountMock(...args),
}));
vi.doMock("../probe.js", () => ({
fetchDiscordApplicationId: async () => "app-1",
}));
vi.doMock("../token.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../token.js")>();
return {
...actual,
normalizeDiscordToken: (value?: string) => value,
};
});
const { clearPluginCommands } = await import("../../../../src/plugins/commands.js");
clearPluginCommands();
});
it("registers plugin commands from the real registry as native Discord commands", async () => {
const { registerPluginCommand } = await import("../../../../src/plugins/commands.js");
expect(
registerPluginCommand("demo-plugin", {
name: "pair",

View File

@ -29,14 +29,18 @@ type MockWebListener = {
export const TEST_NET_IP = "203.0.113.10";
vi.mock("openclaw/plugin-sdk/agent-runtime", () => ({
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
runEmbeddedPiAgent: vi.fn(),
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
}));
vi.mock("openclaw/plugin-sdk/agent-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/agent-runtime")>();
return {
...actual,
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
runEmbeddedPiAgent: vi.fn(),
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
};
});
export async function rmDirWithRetries(
dir: string,

View File

@ -35,6 +35,10 @@ describe("whatsappPlugin outbound sendPoll", () => {
});
expectWhatsAppPollSent(hoisted.sendPollWhatsApp, { cfg, poll, to, accountId });
expect(result).toEqual({ messageId: "wa-poll-1", toJid: "1555@s.whatsapp.net" });
expect(result).toEqual({
channel: "whatsapp",
messageId: "wa-poll-1",
toJid: "1555@s.whatsapp.net",
});
});
});

View File

@ -35,10 +35,15 @@ export function setupAccessControlTestHarness(): void {
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
return {
...actual,
loadConfig: () => config,
};
const mockModule = Object.create(null) as Record<string, unknown>;
Object.defineProperties(mockModule, Object.getOwnPropertyDescriptors(actual));
Object.defineProperty(mockModule, "loadConfig", {
configurable: true,
enumerable: true,
writable: true,
value: () => config,
});
return mockModule;
});
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {

View File

@ -30,6 +30,7 @@ export const readAllowFromStoreMock: AnyMockFn = vi.fn().mockResolvedValue([]);
export const upsertPairingRequestMock: AnyMockFn = vi
.fn()
.mockResolvedValue({ code: "PAIRCODE", created: true });
export const readStoreAllowFromForDmPolicyMock: AnyMockFn = vi.fn().mockResolvedValue([]);
export type MockSock = {
ev: EventEmitter;
@ -96,13 +97,33 @@ vi.mock("openclaw/plugin-sdk/media-runtime", async (importOriginal) => {
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
const mockModule = Object.create(null) as Record<string, unknown>;
Object.defineProperties(mockModule, Object.getOwnPropertyDescriptors(actual));
Object.defineProperty(mockModule, "loadConfig", {
configurable: true,
enumerable: true,
writable: true,
value: () => mockLoadConfig(),
});
return mockModule;
});
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
return {
...actual,
loadConfig: () => mockLoadConfig(),
...getPairingStoreMocks(),
};
});
vi.mock("openclaw/plugin-sdk/conversation-runtime", () => getPairingStoreMocks());
vi.mock("openclaw/plugin-sdk/security-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/security-runtime")>();
return {
...actual,
readStoreAllowFromForDmPolicy: (...args: unknown[]) =>
readStoreAllowFromForDmPolicyMock(...args),
};
});
vi.mock("./session.js", () => ({
createWaSocket: vi.fn().mockResolvedValue(sock),
@ -144,6 +165,7 @@ export function installWebMonitorInboxUnitTestHooks(opts?: { authDir?: boolean }
code: "PAIRCODE",
created: true,
});
readStoreAllowFromForDmPolicyMock.mockResolvedValue([]);
const { resetWebInboundDedupe } = await import("./inbound.js");
resetWebInboundDedupe();
if (createAuthDir) {

View File

@ -1,10 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import { installCommonResolveTargetErrorCases } from "../../shared/resolve-target-test-helpers.js";
vi.mock("openclaw/plugin-sdk/whatsapp", async () => {
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/whatsapp")>(
"openclaw/plugin-sdk/whatsapp",
);
const whatsappMocks = vi.hoisted(() => {
const normalizeWhatsAppTarget = (value: string) => {
if (value === "invalid-target") return null;
// Simulate E.164 normalization: strip leading + and whatsapp: prefix.
@ -12,45 +9,68 @@ vi.mock("openclaw/plugin-sdk/whatsapp", async () => {
return stripped.includes("@g.us") ? stripped : `${stripped}@s.whatsapp.net`;
};
const resolveWhatsAppOutboundTarget = ({
to,
allowFrom,
mode,
}: {
to?: string;
allowFrom: string[];
mode: "explicit" | "implicit";
}) => {
const raw = typeof to === "string" ? to.trim() : "";
if (!raw) {
return { ok: false, error: new Error("missing target") };
}
const normalized = normalizeWhatsAppTarget(raw);
if (!normalized) {
return { ok: false, error: new Error("invalid target") };
}
if (mode === "implicit" && !normalized.endsWith("@g.us")) {
const allowAll = allowFrom.includes("*");
const allowExact = allowFrom.some((entry) => {
if (!entry) {
return false;
}
const normalizedEntry = normalizeWhatsAppTarget(entry.trim());
return normalizedEntry?.toLowerCase() === normalized.toLowerCase();
});
if (!allowAll && !allowExact) {
return { ok: false, error: new Error("target not allowlisted") };
}
}
return { ok: true, to: normalized };
};
return {
normalizeWhatsAppTarget,
resolveWhatsAppOutboundTarget,
};
});
vi.mock("openclaw/plugin-sdk/whatsapp", async () => {
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/whatsapp")>(
"openclaw/plugin-sdk/whatsapp",
);
return {
...actual,
normalizeWhatsAppTarget: whatsappMocks.normalizeWhatsAppTarget,
isWhatsAppGroupJid: (value: string) => value.endsWith("@g.us"),
};
});
vi.mock("openclaw/plugin-sdk/whatsapp-core", async () => {
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/whatsapp-core")>(
"openclaw/plugin-sdk/whatsapp-core",
);
return {
...actual,
getChatChannelMeta: () => ({ id: "whatsapp", label: "WhatsApp" }),
normalizeWhatsAppTarget,
isWhatsAppGroupJid: (value: string) => value.endsWith("@g.us"),
resolveWhatsAppOutboundTarget: ({
to,
allowFrom,
mode,
}: {
to?: string;
allowFrom: string[];
mode: "explicit" | "implicit";
}) => {
const raw = typeof to === "string" ? to.trim() : "";
if (!raw) {
return { ok: false, error: new Error("missing target") };
}
const normalized = normalizeWhatsAppTarget(raw);
if (!normalized) {
return { ok: false, error: new Error("invalid target") };
}
if (mode === "implicit" && !normalized.endsWith("@g.us")) {
const allowAll = allowFrom.includes("*");
const allowExact = allowFrom.some((entry) => {
if (!entry) {
return false;
}
const normalizedEntry = normalizeWhatsAppTarget(entry.trim());
return normalizedEntry?.toLowerCase() === normalized.toLowerCase();
});
if (!allowAll && !allowExact) {
return { ok: false, error: new Error("target not allowlisted") };
}
}
return { ok: true, to: normalized };
},
resolveWhatsAppOutboundTarget: whatsappMocks.resolveWhatsAppOutboundTarget,
missingTargetError: (provider: string, hint: string) =>
new Error(`Delivering to ${provider} requires target ${hint}`),
};

View File

@ -15,7 +15,7 @@ const resolveWhatsAppAuthDirMock = vi.hoisted(() =>
})),
);
vi.mock("../../../src/channel-web.js", () => ({
vi.mock("./login.js", () => ({
loginWeb: loginWebMock,
}));

View File

@ -32,16 +32,21 @@ export function resetLoadConfigMock() {
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
return {
...actual,
loadConfig: () => {
const mockModule = Object.create(null) as Record<string, unknown>;
Object.defineProperties(mockModule, Object.getOwnPropertyDescriptors(actual));
Object.defineProperty(mockModule, "loadConfig", {
configurable: true,
enumerable: true,
writable: true,
value: () => {
const getter = (globalThis as Record<symbol, unknown>)[CONFIG_KEY];
if (typeof getter === "function") {
return getter();
}
return DEFAULT_CONFIG;
},
};
});
return mockModule;
});
// Some web modules live under `src/web/auto-reply/*` and import config via a different
@ -52,16 +57,21 @@ vi.mock("../../config/config.js", async (importOriginal) => {
// For typing in this file (which lives in `src/web/*`), refer to the same module
// via the local relative path.
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
return {
...actual,
loadConfig: () => {
const mockModule = Object.create(null) as Record<string, unknown>;
Object.defineProperties(mockModule, Object.getOwnPropertyDescriptors(actual));
Object.defineProperty(mockModule, "loadConfig", {
configurable: true,
enumerable: true,
writable: true,
value: () => {
const getter = (globalThis as Record<symbol, unknown>)[CONFIG_KEY];
if (typeof getter === "function") {
return getter();
}
return DEFAULT_CONFIG;
},
};
});
return mockModule;
});
vi.mock("openclaw/plugin-sdk/media-runtime", async (importOriginal) => {