Files
iofficeai--aionui/tests/unit/renderer/AgentRepairPanel.dom.test.tsx
2026-07-13 13:20:22 +08:00

238 lines
7.7 KiB
TypeScript

/**
* @license
* Copyright 2025 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { Message } from '@arco-design/web-react';
import AgentRepairPanel from '@/renderer/pages/settings/AgentSettings/AgentRepairPanel';
import type { ManagedAgent } from '@/renderer/utils/model/agentTypes';
import { acpConversation } from '@/common/adapter/ipcBridge';
vi.mock('@/common/adapter/ipcBridge', () => ({
acpConversation: {
getAgentOverrides: {
invoke: vi.fn(),
},
setAgentOverrides: {
invoke: vi.fn(),
},
},
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
describe('AgentRepairPanel', () => {
const mockAgent: ManagedAgent = {
id: 'test-agent-1',
name: 'Test Agent',
agent_type: 'acp',
agent_source: 'custom',
command: '/usr/local/bin/test-cli',
enabled: true,
installed: true,
status: 'offline',
env_override_key_count: 2,
has_command_override: true,
};
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(Message, 'success').mockImplementation(() => undefined as never);
});
it('loads current overrides on mount without an unlock step', async () => {
const getMock = vi.mocked(acpConversation.getAgentOverrides.invoke);
getMock.mockResolvedValue({
command_override: '/custom/path/cli',
env_override: [{ name: 'API_KEY', value: 'secret123' }],
});
const onSaved = vi.fn();
render(<AgentRepairPanel agent={mockAgent} onSaved={onSaved} />);
await waitFor(() => {
expect(getMock).toHaveBeenCalledWith({ id: 'test-agent-1' });
});
await waitFor(() => {
expect(screen.getByPlaceholderText(/repair\.pathPlaceholder/)).toHaveValue('/custom/path/cli');
});
});
it('saves overrides then triggers test connection once', async () => {
const user = userEvent.setup();
const getMock = vi.mocked(acpConversation.getAgentOverrides.invoke);
const setMock = vi.mocked(acpConversation.setAgentOverrides.invoke);
const onSaved = vi.fn();
getMock.mockResolvedValue({
command_override: '/custom/path/cli',
env_override: [
{ name: 'API_KEY', value: 'secret123' },
{ name: 'FACTORY_URL', value: 'http://localhost:8080' },
],
});
setMock.mockResolvedValue({
...mockAgent,
status: 'online',
has_command_override: true,
env_override_key_count: 2,
});
render(<AgentRepairPanel agent={mockAgent} onSaved={onSaved} />);
// Overrides load on mount — wait for the path input to fill.
await waitFor(() => {
expect(getMock).toHaveBeenCalledWith({ id: 'test-agent-1' });
});
await waitFor(() => {
const pathInput = screen.getByPlaceholderText(/repair\.pathPlaceholder/);
expect(pathInput).toHaveValue('/custom/path/cli');
});
// Change path
const pathInput = screen.getByPlaceholderText(/repair\.pathPlaceholder/);
await user.clear(pathInput);
await user.type(pathInput, '/new/path/cli');
// Save
const saveButton = screen.getByRole('button', { name: /repair\.saveAndTest/ });
await user.click(saveButton);
await waitFor(() => {
expect(setMock).toHaveBeenCalledWith({
id: 'test-agent-1',
command_override: '/new/path/cli',
env_override: [
{ name: 'API_KEY', value: 'secret123' },
{ name: 'FACTORY_URL', value: 'http://localhost:8080' },
],
});
expect(Message.success).toHaveBeenCalledWith('settings.agentManagement.testConnectionOnline');
expect(onSaved).toHaveBeenCalledTimes(1);
});
});
it('blocks save on duplicate env keys', async () => {
const user = userEvent.setup();
const getMock = vi.mocked(acpConversation.getAgentOverrides.invoke);
const setMock = vi.mocked(acpConversation.setAgentOverrides.invoke);
const onSaved = vi.fn();
getMock.mockResolvedValue({
env_override: [
{ name: 'API_KEY', value: 'secret1' },
{ name: 'API_KEY', value: 'secret2' },
],
});
render(<AgentRepairPanel agent={mockAgent} onSaved={onSaved} />);
// Wait for the mount-time load to populate the duplicate env rows.
await waitFor(() => {
expect(getMock).toHaveBeenCalledWith({ id: 'test-agent-1' });
});
// Try to save
const saveButton = screen.getByRole('button', { name: /repair\.saveAndTest/ });
await user.click(saveButton);
// Should show error and not call setAgentOverrides
await waitFor(() => {
expect(screen.getByText(/repair\.duplicateKeysError/)).toBeInTheDocument();
});
expect(setMock).not.toHaveBeenCalled();
expect(onSaved).not.toHaveBeenCalled();
});
it('does not save when no override value was entered', async () => {
const user = userEvent.setup();
const getMock = vi.mocked(acpConversation.getAgentOverrides.invoke);
const setMock = vi.mocked(acpConversation.setAgentOverrides.invoke);
const onSaved = vi.fn();
getMock.mockResolvedValue({});
render(<AgentRepairPanel agent={mockAgent} onSaved={onSaved} />);
await waitFor(() => {
expect(getMock).toHaveBeenCalledWith({ id: 'test-agent-1' });
});
const saveButton = screen.getByRole('button', { name: /repair\.saveAndTest/ });
await user.click(saveButton);
await waitFor(() => {
expect(screen.getByText(/repair\.emptyOverridesError/)).toBeInTheDocument();
});
expect(setMock).not.toHaveBeenCalled();
expect(onSaved).not.toHaveBeenCalled();
});
it('shows the offline diagnostic banner and the launch path for an offline agent', async () => {
vi.mocked(acpConversation.getAgentOverrides.invoke).mockResolvedValue({});
render(<AgentRepairPanel agent={mockAgent} onSaved={vi.fn()} />);
await waitFor(() => {
expect(screen.getByText('settings.repair.offlineTitle')).toBeInTheDocument();
});
// Non-online agents expose the launch path as the primary lever.
expect(screen.getByText('settings.repair.pathLabel')).toBeInTheDocument();
});
it('shows the online banner and hides the launch path entirely when online', async () => {
vi.mocked(acpConversation.getAgentOverrides.invoke).mockResolvedValue({});
render(<AgentRepairPanel agent={{ ...mockAgent, status: 'online' }} onSaved={vi.fn()} />);
await waitFor(() => {
expect(screen.getByText('settings.repair.onlineTitle')).toBeInTheDocument();
});
// Online: only environment variables are shown; the launch path is hidden.
expect(screen.getByText('settings.repair.envLabel')).toBeInTheDocument();
expect(screen.queryByText('settings.repair.pathLabel')).toBeNull();
});
it('does not expose override editing for the internal Aion CLI agent', async () => {
vi.mocked(acpConversation.getAgentOverrides.invoke).mockResolvedValue({
command_override: '/bad/path',
env_override: [{ name: 'ANTHROPIC_API_KEY', value: 'sk-x' }],
});
render(
<AgentRepairPanel
agent={{
...mockAgent,
id: '632f31d2',
name: 'Aion CLI',
agent_type: 'aionrs',
agent_source: 'internal',
status: 'online',
}}
onSaved={vi.fn()}
/>
);
await waitFor(() => {
expect(screen.getByText('settings.repair.onlineTitle')).toBeInTheDocument();
});
expect(acpConversation.getAgentOverrides.invoke).not.toHaveBeenCalled();
expect(screen.queryByText('settings.repair.pathLabel')).toBeNull();
expect(screen.queryByText('settings.repair.envLabel')).toBeNull();
expect(screen.queryByRole('button', { name: /repair\.saveAndTest/ })).toBeNull();
});
});