84 lines
6.0 KiB
TypeScript
84 lines
6.0 KiB
TypeScript
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(<CloudResources onChanged={() => 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(<CloudResources onChanged={() => 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(<CloudLifecycle slug={slug} revision={4} versions={[1]} onChanged={changed} />);
|
|
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 })));
|
|
});
|