import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { OpenAISettings } from '../../../src/renderer/pages/settings/OpenAISettings'; import { useConfigStore } from '../../../src/renderer/stores/configStore'; import type { ConfigStore } from '../../../src/renderer/stores/configStore'; jest.mock('../../../src/renderer/stores/configStore'); const mockUseConfigStore = useConfigStore as jest.MockedFunction< typeof useConfigStore >; describe('OpenAISettings', () => { const mockSetOpenAIApiKey = jest.fn(); const mockSetOpenAIModel = jest.fn(); const mockTestOpenAIConnection = jest.fn(); const mockIsOpenAIConfigValid = jest.fn(); const defaultConfig = { openai: { apiKey: '', model: 'gpt-4o' as const, }, }; beforeEach(() => { jest.clearAllMocks(); mockUseConfigStore.mockReturnValue({ config: defaultConfig, setOpenAIApiKey: mockSetOpenAIApiKey, setOpenAIModel: mockSetOpenAIModel, testOpenAIConnection: mockTestOpenAIConnection, isOpenAIConfigValid: mockIsOpenAIConfigValid, isLoading: false, error: null, isConfigured: () => true, setHederaAccountId: jest.fn(), setHederaPrivateKey: jest.fn(), setHederaNetwork: jest.fn(), setAnthropicApiKey: jest.fn(), setAnthropicModel: jest.fn(), setLLMProvider: jest.fn(), setTheme: jest.fn().mockResolvedValue(void 0), setAutoStart: jest.fn(), setLogLevel: jest.fn(), setOperationalMode: jest.fn(), setAutonomousMode: jest.fn(), saveConfig: jest.fn().mockResolvedValue(void 0), loadConfig: jest.fn().mockResolvedValue(void 0), testHederaConnection: jest.fn().mockResolvedValue({ success: true }), testAnthropicConnection: jest.fn().mockResolvedValue({ success: true }), reset: jest.fn(), } as ConfigStore); mockIsOpenAIConfigValid.mockReturnValue(false); }); it('should render all form fields', () => { render(); expect(screen.getByText('OpenAI Configuration')).toBeInTheDocument(); expect( screen.getByText( 'Configure your OpenAI API settings for the conversational agent.' ) ).toBeInTheDocument(); expect(screen.getByText('API Key')).toBeInTheDocument(); expect(screen.getByPlaceholderText('sk-...')).toBeInTheDocument(); expect(screen.getByText('Model')).toBeInTheDocument(); expect(screen.getByRole('combobox')).toBeInTheDocument(); expect(screen.getByText('Test Connection')).toBeInTheDocument(); }); it('should update API key on input', async () => { const user = userEvent.setup(); render(); const apiKeyInput = screen.getByPlaceholderText('sk-...'); await user.type(apiKeyInput, 'sk-test123456789'); await waitFor(() => { expect(mockSetOpenAIApiKey).toHaveBeenCalledWith('sk-test123456789'); }); }); it('should update model on selection', async () => { const user = userEvent.setup(); render(); const modelSelect = screen.getByRole('combobox'); await user.selectOptions(modelSelect, 'gpt-4'); await waitFor(() => { expect(mockSetOpenAIModel).toHaveBeenCalledWith('gpt-4'); }); }); it('should show all available models', () => { render(); const modelSelect = screen.getByRole('combobox') as HTMLSelectElement; const options = Array.from(modelSelect.options).map((opt) => opt.value); expect(options).toContain('gpt-4o'); expect(options).toContain('gpt-4'); expect(options).toContain('gpt-3.5-turbo'); }); it('should show validation error for invalid API key', async () => { render(); const apiKeyInput = screen.getByPlaceholderText('sk-...'); fireEvent.change(apiKeyInput, { target: { value: 'invalid-key' } }); fireEvent.blur(apiKeyInput); await waitFor(() => { expect( screen.getByText("API key must start with 'sk-'") ).toBeInTheDocument(); }); }); it('should disable test button when config is invalid', () => { mockIsOpenAIConfigValid.mockReturnValue(false); render(); const testButton = screen.getByText('Test Connection'); expect(testButton).toBeDisabled(); }); it('should enable test button when config is valid', () => { mockIsOpenAIConfigValid.mockReturnValue(true); render(); const testButton = screen.getByText('Test Connection'); expect(testButton).not.toBeDisabled(); }); it('should test connection successfully', async () => { const user = userEvent.setup(); mockIsOpenAIConfigValid.mockReturnValue(true); mockTestOpenAIConnection.mockResolvedValue({ success: true, error: undefined, }); render(); const testButton = screen.getByText('Test Connection'); await user.click(testButton); expect(screen.getByText('Testing...')).toBeInTheDocument(); await waitFor(() => { expect(screen.getByText('Connection successful!')).toBeInTheDocument(); }); }); it('should show error on connection test failure', async () => { const user = userEvent.setup(); mockIsOpenAIConfigValid.mockReturnValue(true); mockTestOpenAIConnection.mockResolvedValue({ success: false, error: 'Invalid API key', }); render(); const testButton = screen.getByText('Test Connection'); await user.click(testButton); await waitFor(() => { expect(screen.getByText('Invalid API key')).toBeInTheDocument(); }); }); it('should handle connection test exception', async () => { const user = userEvent.setup(); mockIsOpenAIConfigValid.mockReturnValue(true); mockTestOpenAIConnection.mockRejectedValue(new Error('Network timeout')); render(); const testButton = screen.getByText('Test Connection'); await user.click(testButton); await waitFor(() => { expect(screen.getByText('Network timeout')).toBeInTheDocument(); }); }); it('should render with pre-filled values', () => { mockUseConfigStore.mockReturnValue({ config: { openai: { apiKey: 'sk-existingkey123456789', model: 'gpt-4', }, }, setOpenAIApiKey: mockSetOpenAIApiKey, setOpenAIModel: mockSetOpenAIModel, testOpenAIConnection: mockTestOpenAIConnection, isOpenAIConfigValid: mockIsOpenAIConfigValid, isLoading: false, error: null, isConfigured: () => true, setHederaAccountId: jest.fn(), setHederaPrivateKey: jest.fn(), setHederaNetwork: jest.fn(), setAnthropicApiKey: jest.fn(), setAnthropicModel: jest.fn(), setLLMProvider: jest.fn(), setTheme: jest.fn().mockResolvedValue(void 0), setAutoStart: jest.fn(), setLogLevel: jest.fn(), setOperationalMode: jest.fn(), setAutonomousMode: jest.fn(), saveConfig: jest.fn().mockResolvedValue(void 0), loadConfig: jest.fn().mockResolvedValue(void 0), testHederaConnection: jest.fn().mockResolvedValue({ success: true }), testAnthropicConnection: jest.fn().mockResolvedValue({ success: true }), reset: jest.fn(), } as ConfigStore); render(); const apiKeyInput = screen.getByPlaceholderText( 'sk-...' ) as HTMLInputElement; expect(apiKeyInput.value).toBe('sk-existingkey123456789'); const modelSelect = screen.getByRole('combobox') as HTMLSelectElement; expect(modelSelect.value).toBe('gpt-4'); }); it('should mask API key input', () => { render(); const apiKeyInput = screen.getByPlaceholderText( 'sk-...' ) as HTMLInputElement; expect(apiKeyInput.type).toBe('password'); }); it('should show security note for API key', () => { render(); expect( screen.getByText( 'Your API key is encrypted and stored securely using the system keychain.' ) ).toBeInTheDocument(); }); it('should show model cost information', () => { render(); expect( screen.getByText( 'GPT-4o is the latest and most capable model. GPT-4 provides great responses but costs more per token than GPT-3.5 Turbo.' ) ).toBeInTheDocument(); }); it('should set default model to gpt-4o when empty', async () => { const _user = userEvent.setup(); render(); const modelSelect = screen.getByRole('combobox'); fireEvent.change(modelSelect, { target: { value: '' } }); fireEvent.blur(modelSelect); await waitFor(() => { expect(mockSetOpenAIModel).toHaveBeenCalledWith('gpt-4o'); }); }); });