import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createProviderFromPreset } from '../../src/ai/providerPresets' import AiProviderSettings from '../../src/components/settings/AiProviderSettings' import { useAiProviderStore } from '../../src/stores/aiProviderStore' import { mockDesktopApi } from '../setup' describe('AI provider settings', () => { beforeEach(() => { vi.clearAllMocks() useAiProviderStore.setState({ providers: [], activeProviderId: '', isLoaded: true, catalogProviders: [], catalogUpdatedAt: null, }) vi.mocked(mockDesktopApi.aiTestProvider).mockResolvedValue({ success: true, status: 200, model: 'deepseek-chat', latencyMs: 92, answer: 'OK', }) }) it('keeps a new provider transient until a real connection test passes', async () => { render() fireEvent.click(screen.getByRole('button', { name: '添加' })) expect(useAiProviderStore.getState().providers).toHaveLength(0) expect(mockDesktopApi.settingsSet).not.toHaveBeenCalled() const save = screen.getByRole('button', { name: '保存' }) expect(save).toBeDisabled() fireEvent.change(screen.getByLabelText('API 密钥'), { target: { value: 'sk-test-only' } }) fireEvent.click(screen.getByRole('button', { name: '测试' })) await waitFor(() => expect(mockDesktopApi.aiTestProvider).toHaveBeenCalled()) expect(mockDesktopApi.aiProviderSaveKey).not.toHaveBeenCalled() expect(await screen.findByText(/可用/)).toBeVisible() expect(save).toBeEnabled() expect(screen.queryByRole('button', { name: '测试' })).not.toBeInTheDocument() fireEvent.click(save) await waitFor(() => expect(useAiProviderStore.getState().providers).toHaveLength(1)) await waitFor(() => expect(screen.queryByRole('button', { name: '保存' })).not.toBeInTheDocument()) expect(screen.queryByRole('button', { name: '测试' })).not.toBeInTheDocument() expect(screen.queryByText(/可用/)).not.toBeInTheDocument() expect(mockDesktopApi.aiProviderSaveKey).toHaveBeenCalledWith(expect.any(String), 'sk-test-only') expect(JSON.stringify(vi.mocked(mockDesktopApi.settingsSet).mock.calls)).not.toContain('sk-test-only') }) it('invalidates a successful test whenever a connection field changes', async () => { render() fireEvent.click(screen.getByRole('button', { name: '添加' })) fireEvent.change(screen.getByLabelText('API 密钥'), { target: { value: 'sk-test-only' } }) fireEvent.click(screen.getByRole('button', { name: '测试' })) const save = screen.getByRole('button', { name: '保存' }) await waitFor(() => expect(save).toBeEnabled()) fireEvent.click(screen.getByRole('button', { name: '高级' })) fireEvent.change(screen.getByLabelText('接口地址'), { target: { value: 'https://proxy.example.com/v1' } }) expect(save).toBeDisabled() expect(screen.getByText('请重新测试')).toBeVisible() }) function loadSavedProvider() { const provider = { ...createProviderFromPreset('deepseek'), hasApiKey: true } useAiProviderStore.setState({ providers: [provider], activeProviderId: '' }) return provider } it('hides clean actions, saves name changes without testing, and offers a default action', async () => { loadSavedProvider() render() expect(screen.queryByRole('button', { name: '测试' })).not.toBeInTheDocument() expect(screen.queryByRole('button', { name: '保存' })).not.toBeInTheDocument() expect(screen.getByRole('button', { name: '设为默认' })).toBeVisible() fireEvent.change(screen.getByLabelText('名称'), { target: { value: 'Work' } }) expect(screen.queryByRole('button', { name: '测试' })).not.toBeInTheDocument() expect(screen.queryByRole('button', { name: '设为默认' })).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: '保存' })) await waitFor(() => expect(screen.queryByRole('button', { name: '保存' })).not.toBeInTheDocument()) expect(useAiProviderStore.getState().providers[0].name).toBe('Work') expect(mockDesktopApi.aiTestProvider).not.toHaveBeenCalled() }) it('requires validation for token edits and returns to clean state on revert or cancel', () => { const provider = loadSavedProvider() render() fireEvent.change(screen.getByLabelText('API 密钥'), { target: { value: 'sk-new' } }) expect(screen.getByRole('button', { name: '测试' })).toBeEnabled() expect(screen.getByRole('button', { name: '保存' })).toBeDisabled() fireEvent.change(screen.getByLabelText('API 密钥'), { target: { value: '' } }) expect(screen.queryByRole('button', { name: '保存' })).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: '高级' })) fireEvent.change(screen.getByLabelText('接口地址'), { target: { value: 'https://proxy.example.com/v1' } }) expect(screen.getByRole('button', { name: '保存' })).toBeDisabled() fireEvent.click(screen.getByRole('button', { name: '取消修改' })) expect(screen.queryByRole('button', { name: '保存' })).not.toBeInTheDocument() expect(useAiProviderStore.getState().providers[0]).toEqual(provider) }) it('keeps manual connection checks in advanced settings without requiring another save', async () => { loadSavedProvider() render() fireEvent.click(screen.getByRole('button', { name: '高级' })) fireEvent.click(screen.getByRole('button', { name: '重新测试连接' })) expect(await screen.findByText(/可用/)).toBeVisible() expect(screen.queryByRole('button', { name: '保存' })).not.toBeInTheDocument() expect(mockDesktopApi.settingsSet).not.toHaveBeenCalled() }) it('keeps edits available when saving fails', async () => { loadSavedProvider() vi.mocked(mockDesktopApi.settingsSet).mockRejectedValueOnce(new Error('Write failed')) render() fireEvent.change(screen.getByLabelText('名称'), { target: { value: 'Unsaved name' } }) fireEvent.click(screen.getByRole('button', { name: '保存' })) expect(await screen.findByRole('alert')).toHaveTextContent('Write failed') expect(screen.getByRole('button', { name: '保存' })).toBeEnabled() expect(screen.getByLabelText('名称')).toHaveValue('Unsaved name') expect(useAiProviderStore.getState().providers[0].name).toBe('DeepSeek') }) })