@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
text/typescript
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();
});
});
});