mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-21 15:01:03 +00:00
256 lines
7.1 KiB
TypeScript
256 lines
7.1 KiB
TypeScript
/**
|
|
* 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 { describe, expect, it, vi } from "vitest";
|
|
import { getClientManager } from "./client-manager-registry.js";
|
|
import { getAccountConfig } from "./config.js";
|
|
import { sendMessageTwitchInternal } from "./send.js";
|
|
import {
|
|
BASE_TWITCH_TEST_ACCOUNT,
|
|
installTwitchTestHooks,
|
|
makeTwitchTestConfig,
|
|
} from "./test-fixtures.js";
|
|
import { stripMarkdownForTwitch } from "./utils/markdown.js";
|
|
import { isAccountConfigured } from "./utils/twitch.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 = {
|
|
...BASE_TWITCH_TEST_ACCOUNT,
|
|
accessToken: "test123",
|
|
};
|
|
|
|
const mockConfig = makeTwitchTestConfig(mockAccount);
|
|
installTwitchTestHooks();
|
|
|
|
describe("sendMessageTwitchInternal", () => {
|
|
function setupBaseAccount(params?: { configured?: boolean }) {
|
|
vi.mocked(getAccountConfig).mockReturnValue(mockAccount);
|
|
vi.mocked(isAccountConfigured).mockReturnValue(params?.configured ?? true);
|
|
}
|
|
|
|
async function mockSuccessfulSend(params: {
|
|
messageId: string;
|
|
stripMarkdown?: (text: string) => string;
|
|
}) {
|
|
setupBaseAccount();
|
|
vi.mocked(getClientManager).mockReturnValue({
|
|
sendMessage: vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
messageId: params.messageId,
|
|
}),
|
|
} as unknown as ReturnType<typeof getClientManager>);
|
|
vi.mocked(stripMarkdownForTwitch).mockImplementation(
|
|
params.stripMarkdown ?? ((text) => text),
|
|
);
|
|
|
|
return { stripMarkdownForTwitch };
|
|
}
|
|
|
|
it("should send a message successfully", async () => {
|
|
await mockSuccessfulSend({ messageId: "twitch-msg-123" });
|
|
|
|
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 { stripMarkdownForTwitch } = await mockSuccessfulSend({
|
|
messageId: "twitch-msg-456",
|
|
stripMarkdown: (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 () => {
|
|
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 () => {
|
|
setupBaseAccount({ configured: 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 () => {
|
|
// 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 () => {
|
|
setupBaseAccount();
|
|
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 () => {
|
|
setupBaseAccount();
|
|
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 () => {
|
|
setupBaseAccount();
|
|
vi.mocked(getClientManager).mockReturnValue({
|
|
sendMessage: vi.fn().mockRejectedValue(new Error("Connection lost")),
|
|
} as unknown 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 () => {
|
|
setupBaseAccount();
|
|
const mockSend = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
messageId: "twitch-msg-789",
|
|
});
|
|
vi.mocked(getClientManager).mockReturnValue({
|
|
sendMessage: mockSend,
|
|
} as unknown 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",
|
|
);
|
|
});
|
|
});
|
|
});
|