64 lines
3.7 KiB
TypeScript
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();
|
|
});
|