UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

421 lines (336 loc) 12.5 kB
import { NetworkProxySettings } from '@lobechat/electron-client-ipc'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { App } from '@/core/App'; import NetworkProxyCtr from '../NetworkProxyCtr'; // 模拟 logger vi.mock('@/utils/logger', () => ({ createLogger: () => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }), })); // 模拟 undici - 使用 vi.fn() 直接在 Mock 中创建 vi.mock('undici', () => ({ fetch: vi.fn(), getGlobalDispatcher: vi.fn(), setGlobalDispatcher: vi.fn(), Agent: vi.fn(), ProxyAgent: vi.fn(), })); // 模拟 defaultProxySettings vi.mock('@/const/store', () => ({ defaultProxySettings: { enableProxy: false, proxyBypass: 'localhost,127.0.0.1,::1', proxyPort: '', proxyRequireAuth: false, proxyServer: '', proxyType: 'http', }, })); // 模拟 App 及其依赖项 const mockStoreManager = { get: vi.fn(), set: vi.fn(), }; const mockApp = { storeManager: mockStoreManager, } as unknown as App; describe('NetworkProxyCtr', () => { let networkProxyCtr: NetworkProxyCtr; // 动态导入 undici 的 Mock let mockUndici: any; beforeEach(async () => { vi.clearAllMocks(); // 动态导入 undici Mock mockUndici = await import('undici'); networkProxyCtr = new NetworkProxyCtr(mockApp); // 设置 undici mocks 的默认返回值 vi.mocked(mockUndici.Agent).mockReturnValue({}); vi.mocked(mockUndici.ProxyAgent).mockReturnValue({}); vi.mocked(mockUndici.getGlobalDispatcher).mockReturnValue({ destroy: vi.fn().mockResolvedValue(undefined), }); vi.mocked(mockUndici.setGlobalDispatcher).mockReturnValue(undefined); // 设置 fetch mock 的默认返回值 vi.mocked(mockUndici.fetch).mockResolvedValue({ ok: true, status: 200, statusText: 'OK', }); }); describe('ProxyConfigValidator', () => { const validConfig: NetworkProxySettings = { enableProxy: true, proxyType: 'http', proxyServer: 'proxy.example.com', proxyPort: '8080', proxyRequireAuth: false, proxyBypass: 'localhost,127.0.0.1,::1', }; it('should validate enabled proxy config with all required fields', () => { // 通过测试公共方法来间接测试验证逻辑 expect(() => networkProxyCtr.setProxySettings(validConfig)).not.toThrow(); }); it('should validate disabled proxy config', () => { const disabledConfig: NetworkProxySettings = { ...validConfig, enableProxy: false, }; expect(() => networkProxyCtr.setProxySettings(disabledConfig)).not.toThrow(); }); it('should reject invalid proxy type', async () => { const invalidConfig: NetworkProxySettings = { ...validConfig, proxyType: 'invalid' as any, }; await expect(networkProxyCtr.setProxySettings(invalidConfig)).rejects.toThrow(); }); it('should reject missing proxy server', async () => { const invalidConfig: NetworkProxySettings = { ...validConfig, proxyServer: '', }; await expect(networkProxyCtr.setProxySettings(invalidConfig)).rejects.toThrow(); }); it('should reject invalid proxy port', async () => { const invalidConfig: NetworkProxySettings = { ...validConfig, proxyPort: 'invalid', }; await expect(networkProxyCtr.setProxySettings(invalidConfig)).rejects.toThrow(); }); it('should reject missing auth credentials when auth is required', async () => { const invalidConfig: NetworkProxySettings = { ...validConfig, proxyRequireAuth: true, proxyUsername: '', proxyPassword: '', }; await expect(networkProxyCtr.setProxySettings(invalidConfig)).rejects.toThrow(); }); }); describe('getDesktopSettings', () => { it('should return stored proxy settings', async () => { const expectedSettings: NetworkProxySettings = { enableProxy: true, proxyType: 'http', proxyServer: 'proxy.example.com', proxyPort: '8080', proxyRequireAuth: false, proxyBypass: 'localhost,127.0.0.1,::1', }; mockStoreManager.get.mockReturnValue(expectedSettings); const result = await networkProxyCtr.getDesktopSettings(); expect(result).toEqual(expectedSettings); expect(mockStoreManager.get).toHaveBeenCalledWith('networkProxy', expect.any(Object)); }); it('should return default settings when store fails', async () => { mockStoreManager.get.mockImplementation(() => { throw new Error('Store error'); }); const result = await networkProxyCtr.getDesktopSettings(); expect(result).toEqual({ enableProxy: false, proxyBypass: 'localhost,127.0.0.1,::1', proxyPort: '', proxyRequireAuth: false, proxyServer: '', proxyType: 'http', }); }); }); describe('setProxySettings', () => { const validConfig: NetworkProxySettings = { enableProxy: true, proxyType: 'http', proxyServer: 'proxy.example.com', proxyPort: '8080', proxyRequireAuth: false, proxyBypass: 'localhost,127.0.0.1,::1', }; it('should save valid proxy settings', async () => { mockStoreManager.get.mockReturnValue({ enableProxy: false, proxyType: 'http', proxyServer: '', proxyPort: '', proxyRequireAuth: false, proxyBypass: 'localhost,127.0.0.1,::1', }); await networkProxyCtr.setProxySettings(validConfig); expect(mockStoreManager.set).toHaveBeenCalledWith( 'networkProxy', expect.objectContaining(validConfig), ); }); it('should skip update if settings are unchanged', async () => { mockStoreManager.get.mockReturnValue(validConfig); await networkProxyCtr.setProxySettings(validConfig); expect(mockStoreManager.set).not.toHaveBeenCalled(); }); it('should throw error for invalid configuration', async () => { const invalidConfig: NetworkProxySettings = { ...validConfig, proxyServer: '', }; await expect(networkProxyCtr.setProxySettings(invalidConfig)).rejects.toThrow(); }); }); describe('testProxyConnection', () => { it('should return success for successful connection', async () => { const mockResponse = { ok: true, status: 200, statusText: 'OK', }; vi.mocked(mockUndici.fetch).mockResolvedValueOnce(mockResponse); const result = await networkProxyCtr.testProxyConnection('https://www.google.com'); expect(result).toEqual({ success: true }); expect(mockUndici.fetch).toHaveBeenCalledWith('https://www.google.com', expect.any(Object)); }); it('should throw error for failed connection', async () => { const mockResponse = { ok: false, status: 404, statusText: 'Not Found', }; vi.mocked(mockUndici.fetch).mockResolvedValueOnce(mockResponse); await expect(networkProxyCtr.testProxyConnection('https://www.google.com')).rejects.toThrow(); }); it('should throw error for network error', async () => { vi.mocked(mockUndici.fetch).mockRejectedValueOnce(new Error('Network error')); await expect(networkProxyCtr.testProxyConnection('https://www.google.com')).rejects.toThrow(); }); }); describe('testProxyConfig', () => { const validConfig: NetworkProxySettings = { enableProxy: true, proxyType: 'http', proxyServer: 'proxy.example.com', proxyPort: '8080', proxyRequireAuth: false, proxyBypass: 'localhost,127.0.0.1,::1', }; it('should return success for valid config and successful connection', async () => { const mockResponse = { ok: true, status: 200, statusText: 'OK', }; vi.mocked(mockUndici.fetch).mockResolvedValueOnce(mockResponse); const result = await networkProxyCtr.testProxyConfig({ config: validConfig }); expect(result.success).toBe(true); expect(result.responseTime).toBeGreaterThanOrEqual(0); }); it('should return failure for invalid config', async () => { const invalidConfig: NetworkProxySettings = { ...validConfig, proxyServer: '', }; const result = await networkProxyCtr.testProxyConfig({ config: invalidConfig }); expect(result.success).toBe(false); expect(result.message).toContain('Invalid proxy configuration'); }); it('should test direct connection for disabled proxy', async () => { const disabledConfig: NetworkProxySettings = { ...validConfig, enableProxy: false, }; const mockResponse = { ok: true, status: 200, statusText: 'OK', }; vi.mocked(mockUndici.fetch).mockResolvedValueOnce(mockResponse); const result = await networkProxyCtr.testProxyConfig({ config: disabledConfig }); expect(result.success).toBe(true); }); it('should return failure for connection error', async () => { vi.mocked(mockUndici.fetch).mockRejectedValueOnce(new Error('Connection failed')); const result = await networkProxyCtr.testProxyConfig({ config: validConfig }); expect(result.success).toBe(false); expect(result.message).toContain('Connection failed'); }); }); describe('beforeAppReady', () => { it('should apply stored proxy settings on app ready', async () => { const storedConfig: NetworkProxySettings = { enableProxy: true, proxyType: 'http', proxyServer: 'proxy.example.com', proxyPort: '8080', proxyRequireAuth: false, proxyBypass: 'localhost,127.0.0.1,::1', }; mockStoreManager.get.mockReturnValue(storedConfig); await networkProxyCtr.beforeAppReady(); expect(mockStoreManager.get).toHaveBeenCalledWith('networkProxy', expect.any(Object)); }); it('should use default settings if stored config is invalid', async () => { const invalidConfig: NetworkProxySettings = { enableProxy: true, proxyType: 'http', proxyServer: '', // 无效的服务器 proxyPort: '8080', proxyRequireAuth: false, proxyBypass: 'localhost,127.0.0.1,::1', }; mockStoreManager.get.mockReturnValue(invalidConfig); await networkProxyCtr.beforeAppReady(); expect(mockStoreManager.get).toHaveBeenCalledWith('networkProxy', expect.any(Object)); }); it('should handle errors gracefully', async () => { mockStoreManager.get.mockImplementation(() => { throw new Error('Store error'); }); // 不应该抛出错误 await expect(networkProxyCtr.beforeAppReady()).resolves.not.toThrow(); mockStoreManager.get.mockReset(); }); }); describe('ProxyUrlBuilder', () => { it('should build URL without authentication', () => { const config: NetworkProxySettings = { enableProxy: true, proxyType: 'http', proxyServer: 'proxy.example.com', proxyPort: '8080', proxyRequireAuth: false, proxyBypass: 'localhost,127.0.0.1,::1', }; // 通过测试代理设置来间接测试 URL 构建 expect(() => networkProxyCtr.setProxySettings(config)).not.toThrow(); }); it('should build URL with authentication', () => { const config: NetworkProxySettings = { enableProxy: true, proxyType: 'http', proxyServer: 'proxy.example.com', proxyPort: '8080', proxyRequireAuth: true, proxyUsername: 'user', proxyPassword: 'pass', proxyBypass: 'localhost,127.0.0.1,::1', }; // 通过测试代理设置来间接测试 URL 构建 expect(() => networkProxyCtr.setProxySettings(config)).not.toThrow(); }); it('should handle special characters in credentials', () => { const config: NetworkProxySettings = { enableProxy: true, proxyType: 'http', proxyServer: 'proxy.example.com', proxyPort: '8080', proxyRequireAuth: true, proxyUsername: 'user@domain', proxyPassword: 'pass:word', proxyBypass: 'localhost,127.0.0.1,::1', }; // 通过测试代理设置来间接测试 URL 构建 expect(() => networkProxyCtr.setProxySettings(config)).not.toThrow(); }); }); });