Files
makelore/tests/unit/cloud-channel-panel.test.tsx

64 lines
3.7 KiB
TypeScript

import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { CloudChannelPanel } from '@/pages/CloudAgents/CloudChannelPanel';
const api = vi.hoisted(() => ({ call: vi.fn(), recovery: vi.fn(), resolvePending: vi.fn() }));
vi.mock('@/lib/cloud-agents-api', () => ({ cloudAgentsApi: api }));
vi.mock('@/pages/CloudAgents/CloudChannelConversations', () => ({
CloudChannelConversations: ({ slug, channelAccountId }: { slug: string; channelAccountId: string }) => <div> {slug} {channelAccountId}</div>,
}));
const slug = 'ml-' + 'a'.repeat(32);
const accountId = 'wechat-1';
beforeEach(() => {
vi.resetAllMocks();
api.recovery.mockResolvedValue({ recent: null, pending: [] });
api.call.mockImplementation(async (operation: string) => {
if (operation === 'channelActivity') return { items: [], next_offset: null };
throw new Error(`Unexpected operation ${operation}`);
});
});
afterEach(cleanup);
it('shows only account activity and WeChat conversations without invitation or audience controls', async () => {
render(<CloudChannelPanel slug={slug} accountId={accountId} />);
await screen.findByText('还没有渠道活动。');
expect(screen.getByRole('button', { name: '活动记录' })).toBeVisible();
expect(screen.getByRole('button', { name: '微信对话' })).toBeVisible();
expect(screen.queryByText(/本人|受邀|邀请|使用范围|使用权限|授权调用者/)).toBeNull();
expect(api.call).toHaveBeenCalledWith('channelActivity', { slug, channel_account_id: accountId, offset: 0, limit: 50 });
expect(api.call.mock.calls.some(call => ['channelPolicy', 'createChannelPairing', 'channelCallers', 'revokeChannelCaller'].includes(call[0]))).toBe(false);
});
it('opens the account-scoped WeChat conversation surface', async () => {
render(<CloudChannelPanel slug={slug} accountId={accountId} />);
fireEvent.click(await screen.findByRole('button', { name: '微信对话' }));
expect(screen.getByText(`微信会话 ${slug} ${accountId}`)).toBeVisible();
});
it('looks up delivery by result message ID and keeps file retry account-scoped', async () => {
const activity = { run_id: 'run-1', request_id: 'request-1', status: 'completed', created_at: '2026-09-13T00:00:00Z', delivery: { state: 'known', result: { message_id: 'result-message-1' } } };
api.call.mockImplementation(async (operation: string) => {
if (operation === 'channelActivity') return { items: [activity], next_offset: null };
if (operation === 'channelDelivery') return { message_id: 'result-message-1', status: 'partial', parts: [{ part_id: 'part-1', type: 'file', status: 'failed' }] };
if (operation === 'retryChannelDeliveryPart') return { message_id: 'result-message-1', status: 'queued', parts: [] };
throw new Error(`Unexpected operation ${operation}`);
});
render(<CloudChannelPanel slug={slug} accountId={accountId} />);
fireEvent.click(await screen.findByRole('button', { name: '重新补发文件' }));
await waitFor(() => expect(api.call).toHaveBeenCalledWith('retryChannelDeliveryPart', {
slug, channel_account_id: accountId, logical_message_id: 'result-message-1', part_id: 'part-1',
}));
});
it('ignores retired recovery entries so they cannot block account file operations', async () => {
api.recovery.mockResolvedValue({ recent: null, pending: [{
id: 'channelPolicy:old', operation: 'channelPolicy', created_at: '2026-09-13T00:00:00Z', input: { slug, channel_account_id: accountId },
}] });
render(<CloudChannelPanel slug={slug} accountId={accountId} />);
await screen.findByText('还没有渠道活动。');
expect(screen.queryByText(/结果尚未确认/)).toBeNull();
expect(api.resolvePending).not.toHaveBeenCalled();
});