* refactor: remove channel shim directories, point all imports to extensions
Delete the 6 backward-compat shim directories (src/telegram, src/discord,
src/slack, src/signal, src/imessage, src/web) that were re-exporting from
extensions. Update all 112+ source files to import directly from
extensions/{channel}/src/ instead of through the shims.
Also:
- Move src/channels/telegram/ (allow-from, api) to extensions/telegram/src/
- Fix outbound adapters to use resolveOutboundSendDep (fixes 5 pre-existing TS errors)
- Update cross-extension imports (src/web/media.js → extensions/whatsapp/src/media.js)
- Update vitest, tsdown, knip, labeler, and script configs for new paths
- Update guard test allowlists for extension paths
After this, src/ has zero channel-specific implementation code — only the
generic plugin framework remains.
* fix: update raw-fetch guard allowlist line numbers after shim removal
* refactor: document direct extension channel imports
* test: mock transcript module in delivery helpers
174 lines
5.1 KiB
TypeScript
174 lines
5.1 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "../../../config/config.js";
|
|
|
|
vi.mock("../../../../extensions/slack/src/send.js", () => ({
|
|
sendMessageSlack: vi.fn().mockResolvedValue({ messageId: "1234.5678", channelId: "C123" }),
|
|
}));
|
|
|
|
vi.mock("../../../plugins/hook-runner-global.js", () => ({
|
|
getGlobalHookRunner: vi.fn(),
|
|
}));
|
|
|
|
import { sendMessageSlack } from "../../../../extensions/slack/src/send.js";
|
|
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
|
|
import { slackOutbound } from "./slack.js";
|
|
|
|
type SlackSendTextCtx = {
|
|
to: string;
|
|
text: string;
|
|
accountId: string;
|
|
replyToId: string;
|
|
identity?: {
|
|
name?: string;
|
|
avatarUrl?: string;
|
|
emoji?: string;
|
|
};
|
|
};
|
|
|
|
const BASE_SLACK_SEND_CTX = {
|
|
to: "C123",
|
|
accountId: "default",
|
|
replyToId: "1111.2222",
|
|
} as const;
|
|
|
|
const sendSlackText = async (ctx: SlackSendTextCtx) => {
|
|
const sendText = slackOutbound.sendText as NonNullable<typeof slackOutbound.sendText>;
|
|
return await sendText({
|
|
cfg: {} as OpenClawConfig,
|
|
...ctx,
|
|
});
|
|
};
|
|
|
|
const sendSlackTextWithDefaults = async (
|
|
overrides: Partial<SlackSendTextCtx> & Pick<SlackSendTextCtx, "text">,
|
|
) => {
|
|
return await sendSlackText({
|
|
...BASE_SLACK_SEND_CTX,
|
|
...overrides,
|
|
});
|
|
};
|
|
|
|
const expectSlackSendCalledWith = (
|
|
text: string,
|
|
options?: {
|
|
identity?: {
|
|
username?: string;
|
|
iconUrl?: string;
|
|
iconEmoji?: string;
|
|
};
|
|
},
|
|
) => {
|
|
const expected = {
|
|
threadTs: "1111.2222",
|
|
accountId: "default",
|
|
cfg: expect.any(Object),
|
|
...(options?.identity ? { identity: expect.objectContaining(options.identity) } : {}),
|
|
};
|
|
expect(sendMessageSlack).toHaveBeenCalledWith("C123", text, expect.objectContaining(expected));
|
|
};
|
|
|
|
describe("slack outbound hook wiring", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("calls send without hooks when no hooks registered", async () => {
|
|
vi.mocked(getGlobalHookRunner).mockReturnValue(null);
|
|
|
|
await sendSlackTextWithDefaults({ text: "hello" });
|
|
expectSlackSendCalledWith("hello");
|
|
});
|
|
|
|
it("forwards identity opts when present", async () => {
|
|
vi.mocked(getGlobalHookRunner).mockReturnValue(null);
|
|
|
|
await sendSlackTextWithDefaults({
|
|
text: "hello",
|
|
identity: {
|
|
name: "My Agent",
|
|
avatarUrl: "https://example.com/avatar.png",
|
|
emoji: ":should_not_send:",
|
|
},
|
|
});
|
|
|
|
expectSlackSendCalledWith("hello", {
|
|
identity: { username: "My Agent", iconUrl: "https://example.com/avatar.png" },
|
|
});
|
|
});
|
|
|
|
it("forwards icon_emoji only when icon_url is absent", async () => {
|
|
vi.mocked(getGlobalHookRunner).mockReturnValue(null);
|
|
|
|
await sendSlackTextWithDefaults({
|
|
text: "hello",
|
|
identity: { emoji: ":lobster:" },
|
|
});
|
|
|
|
expectSlackSendCalledWith("hello", {
|
|
identity: { iconEmoji: ":lobster:" },
|
|
});
|
|
});
|
|
|
|
it("calls message_sending hook before sending", async () => {
|
|
const mockRunner = {
|
|
hasHooks: vi.fn().mockReturnValue(true),
|
|
runMessageSending: vi.fn().mockResolvedValue(undefined),
|
|
};
|
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as any);
|
|
|
|
await sendSlackTextWithDefaults({ text: "hello" });
|
|
|
|
expect(mockRunner.hasHooks).toHaveBeenCalledWith("message_sending");
|
|
expect(mockRunner.runMessageSending).toHaveBeenCalledWith(
|
|
{ to: "C123", content: "hello", metadata: { threadTs: "1111.2222", channelId: "C123" } },
|
|
{ channelId: "slack", accountId: "default" },
|
|
);
|
|
expectSlackSendCalledWith("hello");
|
|
});
|
|
|
|
it("cancels send when hook returns cancel:true", async () => {
|
|
const mockRunner = {
|
|
hasHooks: vi.fn().mockReturnValue(true),
|
|
runMessageSending: vi.fn().mockResolvedValue({ cancel: true }),
|
|
};
|
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as any);
|
|
|
|
const result = await sendSlackTextWithDefaults({ text: "hello" });
|
|
|
|
expect(sendMessageSlack).not.toHaveBeenCalled();
|
|
expect(result.channel).toBe("slack");
|
|
});
|
|
|
|
it("modifies text when hook returns content", async () => {
|
|
const mockRunner = {
|
|
hasHooks: vi.fn().mockReturnValue(true),
|
|
runMessageSending: vi.fn().mockResolvedValue({ content: "modified" }),
|
|
};
|
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as any);
|
|
|
|
await sendSlackTextWithDefaults({ text: "original" });
|
|
expectSlackSendCalledWith("modified");
|
|
});
|
|
|
|
it("skips hooks when runner has no message_sending hooks", async () => {
|
|
const mockRunner = {
|
|
hasHooks: vi.fn().mockReturnValue(false),
|
|
runMessageSending: vi.fn(),
|
|
};
|
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as any);
|
|
|
|
await sendSlackTextWithDefaults({ text: "hello" });
|
|
|
|
expect(mockRunner.runMessageSending).not.toHaveBeenCalled();
|
|
expect(sendMessageSlack).toHaveBeenCalled();
|
|
});
|
|
});
|