227 lines
7.5 KiB
TypeScript
227 lines
7.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { render, screen } from '@testing-library/react';
|
|
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
import { Settings } from '@/pages/Settings';
|
|
import { useGatewayStore } from '@/stores/gateway';
|
|
import { useSettingsStore } from '@/stores/settings';
|
|
import { useYinianStore } from '@/stores/yinian';
|
|
import i18n from '@/i18n';
|
|
|
|
const hostApiFetchMock = vi.fn();
|
|
|
|
vi.mock('@/components/settings/ProvidersSettings', () => ({
|
|
ProvidersSettings: () => <div data-testid="settings-advanced-model-config-panel">模型配置设置</div>,
|
|
}));
|
|
|
|
vi.mock('@/components/settings/AgentSystemDocumentsSettings', () => ({
|
|
AgentSystemDocumentsSettings: () => <div data-testid="settings-agent-system-documents-panel">系统文档管理</div>,
|
|
}));
|
|
|
|
vi.mock('@/components/settings/UpdateSettings', () => ({
|
|
UpdateSettings: () => null,
|
|
}));
|
|
|
|
vi.mock('@/pages/Channels', () => ({
|
|
Channels: () => null,
|
|
}));
|
|
|
|
vi.mock('@/pages/YinianSkills', () => ({
|
|
YinianSkills: () => null,
|
|
}));
|
|
|
|
vi.mock('@/lib/host-api', () => ({
|
|
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
|
|
}));
|
|
|
|
vi.mock('@/lib/api-client', () => ({
|
|
getGatewayWsDiagnosticEnabled: () => false,
|
|
invokeIpc: vi.fn().mockResolvedValue({ success: true, command: 'agent' }),
|
|
setGatewayWsDiagnosticEnabled: vi.fn(),
|
|
toUserMessage: (error: unknown) => String(error),
|
|
}));
|
|
|
|
vi.mock('@/lib/telemetry', () => ({
|
|
clearUiTelemetry: vi.fn(),
|
|
getUiTelemetrySnapshot: vi.fn(() => []),
|
|
subscribeUiTelemetry: vi.fn(() => () => undefined),
|
|
trackUiEvent: vi.fn(),
|
|
}));
|
|
|
|
const hotel = {
|
|
id: 'workspace-1',
|
|
name: '智念空间',
|
|
city: '杭州',
|
|
role: 'manager' as const,
|
|
permissions: [],
|
|
ota: [],
|
|
};
|
|
|
|
function renderSettings(initialEntry = '/settings/runtime') {
|
|
return render(
|
|
<MemoryRouter initialEntries={[initialEntry]}>
|
|
<Routes>
|
|
<Route path="/settings/*" element={<Settings />} />
|
|
</Routes>
|
|
</MemoryRouter>,
|
|
);
|
|
}
|
|
|
|
describe('Settings advanced model config', () => {
|
|
beforeEach(async () => {
|
|
vi.clearAllMocks();
|
|
await i18n.changeLanguage('zh');
|
|
hostApiFetchMock.mockImplementation((path: string) => {
|
|
if (path.startsWith('/api/diagnostics/model-config')) {
|
|
return Promise.resolve({
|
|
capturedAt: 1,
|
|
ok: true,
|
|
model: { primary: null, fallbacks: [], providerKey: null, modelId: null },
|
|
runtime: { heartbeatEvery: null, heartbeatDisabled: false },
|
|
providers: [],
|
|
authProfiles: { path: '', exists: false, providers: [] },
|
|
checks: [],
|
|
paths: { openclawConfig: '', authProfiles: '' },
|
|
});
|
|
}
|
|
if (path.startsWith('/api/diagnostics/office-runtime')) {
|
|
return Promise.resolve({
|
|
capturedAt: 1,
|
|
ok: true,
|
|
repairAttempted: false,
|
|
python: { executable: null, packages: [] },
|
|
node: { modules: [] },
|
|
dotnet: { available: false, version: null },
|
|
checks: [],
|
|
});
|
|
}
|
|
return Promise.resolve({});
|
|
});
|
|
useSettingsStore.setState({
|
|
devModeUnlocked: false,
|
|
gatewayAutoStart: false,
|
|
telemetryEnabled: true,
|
|
proxyEnabled: false,
|
|
proxyServer: '',
|
|
proxyHttpServer: '',
|
|
proxyHttpsServer: '',
|
|
proxyAllServer: '',
|
|
proxyBypassRules: '<local>',
|
|
});
|
|
useGatewayStore.setState({
|
|
status: { state: 'stopped', port: 18789, gatewayReady: false },
|
|
});
|
|
useYinianStore.setState({
|
|
status: 'authenticated',
|
|
session: {
|
|
authenticated: true,
|
|
user: { id: 'user-1', name: '王管理员' },
|
|
hotels: [hotel],
|
|
currentHotelId: hotel.id,
|
|
accessTokenExpiresAt: 100,
|
|
},
|
|
config: {
|
|
serverTime: 1,
|
|
user: { id: 'user-1', name: '王管理员' },
|
|
hotel,
|
|
hotels: [hotel],
|
|
entitlements: [],
|
|
notificationChannels: [],
|
|
featureFlags: {},
|
|
uiPolicy: { defaultPage: 'today', showAdvancedSettings: false },
|
|
},
|
|
error: null,
|
|
});
|
|
});
|
|
|
|
it('keeps model configuration settings inside advanced mode', async () => {
|
|
useSettingsStore.setState({ devModeUnlocked: true });
|
|
|
|
renderSettings();
|
|
|
|
expect(await screen.findByTestId('settings-model-config-section')).toBeVisible();
|
|
expect(screen.getByTestId('settings-advanced-model-config-panel')).toHaveTextContent('模型配置设置');
|
|
expect(screen.getByTestId('settings-model-diagnostics')).toBeVisible();
|
|
});
|
|
|
|
it('keeps model configuration settings hidden until advanced mode is opened', () => {
|
|
renderSettings();
|
|
|
|
expect(screen.queryByTestId('settings-model-config-section')).not.toBeInTheDocument();
|
|
expect(screen.queryByTestId('settings-advanced-model-config-panel')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('opens the agent system documents settings tab by route', () => {
|
|
renderSettings('/settings/system-docs');
|
|
|
|
expect(screen.getByText('系统文档')).toBeVisible();
|
|
expect(screen.getByTestId('settings-agent-system-documents-panel')).toHaveTextContent('系统文档管理');
|
|
});
|
|
|
|
it('shows only the current default model service in diagnostics', async () => {
|
|
useSettingsStore.setState({ devModeUnlocked: true });
|
|
hostApiFetchMock.mockImplementation((path: string) => {
|
|
if (path.startsWith('/api/diagnostics/model-config')) {
|
|
return Promise.resolve({
|
|
capturedAt: 1,
|
|
ok: true,
|
|
model: {
|
|
primary: 'deepseek/deepseek-v4-pro',
|
|
fallbacks: [],
|
|
providerKey: 'deepseek',
|
|
modelId: 'deepseek-v4-pro',
|
|
},
|
|
runtime: { heartbeatEvery: '0m', heartbeatDisabled: true },
|
|
providers: [{
|
|
key: 'deepseek',
|
|
configured: true,
|
|
baseUrl: 'https://api.deepseek.com/v1',
|
|
api: 'openai-completions',
|
|
timeoutSeconds: null,
|
|
authHeader: null,
|
|
modelCount: 1,
|
|
}],
|
|
authProfiles: {
|
|
path: '',
|
|
exists: true,
|
|
providers: [{
|
|
provider: 'deepseek',
|
|
profileCount: 1,
|
|
profileIds: ['deepseek:default'],
|
|
types: ['api_key'],
|
|
hasDefaultProfile: true,
|
|
lastGoodProfileId: 'deepseek:default',
|
|
}],
|
|
},
|
|
checks: [
|
|
{ id: 'primary-model', label: '默认模型', status: 'ok', detail: 'deepseek/deepseek-v4-pro' },
|
|
{ id: 'provider-entry', label: '模型服务', status: 'ok', detail: '已配置 deepseek' },
|
|
{ id: 'auth-profile', label: '调用凭据', status: 'ok', detail: '已找到 deepseek 的本地凭据' },
|
|
],
|
|
paths: { openclawConfig: '', authProfiles: '' },
|
|
});
|
|
}
|
|
if (path.startsWith('/api/diagnostics/office-runtime')) {
|
|
return Promise.resolve({
|
|
capturedAt: 1,
|
|
ok: true,
|
|
repairAttempted: false,
|
|
python: { executable: null, packages: [] },
|
|
node: { modules: [] },
|
|
dotnet: { available: false, version: null },
|
|
checks: [],
|
|
});
|
|
}
|
|
return Promise.resolve({});
|
|
});
|
|
|
|
renderSettings();
|
|
|
|
expect(await screen.findByText('deepseek/deepseek-v4-pro')).toBeVisible();
|
|
expect(screen.getByText('当前模型服务')).toBeVisible();
|
|
expect(screen.getAllByText('deepseek').length).toBeGreaterThan(0);
|
|
expect(screen.queryByText('yinian-model')).not.toBeInTheDocument();
|
|
expect(screen.queryByText('minimax')).not.toBeInTheDocument();
|
|
expect(screen.queryByText('minimax-portal')).not.toBeInTheDocument();
|
|
});
|
|
});
|