mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-29 18:12:52 +00:00
fix(test): stabilize line and irc extension suites
This commit is contained in:
@@ -6,69 +6,39 @@ import { setIrcRuntime } from "./runtime.js";
|
||||
import type { CoreConfig, IrcInboundMessage } from "./types.js";
|
||||
|
||||
const {
|
||||
createChannelPairingControllerMock,
|
||||
deliverFormattedTextWithAttachmentsMock,
|
||||
dispatchInboundReplyWithBaseMock,
|
||||
isDangerousNameMatchingEnabledMock,
|
||||
logInboundDropMock,
|
||||
readStoreAllowFromForDmPolicyMock,
|
||||
resolveAllowlistProviderRuntimeGroupPolicyMock,
|
||||
resolveControlCommandGateMock,
|
||||
resolveDefaultGroupPolicyMock,
|
||||
resolveEffectiveAllowFromListsMock,
|
||||
warnMissingProviderGroupPolicyFallbackOnceMock,
|
||||
buildMentionRegexesMock,
|
||||
hasControlCommandMock,
|
||||
matchesMentionPatternsMock,
|
||||
readAllowFromStoreMock,
|
||||
shouldHandleTextCommandsMock,
|
||||
upsertPairingRequestMock,
|
||||
} = vi.hoisted(() => {
|
||||
return {
|
||||
createChannelPairingControllerMock: vi.fn(),
|
||||
deliverFormattedTextWithAttachmentsMock: vi.fn(),
|
||||
dispatchInboundReplyWithBaseMock: vi.fn(),
|
||||
isDangerousNameMatchingEnabledMock: vi.fn(),
|
||||
logInboundDropMock: vi.fn(),
|
||||
readStoreAllowFromForDmPolicyMock: vi.fn(),
|
||||
resolveAllowlistProviderRuntimeGroupPolicyMock: vi.fn(),
|
||||
resolveControlCommandGateMock: vi.fn(),
|
||||
resolveDefaultGroupPolicyMock: vi.fn(),
|
||||
resolveEffectiveAllowFromListsMock: vi.fn(),
|
||||
warnMissingProviderGroupPolicyFallbackOnceMock: vi.fn(),
|
||||
buildMentionRegexesMock: vi.fn(() => []),
|
||||
hasControlCommandMock: vi.fn(() => false),
|
||||
matchesMentionPatternsMock: vi.fn(() => false),
|
||||
readAllowFromStoreMock: vi.fn(async () => []),
|
||||
shouldHandleTextCommandsMock: vi.fn(() => false),
|
||||
upsertPairingRequestMock: vi.fn(async () => ({ code: "CODE", created: true })),
|
||||
};
|
||||
});
|
||||
|
||||
const sendMessageIrcMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./runtime-api.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./runtime-api.js")>("./runtime-api.js");
|
||||
return {
|
||||
...actual,
|
||||
createChannelPairingController: createChannelPairingControllerMock,
|
||||
deliverFormattedTextWithAttachments: deliverFormattedTextWithAttachmentsMock,
|
||||
dispatchInboundReplyWithBase: dispatchInboundReplyWithBaseMock,
|
||||
isDangerousNameMatchingEnabled: isDangerousNameMatchingEnabledMock,
|
||||
logInboundDrop: logInboundDropMock,
|
||||
readStoreAllowFromForDmPolicy: readStoreAllowFromForDmPolicyMock,
|
||||
resolveAllowlistProviderRuntimeGroupPolicy: resolveAllowlistProviderRuntimeGroupPolicyMock,
|
||||
resolveControlCommandGate: resolveControlCommandGateMock,
|
||||
resolveDefaultGroupPolicy: resolveDefaultGroupPolicyMock,
|
||||
resolveEffectiveAllowFromLists: resolveEffectiveAllowFromListsMock,
|
||||
warnMissingProviderGroupPolicyFallbackOnce: warnMissingProviderGroupPolicyFallbackOnceMock,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./send.js", () => ({
|
||||
sendMessageIrc: sendMessageIrcMock,
|
||||
}));
|
||||
|
||||
function installIrcRuntime() {
|
||||
setIrcRuntime({
|
||||
channel: {
|
||||
pairing: {
|
||||
readAllowFromStore: readAllowFromStoreMock,
|
||||
upsertPairingRequest: upsertPairingRequestMock,
|
||||
},
|
||||
commands: {
|
||||
shouldHandleTextCommands: vi.fn(() => false),
|
||||
shouldHandleTextCommands: shouldHandleTextCommandsMock,
|
||||
},
|
||||
text: {
|
||||
hasControlCommand: vi.fn(() => false),
|
||||
hasControlCommand: hasControlCommandMock,
|
||||
},
|
||||
mentions: {
|
||||
buildMentionRegexes: vi.fn(() => []),
|
||||
matchesMentionPatterns: vi.fn(() => false),
|
||||
buildMentionRegexes: buildMentionRegexesMock,
|
||||
matchesMentionPatterns: matchesMentionPatternsMock,
|
||||
},
|
||||
},
|
||||
} as never);
|
||||
@@ -115,36 +85,10 @@ describe("irc inbound behavior", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
installIrcRuntime();
|
||||
resolveDefaultGroupPolicyMock.mockReturnValue("allowlist");
|
||||
resolveAllowlistProviderRuntimeGroupPolicyMock.mockReturnValue({
|
||||
groupPolicy: "allowlist",
|
||||
providerMissingFallbackApplied: false,
|
||||
});
|
||||
warnMissingProviderGroupPolicyFallbackOnceMock.mockReturnValue(undefined);
|
||||
readStoreAllowFromForDmPolicyMock.mockResolvedValue([]);
|
||||
isDangerousNameMatchingEnabledMock.mockReturnValue(false);
|
||||
resolveEffectiveAllowFromListsMock.mockReturnValue({
|
||||
effectiveAllowFrom: [],
|
||||
effectiveGroupAllowFrom: [],
|
||||
});
|
||||
deliverFormattedTextWithAttachmentsMock.mockImplementation(async ({ payload, send }) => {
|
||||
await send({ text: payload.text, replyToId: undefined });
|
||||
return true;
|
||||
});
|
||||
readAllowFromStoreMock.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it("issues a DM pairing challenge and sends the reply to the sender nick", async () => {
|
||||
const issueChallenge = vi.fn(async ({ sendPairingReply }) => {
|
||||
await sendPairingReply("pair me");
|
||||
});
|
||||
createChannelPairingControllerMock.mockReturnValue({
|
||||
readStoreForDmPolicy: vi.fn(),
|
||||
issueChallenge,
|
||||
});
|
||||
resolveControlCommandGateMock.mockReturnValue({
|
||||
commandAuthorized: false,
|
||||
shouldBlock: false,
|
||||
});
|
||||
const sendReply = vi.fn(async () => {});
|
||||
|
||||
await handleIrcInbound({
|
||||
@@ -155,25 +99,30 @@ describe("irc inbound behavior", () => {
|
||||
sendReply,
|
||||
});
|
||||
|
||||
expect(issueChallenge).toHaveBeenCalledTimes(1);
|
||||
expect(sendReply).toHaveBeenCalledWith("alice", "pair me", undefined);
|
||||
expect(dispatchInboundReplyWithBaseMock).not.toHaveBeenCalled();
|
||||
expect(upsertPairingRequestMock).toHaveBeenCalledWith({
|
||||
channel: "irc",
|
||||
accountId: "default",
|
||||
id: "alice!ident@example.com",
|
||||
meta: { name: "alice" },
|
||||
});
|
||||
expect(sendReply).toHaveBeenCalledTimes(1);
|
||||
expect(sendReply).toHaveBeenCalledWith(
|
||||
"alice",
|
||||
expect.stringContaining("OpenClaw: access not configured."),
|
||||
undefined,
|
||||
);
|
||||
expect(sendReply).toHaveBeenCalledWith(
|
||||
"alice",
|
||||
expect.stringContaining("Your IRC id: alice!ident@example.com"),
|
||||
undefined,
|
||||
);
|
||||
expect(sendReply).toHaveBeenCalledWith("alice", expect.stringContaining("CODE"), undefined);
|
||||
});
|
||||
|
||||
it("drops unauthorized group control commands before dispatch", async () => {
|
||||
createChannelPairingControllerMock.mockReturnValue({
|
||||
readStoreForDmPolicy: vi.fn(),
|
||||
issueChallenge: vi.fn(),
|
||||
});
|
||||
resolveEffectiveAllowFromListsMock.mockReturnValue({
|
||||
effectiveAllowFrom: [],
|
||||
effectiveGroupAllowFrom: ["alice!ident@example.com"],
|
||||
});
|
||||
resolveControlCommandGateMock.mockReturnValue({
|
||||
commandAuthorized: false,
|
||||
shouldBlock: true,
|
||||
});
|
||||
const runtime = createRuntimeEnv();
|
||||
shouldHandleTextCommandsMock.mockReturnValue(true);
|
||||
hasControlCommandMock.mockReturnValue(true);
|
||||
|
||||
await handleIrcInbound({
|
||||
message: createMessage({
|
||||
@@ -186,9 +135,11 @@ describe("irc inbound behavior", () => {
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: [],
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["alice!ident@example.com"],
|
||||
groupAllowFrom: ["bob!ident@example.com"],
|
||||
groups: {
|
||||
"#ops": {},
|
||||
"#ops": {
|
||||
allowFrom: ["alice!ident@example.com"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -196,13 +147,8 @@ describe("irc inbound behavior", () => {
|
||||
runtime,
|
||||
});
|
||||
|
||||
expect(logInboundDropMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
channel: "irc",
|
||||
reason: "control command (unauthorized)",
|
||||
target: "alice!ident@example.com",
|
||||
}),
|
||||
expect(runtime.log).toHaveBeenCalledWith(
|
||||
"irc: drop control command (unauthorized) target=alice!ident@example.com",
|
||||
);
|
||||
expect(dispatchInboundReplyWithBaseMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createSendCfgThreadingRuntime,
|
||||
expectProvidedCfgSkipsRuntimeLoad,
|
||||
expectRuntimeCfgFallback,
|
||||
} from "../../../test/helpers/extensions/send-config.js";
|
||||
import { createSendCfgThreadingRuntime } from "../../../test/helpers/extensions/send-config.js";
|
||||
import type { IrcClient } from "./client.js";
|
||||
import { setIrcRuntime } from "./runtime.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => {
|
||||
@@ -17,28 +14,12 @@ const hoisted = vi.hoisted(() => {
|
||||
resolveMarkdownTableMode,
|
||||
convertMarkdownTables,
|
||||
record,
|
||||
resolveIrcAccount: vi.fn(() => ({
|
||||
configured: true,
|
||||
accountId: "default",
|
||||
host: "irc.example.com",
|
||||
nick: "openclaw",
|
||||
port: 6697,
|
||||
tls: true,
|
||||
})),
|
||||
normalizeIrcMessagingTarget: vi.fn((value: string) => value.trim()),
|
||||
connectIrcClient: vi.fn(),
|
||||
buildIrcConnectOptions: vi.fn(() => ({})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./runtime.js", () => ({
|
||||
getIrcRuntime: () => createSendCfgThreadingRuntime(hoisted),
|
||||
}));
|
||||
|
||||
vi.mock("./accounts.js", () => ({
|
||||
resolveIrcAccount: hoisted.resolveIrcAccount,
|
||||
}));
|
||||
|
||||
vi.mock("./normalize.js", () => ({
|
||||
normalizeIrcMessagingTarget: hoisted.normalizeIrcMessagingTarget,
|
||||
}));
|
||||
@@ -64,10 +45,24 @@ import { sendMessageIrc } from "./send.js";
|
||||
describe("sendMessageIrc cfg threading", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
setIrcRuntime(createSendCfgThreadingRuntime(hoisted) as never);
|
||||
});
|
||||
|
||||
it("uses explicitly provided cfg without loading runtime config", async () => {
|
||||
const providedCfg = { source: "provided" } as unknown as CoreConfig;
|
||||
const providedCfg = {
|
||||
channels: {
|
||||
irc: {
|
||||
host: "irc.example.com",
|
||||
nick: "openclaw",
|
||||
accounts: {
|
||||
work: {
|
||||
host: "irc.example.com",
|
||||
nick: "workbot",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as CoreConfig;
|
||||
const client = {
|
||||
isReady: vi.fn(() => true),
|
||||
sendPrivmsg: vi.fn(),
|
||||
@@ -79,18 +74,27 @@ describe("sendMessageIrc cfg threading", () => {
|
||||
accountId: "work",
|
||||
});
|
||||
|
||||
expectProvidedCfgSkipsRuntimeLoad({
|
||||
loadConfig: hoisted.loadConfig,
|
||||
resolveAccount: hoisted.resolveIrcAccount,
|
||||
cfg: providedCfg,
|
||||
accountId: "work",
|
||||
});
|
||||
expect(hoisted.loadConfig).not.toHaveBeenCalled();
|
||||
expect(client.sendPrivmsg).toHaveBeenCalledWith("#room", "hello");
|
||||
expect(result).toEqual({ messageId: "irc-msg-1", target: "#room" });
|
||||
expect(hoisted.record).toHaveBeenCalledWith({
|
||||
channel: "irc",
|
||||
accountId: "work",
|
||||
direction: "outbound",
|
||||
});
|
||||
expect(result.target).toBe("#room");
|
||||
expect(result.messageId).toEqual(expect.any(String));
|
||||
expect(result.messageId.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("falls back to runtime config when cfg is omitted", async () => {
|
||||
const runtimeCfg = { source: "runtime" } as unknown as CoreConfig;
|
||||
const runtimeCfg = {
|
||||
channels: {
|
||||
irc: {
|
||||
host: "irc.example.com",
|
||||
nick: "openclaw",
|
||||
},
|
||||
},
|
||||
} as unknown as CoreConfig;
|
||||
hoisted.loadConfig.mockReturnValueOnce(runtimeCfg);
|
||||
const client = {
|
||||
isReady: vi.fn(() => true),
|
||||
@@ -99,12 +103,12 @@ describe("sendMessageIrc cfg threading", () => {
|
||||
|
||||
await sendMessageIrc("#ops", "ping", { client });
|
||||
|
||||
expectRuntimeCfgFallback({
|
||||
loadConfig: hoisted.loadConfig,
|
||||
resolveAccount: hoisted.resolveIrcAccount,
|
||||
cfg: runtimeCfg,
|
||||
accountId: undefined,
|
||||
});
|
||||
expect(hoisted.loadConfig).toHaveBeenCalledTimes(1);
|
||||
expect(client.sendPrivmsg).toHaveBeenCalledWith("#ops", "ping");
|
||||
expect(hoisted.record).toHaveBeenCalledWith({
|
||||
channel: "irc",
|
||||
accountId: "default",
|
||||
direction: "outbound",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,11 +20,15 @@ const { readAllowFromStoreMock, upsertPairingRequestMock } = vi.hoisted(() => ({
|
||||
upsertPairingRequestMock: vi.fn(async () => ({ code: "CODE", created: true })),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({
|
||||
resolvePairingIdLabel: () => "lineUserId",
|
||||
readChannelAllowFromStore: readAllowFromStoreMock,
|
||||
upsertChannelPairingRequest: upsertPairingRequestMock,
|
||||
}));
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
resolvePairingIdLabel: () => "lineUserId",
|
||||
readChannelAllowFromStore: readAllowFromStoreMock,
|
||||
upsertChannelPairingRequest: upsertPairingRequestMock,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./download.js", () => ({
|
||||
downloadLineMedia: async () => {
|
||||
|
||||
Reference in New Issue
Block a user