import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { CloudResources } from '@/pages/CloudAgents/CloudResources'; import { CloudLifecycle } from '@/pages/CloudAgents/CloudLifecycle'; import { EMPTY_CLOUD_CONFIGURATION } from '../../shared/cloud-agents'; const api = vi.hoisted(() => ({ call: vi.fn(), get: vi.fn(), uploadSkill: vi.fn() })); vi.mock('@/lib/cloud-agents-api', () => ({ cloudAgentsApi: api })); const slug = 'ml-' + 'a'.repeat(32); const draft = { slug, name: '当前名称', purpose: '创作', system_prompt: '当前指令', configuration: EMPTY_CLOUD_CONFIGURATION, draft_revision: 4, published_version: 2, enabled: true, archived: false, updated_at: '2026-09-10T00:00:00Z' }; beforeEach(() => vi.resetAllMocks()); afterEach(cleanup); it('keeps existing MCP credentials when editing, and clears them only when explicitly selected', async () => { const mcp = { slug: 'personal-mcp', name: '私人资料', description: '', url: 'https://example.test/mcp', transport: 'streamable_http', enabled: true, has_credentials: true }; api.call.mockImplementation(async operation => operation === 'resources' ? { mcps: [mcp], skills: [], subagents: [] } : mcp); render( undefined} />); fireEvent.click(screen.getByText('管理我的云端能力')); fireEvent.click(await screen.findByRole('button', { name: '编辑连接' })); expect(screen.getByLabelText('Bearer 凭据')).toHaveValue(''); fireEvent.change(screen.getByLabelText('连接名称'), { target: { value: '新名称' } }); fireEvent.click(screen.getByRole('button', { name: '保存连接' })); await waitFor(() => expect(screen.queryByLabelText('连接名称')).not.toBeInTheDocument()); expect(api.call.mock.calls.find(call => call[0] === 'updateMcp')?.[1]).toMatchObject({ key: 'personal-mcp', name: '新名称', headers: null }); fireEvent.click(screen.getByRole('button', { name: '编辑连接' })); fireEvent.click(screen.getByLabelText('清除已有凭据')); fireEvent.click(screen.getByRole('button', { name: '保存连接' })); await waitFor(() => expect(api.call.mock.calls.filter(call => call[0] === 'updateMcp')).toHaveLength(2)); expect(api.call.mock.calls.filter(call => call[0] === 'updateMcp')[1][1].headers).toEqual({}); }); it('keeps one child creation intent after a lost response and waits for Skill installation confirmation', async () => { let attempt = 0; api.call.mockImplementation(async operation => { if (operation === 'resources') return { mcps: [], skills: [], subagents: [] }; if (operation === 'createChild') { if (++attempt === 1) throw new Error('超时'); return { slug: 'child' }; } if (operation === 'confirmSkill') return { items: [{ slug: 'writer', success: true }] }; throw new Error(operation); }); api.uploadSkill.mockResolvedValue({ draft_id: 'draft', items: [{ slug: 'writer', name: '写作', description: '辅助写作' }] }); render( undefined} />); fireEvent.click(screen.getByText('管理我的云端能力')); await waitFor(() => expect(api.call).toHaveBeenCalledWith('resources', {})); await waitFor(() => expect(screen.getByRole('button', { name: '创建子智能体' })).toBeEnabled()); fireEvent.click(screen.getByRole('button', { name: '创建子智能体' })); fireEvent.change(screen.getByLabelText('子智能体名称'), { target: { value: '核对' } }); fireEvent.change(screen.getByLabelText('子智能体用途'), { target: { value: '核对事实' } }); fireEvent.change(screen.getByLabelText('子智能体指令'), { target: { value: '检查材料' } }); fireEvent.click(screen.getByRole('button', { name: '保存子智能体' })); fireEvent.click(await screen.findByRole('button', { name: '重试保存子智能体' })); await waitFor(() => expect(screen.queryByLabelText('子智能体名称')).not.toBeInTheDocument()); const calls = api.call.mock.calls.filter(call => call[0] === 'createChild'); expect(calls).toHaveLength(2); expect(calls[0][1]).toEqual(calls[1][1]); fireEvent.click(screen.getByRole('button', { name: '上传 Skill' })); const confirm = await screen.findByRole('button', { name: '确认安装到个人能力' }); expect(api.call.mock.calls.some(call => call[0] === 'confirmSkill')).toBe(false); fireEvent.click(confirm); await waitFor(() => expect(api.call).toHaveBeenCalledWith('confirmSkill', { draft_id: 'draft' })); }); it('compares an old publication, restores only a draft and requires confirmation to archive', async () => { api.get.mockResolvedValue(draft); api.call.mockImplementation(async operation => { if (operation === 'version') return { ...draft, version: 1, name: '旧名称', system_prompt: '旧指令' }; if (operation === 'restoreVersion') return { ...draft, draft_revision: 5, name: '旧名称' }; if (operation === 'archiveAgent') return { ...draft, archived: true, enabled: false }; throw new Error(operation); }); const changed = vi.fn(); render(); fireEvent.click(screen.getByRole('button', { name: '比较版本 1' })); expect(await screen.findByText('旧指令')).toBeVisible(); fireEvent.click(screen.getByRole('button', { name: '恢复为新草稿' })); await waitFor(() => expect(changed).toHaveBeenCalledWith(expect.objectContaining({ draft_revision: 5, published_version: 2 }))); expect(api.call).toHaveBeenCalledWith('restoreVersion', { slug, version: '1', expected_revision: 4 }); expect(api.call.mock.calls.some(call => call[0] === 'publish')).toBe(false); fireEvent.click(screen.getByRole('button', { name: '管理归档' })); const confirm = await screen.findByRole('button', { name: '确认归档' }); expect(api.call.mock.calls.some(call => call[0] === 'archiveAgent')).toBe(false); fireEvent.click(confirm); await waitFor(() => expect(changed).toHaveBeenCalledWith(expect.objectContaining({ archived: true, enabled: false }))); });