Files
DeskClaw/tests/unit/channel-routes.test.ts

239 lines
6.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { IncomingMessage, ServerResponse } from 'http';
const listConfiguredChannelsMock = vi.fn();
const listConfiguredChannelAccountsMock = vi.fn();
const readOpenClawConfigMock = vi.fn();
const listAgentsSnapshotMock = vi.fn();
const sendJsonMock = vi.fn();
vi.mock('@electron/utils/channel-config', () => ({
cleanupDanglingWeChatPluginState: vi.fn(),
deleteChannelAccountConfig: vi.fn(),
deleteChannelConfig: vi.fn(),
getChannelFormValues: vi.fn(),
listConfiguredChannelAccounts: (...args: unknown[]) => listConfiguredChannelAccountsMock(...args),
listConfiguredChannels: (...args: unknown[]) => listConfiguredChannelsMock(...args),
readOpenClawConfig: (...args: unknown[]) => readOpenClawConfigMock(...args),
saveChannelConfig: vi.fn(),
setChannelDefaultAccount: vi.fn(),
setChannelEnabled: vi.fn(),
validateChannelConfig: vi.fn(),
validateChannelCredentials: vi.fn(),
}));
vi.mock('@electron/utils/agent-config', () => ({
assignChannelAccountToAgent: vi.fn(),
clearAllBindingsForChannel: vi.fn(),
clearChannelBinding: vi.fn(),
listAgentsSnapshot: (...args: unknown[]) => listAgentsSnapshotMock(...args),
}));
vi.mock('@electron/utils/plugin-install', () => ({
ensureDingTalkPluginInstalled: vi.fn(),
ensureFeishuPluginInstalled: vi.fn(),
ensureQQBotPluginInstalled: vi.fn(),
ensureWeChatPluginInstalled: vi.fn(),
ensureWeComPluginInstalled: vi.fn(),
}));
vi.mock('@electron/utils/wechat-login', () => ({
cancelWeChatLoginSession: vi.fn(),
saveWeChatAccountState: vi.fn(),
startWeChatLoginSession: vi.fn(),
waitForWeChatLoginSession: vi.fn(),
}));
vi.mock('@electron/utils/whatsapp-login', () => ({
whatsAppLoginManager: {
start: vi.fn(),
stop: vi.fn(),
},
}));
vi.mock('@electron/api/route-utils', () => ({
parseJsonBody: vi.fn().mockResolvedValue({}),
sendJson: (...args: unknown[]) => sendJsonMock(...args),
}));
describe('handleChannelRoutes', () => {
beforeEach(() => {
vi.resetAllMocks();
listAgentsSnapshotMock.mockResolvedValue({
agents: [],
channelAccountOwners: {},
});
readOpenClawConfigMock.mockResolvedValue({
channels: {},
});
});
it('reports healthy running multi-account channels as connected', async () => {
listConfiguredChannelsMock.mockResolvedValue(['feishu']);
listConfiguredChannelAccountsMock.mockResolvedValue({
feishu: {
defaultAccountId: 'default',
accountIds: ['default', 'feishu-2412524e'],
},
});
readOpenClawConfigMock.mockResolvedValue({
channels: {
feishu: {
defaultAccount: 'default',
},
},
});
listAgentsSnapshotMock.mockResolvedValue({
agents: [],
channelAccountOwners: {
'feishu:default': 'main',
'feishu:feishu-2412524e': 'code',
},
});
const rpc = vi.fn().mockResolvedValue({
channels: {
feishu: {
configured: true,
},
},
channelAccounts: {
feishu: [
{
accountId: 'default',
configured: true,
connected: false,
running: true,
linked: false,
},
{
accountId: 'feishu-2412524e',
configured: true,
connected: false,
running: true,
linked: false,
},
],
},
channelDefaultAccountId: {
feishu: 'default',
},
});
const { handleChannelRoutes } = await import('@electron/api/routes/channels');
const handled = await handleChannelRoutes(
{ method: 'GET' } as IncomingMessage,
{} as ServerResponse,
new URL('http://127.0.0.1:3210/api/channels/accounts'),
{
gatewayManager: {
rpc,
getStatus: () => ({ state: 'running' }),
debouncedReload: vi.fn(),
debouncedRestart: vi.fn(),
},
} as never,
);
expect(handled).toBe(true);
expect(rpc).toHaveBeenCalledWith('channels.status', { probe: true });
expect(sendJsonMock).toHaveBeenCalledWith(
expect.anything(),
200,
expect.objectContaining({
success: true,
channels: [
expect.objectContaining({
channelType: 'feishu',
status: 'connected',
accounts: expect.arrayContaining([
expect.objectContaining({ accountId: 'default', status: 'connected' }),
expect.objectContaining({ accountId: 'feishu-2412524e', status: 'connected' }),
]),
}),
],
}),
);
});
it('keeps channel connected when one account is healthy and another errors', async () => {
listConfiguredChannelsMock.mockResolvedValue(['telegram']);
listConfiguredChannelAccountsMock.mockResolvedValue({
telegram: {
defaultAccountId: 'default',
accountIds: ['default', 'telegram-b'],
},
});
readOpenClawConfigMock.mockResolvedValue({
channels: {
telegram: {
defaultAccount: 'default',
},
},
});
const rpc = vi.fn().mockResolvedValue({
channels: {
telegram: {
configured: true,
},
},
channelAccounts: {
telegram: [
{
accountId: 'default',
configured: true,
connected: true,
running: true,
linked: false,
},
{
accountId: 'telegram-b',
configured: true,
connected: false,
running: false,
linked: false,
lastError: 'secondary bot failed',
},
],
},
channelDefaultAccountId: {
telegram: 'default',
},
});
const { handleChannelRoutes } = await import('@electron/api/routes/channels');
await handleChannelRoutes(
{ method: 'GET' } as IncomingMessage,
{} as ServerResponse,
new URL('http://127.0.0.1:3210/api/channels/accounts'),
{
gatewayManager: {
rpc,
getStatus: () => ({ state: 'running' }),
debouncedReload: vi.fn(),
debouncedRestart: vi.fn(),
},
} as never,
);
expect(sendJsonMock).toHaveBeenCalledWith(
expect.anything(),
200,
expect.objectContaining({
success: true,
channels: [
expect.objectContaining({
channelType: 'telegram',
status: 'connected',
accounts: expect.arrayContaining([
expect.objectContaining({ accountId: 'default', status: 'connected' }),
expect.objectContaining({ accountId: 'telegram-b', status: 'error' }),
]),
}),
],
}),
);
});
});