UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

290 lines (243 loc) 9.08 kB
/** * Tests for send.ts module * * Tests cover: * - Message sending with valid configuration * - Account resolution and validation * - Channel normalization * - Markdown stripping * - Error handling for missing/invalid accounts * - Registry integration */ import type { OpenClawConfig } from "openclaw/plugin-sdk"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { sendMessageTwitchInternal } from "./send.js"; // Mock dependencies vi.mock("./config.js", () => ({ DEFAULT_ACCOUNT_ID: "default", getAccountConfig: vi.fn(), })); vi.mock("./utils/twitch.js", () => ({ generateMessageId: vi.fn(() => "test-msg-id"), isAccountConfigured: vi.fn(() => true), normalizeTwitchChannel: (channel: string) => channel.toLowerCase().replace(/^#/, ""), })); vi.mock("./utils/markdown.js", () => ({ stripMarkdownForTwitch: vi.fn((text: string) => text.replace(/\*\*/g, "")), })); vi.mock("./client-manager-registry.js", () => ({ getClientManager: vi.fn(), })); describe("send", () => { const mockLogger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), }; const mockAccount = { username: "testbot", token: "oauth:test123", clientId: "test-client-id", channel: "#testchannel", }; const mockConfig = { channels: { twitch: { accounts: { default: mockAccount, }, }, }, } as unknown as OpenClawConfig; beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { vi.restoreAllMocks(); }); describe("sendMessageTwitchInternal", () => { it("should send a message successfully", async () => { const { getAccountConfig } = await import("./config.js"); const { getClientManager } = await import("./client-manager-registry.js"); const { stripMarkdownForTwitch } = await import("./utils/markdown.js"); vi.mocked(getAccountConfig).mockReturnValue(mockAccount); vi.mocked(getClientManager).mockReturnValue({ sendMessage: vi.fn().mockResolvedValue({ ok: true, messageId: "twitch-msg-123", }), } as ReturnType<typeof getClientManager>); vi.mocked(stripMarkdownForTwitch).mockImplementation((text) => text); const result = await sendMessageTwitchInternal( "#testchannel", "Hello Twitch!", mockConfig, "default", false, mockLogger as unknown as Console, ); expect(result.ok).toBe(true); expect(result.messageId).toBe("twitch-msg-123"); }); it("should strip markdown when enabled", async () => { const { getAccountConfig } = await import("./config.js"); const { getClientManager } = await import("./client-manager-registry.js"); const { stripMarkdownForTwitch } = await import("./utils/markdown.js"); vi.mocked(getAccountConfig).mockReturnValue(mockAccount); vi.mocked(getClientManager).mockReturnValue({ sendMessage: vi.fn().mockResolvedValue({ ok: true, messageId: "twitch-msg-456", }), } as ReturnType<typeof getClientManager>); vi.mocked(stripMarkdownForTwitch).mockImplementation((text) => text.replace(/\*\*/g, "")); await sendMessageTwitchInternal( "#testchannel", "**Bold** text", mockConfig, "default", true, mockLogger as unknown as Console, ); expect(stripMarkdownForTwitch).toHaveBeenCalledWith("**Bold** text"); }); it("should return error when account not found", async () => { const { getAccountConfig } = await import("./config.js"); vi.mocked(getAccountConfig).mockReturnValue(null); const result = await sendMessageTwitchInternal( "#testchannel", "Hello!", mockConfig, "nonexistent", false, mockLogger as unknown as Console, ); expect(result.ok).toBe(false); expect(result.error).toContain("Account not found: nonexistent"); }); it("should return error when account not configured", async () => { const { getAccountConfig } = await import("./config.js"); const { isAccountConfigured } = await import("./utils/twitch.js"); vi.mocked(getAccountConfig).mockReturnValue(mockAccount); vi.mocked(isAccountConfigured).mockReturnValue(false); const result = await sendMessageTwitchInternal( "#testchannel", "Hello!", mockConfig, "default", false, mockLogger as unknown as Console, ); expect(result.ok).toBe(false); expect(result.error).toContain("not properly configured"); }); it("should return error when no channel specified", async () => { const { getAccountConfig } = await import("./config.js"); const { isAccountConfigured } = await import("./utils/twitch.js"); // Set channel to undefined to trigger the error (bypassing type check) const accountWithoutChannel = { ...mockAccount, channel: undefined as unknown as string, }; vi.mocked(getAccountConfig).mockReturnValue(accountWithoutChannel); vi.mocked(isAccountConfigured).mockReturnValue(true); const result = await sendMessageTwitchInternal( "", "Hello!", mockConfig, "default", false, mockLogger as unknown as Console, ); expect(result.ok).toBe(false); expect(result.error).toContain("No channel specified"); }); it("should skip sending empty message after markdown stripping", async () => { const { getAccountConfig } = await import("./config.js"); const { isAccountConfigured } = await import("./utils/twitch.js"); const { stripMarkdownForTwitch } = await import("./utils/markdown.js"); vi.mocked(getAccountConfig).mockReturnValue(mockAccount); vi.mocked(isAccountConfigured).mockReturnValue(true); vi.mocked(stripMarkdownForTwitch).mockReturnValue(""); const result = await sendMessageTwitchInternal( "#testchannel", "**Only markdown**", mockConfig, "default", true, mockLogger as unknown as Console, ); expect(result.ok).toBe(true); expect(result.messageId).toBe("skipped"); }); it("should return error when client manager not found", async () => { const { getAccountConfig } = await import("./config.js"); const { isAccountConfigured } = await import("./utils/twitch.js"); const { getClientManager } = await import("./client-manager-registry.js"); vi.mocked(getAccountConfig).mockReturnValue(mockAccount); vi.mocked(isAccountConfigured).mockReturnValue(true); vi.mocked(getClientManager).mockReturnValue(undefined); const result = await sendMessageTwitchInternal( "#testchannel", "Hello!", mockConfig, "default", false, mockLogger as unknown as Console, ); expect(result.ok).toBe(false); expect(result.error).toContain("Client manager not found"); }); it("should handle send errors gracefully", async () => { const { getAccountConfig } = await import("./config.js"); const { isAccountConfigured } = await import("./utils/twitch.js"); const { getClientManager } = await import("./client-manager-registry.js"); vi.mocked(getAccountConfig).mockReturnValue(mockAccount); vi.mocked(isAccountConfigured).mockReturnValue(true); vi.mocked(getClientManager).mockReturnValue({ sendMessage: vi.fn().mockRejectedValue(new Error("Connection lost")), } as ReturnType<typeof getClientManager>); const result = await sendMessageTwitchInternal( "#testchannel", "Hello!", mockConfig, "default", false, mockLogger as unknown as Console, ); expect(result.ok).toBe(false); expect(result.error).toBe("Connection lost"); expect(mockLogger.error).toHaveBeenCalled(); }); it("should use account channel when channel parameter is empty", async () => { const { getAccountConfig } = await import("./config.js"); const { isAccountConfigured } = await import("./utils/twitch.js"); const { getClientManager } = await import("./client-manager-registry.js"); vi.mocked(getAccountConfig).mockReturnValue(mockAccount); vi.mocked(isAccountConfigured).mockReturnValue(true); const mockSend = vi.fn().mockResolvedValue({ ok: true, messageId: "twitch-msg-789", }); vi.mocked(getClientManager).mockReturnValue({ sendMessage: mockSend, } as ReturnType<typeof getClientManager>); await sendMessageTwitchInternal( "", "Hello!", mockConfig, "default", false, mockLogger as unknown as Console, ); expect(mockSend).toHaveBeenCalledWith( mockAccount, "testchannel", // normalized account channel "Hello!", mockConfig, "default", ); }); }); });